From 0a96e8e006e9bd5839d81279ac83945ed1eb6097 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Fri, 18 Oct 2024 16:50:38 -0400 Subject: [PATCH] rfc/issue 1167 content as data (#1266) * initial implementation of content collections with rich frontmatter support * add test cases for collections and prerendering * rename interpolateFrontmatter to activeFrontmatter * refactor id and lable graph properties * refactor graph title behavior * full graph and graphql plugin refactoring * update website for new graph refactoring * make sure active frontmatter is enabled for title substition * restore header nav ordering * comment cleanup * eslint ignore * support multiple import maps * add id to graph and refactor usage * refactoring pagePath to pageHref * update test cases * rename data/queries.js to client.js * handle default title from graph and provide default graph content as data in layouts * handle default title from graph and provide default graph content as data in layouts * refactor content as data handling to its own plugin * refactor for better windows interop * misc refactoring for active frontmatter * refactor outputPath to outputHref * add labels for custom side nav output * refresh content as data and GraphQL docs * update for new docs redirects * filter hidden files and improve unsupported page format detection message * opt-on content as data config and misc refactoring and TODOs * rename test case * update docs for content as data config option and patterns * conslidate content as data into dev server * misc refactoring * content as data import map test cases * fix selectors in test cases * rename test cases * consolidate configuration options and update docs * rename test cases --- greenwood.config.js | 2 +- netlify.toml | 17 +- packages/cli/src/commands/build.js | 20 +- packages/cli/src/config/rollup.config.js | 43 +- packages/cli/src/data/client.js | 25 + packages/cli/src/lib/content-utils.js | 23 + packages/cli/src/lib/layout-utils.js | 40 +- packages/cli/src/lib/walker-package-ranger.js | 36 +- packages/cli/src/lifecycles/bundle.js | 39 +- packages/cli/src/lifecycles/config.js | 12 +- packages/cli/src/lifecycles/graph.js | 274 +++++----- packages/cli/src/lifecycles/prerender.js | 42 +- packages/cli/src/lifecycles/serve.js | 21 +- .../src/plugins/resource/plugin-api-routes.js | 2 +- .../resource/plugin-content-as-data.js | 159 ++++++ .../plugins/resource/plugin-node-modules.js | 10 +- .../plugins/resource/plugin-standard-html.js | 52 +- .../plugins/resource/plugin-standard-json.js | 14 +- .../plugins/resource/plugin-static-router.js | 2 +- .../build.config.active-frontmatter.spec.js | 139 +++++ .../greenwood.config.js | 3 + .../src/layouts/blog.html | 2 +- .../src/pages/blog/first-post.md | 4 +- .../src/pages/blog/second-post.md | 17 + .../src/pages/index.html | 14 + ...ild.config.interpolate-frontmatter.spec.js | 84 --- .../greenwood.config.js | 3 - .../build.config-optimization-default.spec.js | 2 +- .../build.config-optimization-inline.spec.js | 9 +- .../build.config-optimization-none.spec.js | 2 +- ...uild.config-optimization-overrides.spec.js | 2 +- .../build.config-optimization-static.spec.js | 2 +- ...build.config.prerender-collections.spec.js | 210 ++++++++ .../greenwood.config.js | 4 + .../package.json | 3 + .../src/components/blog-posts-list.js | 27 + .../src/components/header.js | 29 ++ .../src/components/toc.js | 24 + .../src/pages/blog/first-post.md | 3 + .../src/pages/blog/index.html | 14 + .../src/pages/blog/second-post.md | 3 + .../src/pages/index.html | 15 + .../src/pages/toc.html | 15 + .../build.config.prerender.spec.js | 2 +- .../build.default.ssr-static-export.spec.js | 6 +- .../src/pages/artists.js | 10 +- .../build.default.workspace-404.spec.js | 2 +- ...ult.workspace-layouts-page-and-app.spec.js | 2 +- ...orkspace-layouts-page-bare-merging.spec.js | 2 +- ...ild.default.workspace-layouts-page.spec.js | 2 +- .../build.default.workspace-nested.spec.js | 76 +-- .../src/pages/something.owen | 1 + ...t.workspace-user-directory-mapping.spec.js | 4 +- .../build.plugins.adapter/generic-adapter.js | 11 +- .../build.plugins.context.spec.js | 2 +- .../build.plugins-source.spec.js | 2 +- .../build.plugins.source/greenwood.config.js | 6 +- .../develop.config.active-content.spec.js | 157 ++++++ .../greenwood.config.js | 3 + .../package.json | 3 + .../src/components/blog-posts-list.js | 27 + .../src/components/header.js | 29 ++ .../src/components/toc.js | 24 + .../src/pages/blog/first-post.md | 3 + .../src/pages/blog/index.html | 14 + .../src/pages/blog/second-post.md | 3 + .../src/pages/contact.html | 10 + .../src/pages/index.html | 15 + .../src/pages/toc.html | 15 + .../develop.config.base-path.spec.js | 25 - ...velop.config.polyfills-import-maps.spec.js | 94 +++- .../package.json | 5 +- .../develop.default/develop.default.spec.js | 35 +- .../cases/develop.ssr/develop.ssr.spec.js | 6 +- .../cases/develop.ssr/src/pages/artists.js | 10 +- .../serve.config.base-path.spec.js | 29 +- .../src/pages/artists.js | 8 +- .../serve.default.ssr.spec.js | 4 +- .../serve.default.ssr/src/pages/artists.js | 10 +- .../cases/serve.default/serve.default.spec.js | 25 - packages/plugin-adapter-netlify/src/index.js | 14 +- packages/plugin-adapter-vercel/src/index.js | 14 +- packages/plugin-graphql/README.md | 238 +++++++-- packages/plugin-graphql/src/index.js | 5 +- .../plugin-graphql/src/queries/children.gql | 9 +- .../plugin-graphql/src/queries/collection.gql | 13 + .../plugin-graphql/src/queries/config.gql | 11 - packages/plugin-graphql/src/queries/graph.gql | 9 +- packages/plugin-graphql/src/queries/menu.gql | 20 - packages/plugin-graphql/src/schema/config.js | 35 -- packages/plugin-graphql/src/schema/graph.js | 97 ++-- packages/plugin-graphql/src/schema/schema.js | 3 - .../develop.default/develop.default.spec.js | 3 +- .../loaders-prerender.query-children.spec.js | 2 +- .../qraphql-server/graphql-server.spec.js | 4 +- .../query-children/query-children.spec.js | 2 +- .../greenwood.config.js | 0 .../test/cases/query-collection/package.json | 7 + .../query-collection.spec.js} | 8 +- .../src/components/header.js | 8 +- .../src/layouts/page.html | 0 .../src/pages/about.md | 3 +- .../src/pages/contact.md | 3 +- .../src/pages/index.md | 0 .../cases/query-config/query-config.spec.js | 116 ----- .../query-config/src/components/footer.js | 22 - .../cases/query-config/src/pages/index.html | 12 - .../package.json | 2 +- .../query-custom-schema.spec.js | 112 +++- .../query-custom-schema/src/pages/index.html | 66 ++- .../cases/query-graph/query-graph.spec.js | 4 +- .../cases/query-graph/src/pages/index.html | 2 +- .../test/cases/query-menu/greenwood.config.js | 11 - .../plugin-graphql/test/unit/common.spec.js | 8 +- .../plugin-graphql/test/unit/mocks/config.js | 12 - .../plugin-graphql/test/unit/mocks/graph.js | 272 +++++----- .../test/unit/schema/config.spec.js | 53 -- .../test/unit/schema/graph.children.spec.js | 85 +++ .../test/unit/schema/graph.collection.spec.js | 65 +++ .../test/unit/schema/graph.menu.spec.js | 456 ---------------- .../test/unit/schema/graph.spec.js | 31 +- .../cases/serve.default/serve.default.spec.js | 14 +- .../cases/serve.default/src/pages/artists.js | 10 +- .../src/plugins/server.js | 5 +- .../src/puppeteer-handler.js | 3 +- www/components/header/header.js | 12 +- www/components/shelf/shelf.js | 22 +- www/pages/about/community.md | 6 +- www/pages/about/features.md | 8 +- www/pages/about/goals.md | 6 +- www/pages/about/how-it-works.md | 8 +- www/pages/about/index.md | 6 +- www/pages/about/tech-stack.md | 8 +- www/pages/blog/index.md | 6 +- www/pages/blog/release/v0-15-0.md | 1 - www/pages/blog/release/v0-18-0.md | 1 - www/pages/blog/release/v0-19-0.md | 1 - www/pages/blog/release/v0-20-0.md | 1 - www/pages/blog/release/v0-21-0.md | 1 - www/pages/blog/release/v0-23-0.md | 1 - www/pages/blog/release/v0-24-0.md | 1 - www/pages/blog/release/v0-26-0.md | 1 - www/pages/blog/release/v0-27-0.md | 1 - www/pages/blog/release/v0-28-0.md | 1 - www/pages/blog/release/v0-29-0.md | 1 - www/pages/blog/state-of-greenwood-2022.md | 1 - www/pages/blog/state-of-greenwood-2023.md | 1 - www/pages/docs/api-routes.md | 10 +- www/pages/docs/component-model.md | 6 +- www/pages/docs/configuration.md | 66 +-- www/pages/docs/css-and-images.md | 10 +- www/pages/docs/data.md | 486 +++++++----------- www/pages/docs/front-matter.md | 54 +- www/pages/docs/index.md | 6 +- www/pages/docs/layouts.md | 10 +- www/pages/docs/markdown.md | 8 +- www/pages/docs/menus.md | 295 ----------- www/pages/docs/scripts.md | 10 +- www/pages/docs/server-rendering.md | 18 +- www/pages/getting-started/branding.md | 10 +- www/pages/getting-started/build-and-deploy.md | 5 +- www/pages/getting-started/creating-content.md | 8 +- www/pages/getting-started/index.md | 6 +- www/pages/getting-started/key-concepts.md | 8 +- www/pages/getting-started/next-steps.md | 6 +- www/pages/getting-started/optimizing.md | 8 +- www/pages/getting-started/project-setup.md | 8 +- www/pages/getting-started/quick-start.md | 8 +- .../guides/cloudflare-workers-deployment.md | 7 +- www/pages/guides/firebase.md | 6 +- www/pages/guides/github-pages.md | 6 +- www/pages/guides/index.md | 6 +- www/pages/guides/netlify-cms.md | 6 +- www/pages/guides/netlify-deploy.md | 6 +- www/pages/guides/now.md | 6 +- www/pages/guides/s3-cloudfront.md | 6 +- www/pages/guides/theme-packs.md | 6 +- www/pages/plugins/adapter.md | 19 +- www/pages/plugins/context.md | 6 +- www/pages/plugins/copy.md | 6 +- www/pages/plugins/custom-plugins.md | 6 +- www/pages/plugins/index.md | 6 +- www/pages/plugins/renderer.md | 6 +- www/pages/plugins/resource.md | 6 +- www/pages/plugins/rollup.md | 6 +- www/pages/plugins/server.md | 6 +- www/pages/plugins/source.md | 6 +- 187 files changed, 2666 insertions(+), 2617 deletions(-) create mode 100644 packages/cli/src/data/client.js create mode 100644 packages/cli/src/lib/content-utils.js create mode 100644 packages/cli/src/plugins/resource/plugin-content-as-data.js create mode 100644 packages/cli/test/cases/build.config.active-frontmatter/build.config.active-frontmatter.spec.js create mode 100644 packages/cli/test/cases/build.config.active-frontmatter/greenwood.config.js rename packages/cli/test/cases/{build.config.interpolate-frontmatter => build.config.active-frontmatter}/src/layouts/blog.html (69%) rename packages/cli/test/cases/{build.config.interpolate-frontmatter => build.config.active-frontmatter}/src/pages/blog/first-post.md (55%) create mode 100644 packages/cli/test/cases/build.config.active-frontmatter/src/pages/blog/second-post.md create mode 100644 packages/cli/test/cases/build.config.active-frontmatter/src/pages/index.html delete mode 100644 packages/cli/test/cases/build.config.interpolate-frontmatter/build.config.interpolate-frontmatter.spec.js delete mode 100644 packages/cli/test/cases/build.config.interpolate-frontmatter/greenwood.config.js create mode 100644 packages/cli/test/cases/build.config.prerender-collections/build.config.prerender-collections.spec.js create mode 100644 packages/cli/test/cases/build.config.prerender-collections/greenwood.config.js create mode 100644 packages/cli/test/cases/build.config.prerender-collections/package.json create mode 100644 packages/cli/test/cases/build.config.prerender-collections/src/components/blog-posts-list.js create mode 100644 packages/cli/test/cases/build.config.prerender-collections/src/components/header.js create mode 100644 packages/cli/test/cases/build.config.prerender-collections/src/components/toc.js create mode 100644 packages/cli/test/cases/build.config.prerender-collections/src/pages/blog/first-post.md create mode 100644 packages/cli/test/cases/build.config.prerender-collections/src/pages/blog/index.html create mode 100644 packages/cli/test/cases/build.config.prerender-collections/src/pages/blog/second-post.md create mode 100644 packages/cli/test/cases/build.config.prerender-collections/src/pages/index.html create mode 100644 packages/cli/test/cases/build.config.prerender-collections/src/pages/toc.html create mode 100644 packages/cli/test/cases/build.default.workspace-top-level-pages/src/pages/something.owen create mode 100644 packages/cli/test/cases/develop.config.active-content/develop.config.active-content.spec.js create mode 100644 packages/cli/test/cases/develop.config.active-content/greenwood.config.js create mode 100644 packages/cli/test/cases/develop.config.active-content/package.json create mode 100644 packages/cli/test/cases/develop.config.active-content/src/components/blog-posts-list.js create mode 100644 packages/cli/test/cases/develop.config.active-content/src/components/header.js create mode 100644 packages/cli/test/cases/develop.config.active-content/src/components/toc.js create mode 100644 packages/cli/test/cases/develop.config.active-content/src/pages/blog/first-post.md create mode 100644 packages/cli/test/cases/develop.config.active-content/src/pages/blog/index.html create mode 100644 packages/cli/test/cases/develop.config.active-content/src/pages/blog/second-post.md create mode 100644 packages/cli/test/cases/develop.config.active-content/src/pages/contact.html create mode 100644 packages/cli/test/cases/develop.config.active-content/src/pages/index.html create mode 100644 packages/cli/test/cases/develop.config.active-content/src/pages/toc.html create mode 100644 packages/plugin-graphql/src/queries/collection.gql delete mode 100644 packages/plugin-graphql/src/queries/config.gql delete mode 100644 packages/plugin-graphql/src/queries/menu.gql delete mode 100644 packages/plugin-graphql/src/schema/config.js rename packages/plugin-graphql/test/cases/{query-config => query-collection}/greenwood.config.js (100%) create mode 100644 packages/plugin-graphql/test/cases/query-collection/package.json rename packages/plugin-graphql/test/cases/{query-menu/query-menu.spec.js => query-collection/query-collection.spec.js} (96%) rename packages/plugin-graphql/test/cases/{query-menu => query-collection}/src/components/header.js (83%) rename packages/plugin-graphql/test/cases/{query-menu => query-collection}/src/layouts/page.html (100%) rename packages/plugin-graphql/test/cases/{query-menu => query-collection}/src/pages/about.md (61%) rename packages/plugin-graphql/test/cases/{query-menu => query-collection}/src/pages/contact.md (65%) rename packages/plugin-graphql/test/cases/{query-menu => query-collection}/src/pages/index.md (100%) delete mode 100644 packages/plugin-graphql/test/cases/query-config/query-config.spec.js delete mode 100644 packages/plugin-graphql/test/cases/query-config/src/components/footer.js delete mode 100644 packages/plugin-graphql/test/cases/query-config/src/pages/index.html rename packages/plugin-graphql/test/cases/{query-menu => query-custom-schema}/package.json (58%) delete mode 100644 packages/plugin-graphql/test/cases/query-menu/greenwood.config.js delete mode 100644 packages/plugin-graphql/test/unit/mocks/config.js delete mode 100644 packages/plugin-graphql/test/unit/schema/config.spec.js create mode 100644 packages/plugin-graphql/test/unit/schema/graph.children.spec.js create mode 100644 packages/plugin-graphql/test/unit/schema/graph.collection.spec.js delete mode 100644 packages/plugin-graphql/test/unit/schema/graph.menu.spec.js delete mode 100644 www/pages/docs/menus.md diff --git a/greenwood.config.js b/greenwood.config.js index 216952137..2ba3aca86 100644 --- a/greenwood.config.js +++ b/greenwood.config.js @@ -10,7 +10,7 @@ export default { workspace: new URL('./www/', import.meta.url), optimization: 'inline', staticRouter: true, - interpolateFrontmatter: true, + activeContent: true, plugins: [ greenwoodPluginGraphQL(), greenwoodPluginPolyfills({ diff --git a/netlify.toml b/netlify.toml index 3db7ff18d..5b5829366 100644 --- a/netlify.toml +++ b/netlify.toml @@ -10,4 +10,19 @@ [[redirects]] from = "/docs/tech-stack/" - to = "/about/tech-stack/" \ No newline at end of file + to = "/about/tech-stack/" + +[[redirects]] + from = "/docs/menus/:splat" + to = "/docs/data/" + status = 200 + +[[redirects]] + from = "/docs/data/#external-sources" + to = "/docs/data/#pages-data" + status = 200 + +[[redirects]] + from = "/docs/data/#internal-sources" + to = "/docs/data/#pages-data" + status = 200 \ No newline at end of file diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index 82937397c..87f13e2d1 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -1,6 +1,7 @@ import { bundleCompilation } from '../lifecycles/bundle.js'; import { checkResourceExists } from '../lib/resource-utils.js'; import { copyAssets } from '../lifecycles/copy.js'; +import { getDevServer } from '../lifecycles/serve.js'; import fs from 'fs/promises'; import { preRenderCompilationWorker, preRenderCompilationCustom, staticRenderCompilation } from '../lifecycles/prerender.js'; import { ServerInterface } from '../lib/server-interface.js'; @@ -10,7 +11,7 @@ const runProductionBuild = async (compilation) => { return new Promise(async (resolve, reject) => { try { - const { prerender } = compilation.config; + const { prerender, activeContent, plugins } = compilation.config; const outputDir = compilation.context.outputDir; const prerenderPlugin = compilation.config.plugins.find(plugin => plugin.type === 'renderer') ? compilation.config.plugins.find(plugin => plugin.type === 'renderer').provider(compilation) @@ -18,6 +19,7 @@ const runProductionBuild = async (compilation) => { const adapterPlugin = compilation.config.plugins.find(plugin => plugin.type === 'adapter') ? compilation.config.plugins.find(plugin => plugin.type === 'adapter').provider(compilation) : null; + const shouldPrerender = prerender || prerenderPlugin.prerender; if (!await checkResourceExists(outputDir)) { await fs.mkdir(outputDir, { @@ -25,10 +27,10 @@ const runProductionBuild = async (compilation) => { }); } - if (prerender || prerenderPlugin.prerender) { - // start any servers if needed + if (shouldPrerender || (activeContent && shouldPrerender)) { + // start any of the user's server plugins if needed const servers = [...compilation.config.plugins.filter((plugin) => { - return plugin.type === 'server'; + return plugin.type === 'server' && !plugin.isGreenwoodDefaultPlugin; }).map((plugin) => { const provider = plugin.provider(compilation); @@ -39,6 +41,16 @@ const runProductionBuild = async (compilation) => { return provider; })]; + if (activeContent) { + (await getDevServer({ + ...compilation, + // prune for the content as data plugin and start the dev server with only that plugin enabled + plugins: [plugins.find(plugin => plugin.name === 'plugin-active-content')] + })).listen(compilation.config.devServer.port, () => { + console.info('Initializing active content...'); + }); + } + await Promise.all(servers.map(async (server) => { await server.start(); diff --git a/packages/cli/src/config/rollup.config.js b/packages/cli/src/config/rollup.config.js index 53734d8e7..a434c3bc8 100644 --- a/packages/cli/src/config/rollup.config.js +++ b/packages/cli/src/config/rollup.config.js @@ -167,7 +167,7 @@ function greenwoodSyncSsrEntryPointsOutputPaths(compilation) { name: 'greenwood-sync-ssr-pages-entry-point-output-paths', generateBundle(options, bundle) { const { basePath } = compilation.config; - const { scratchDir } = compilation.context; + const { scratchDir, outputDir } = compilation.context; // map rollup bundle names back to original SSR pages for syncing input <> output bundle names Object.keys(bundle).forEach((key) => { @@ -178,7 +178,7 @@ function greenwoodSyncSsrEntryPointsOutputPaths(compilation) { compilation.graph.forEach((page, idx) => { if (page.route === route) { - compilation.graph[idx].outputPath = key; + compilation.graph[idx].outputHref = new URL(`./${key}`, outputDir).href; } }); } @@ -192,7 +192,7 @@ function greenwoodSyncApiRoutesOutputPath(compilation) { name: 'greenwood-sync-api-routes-output-paths', generateBundle(options, bundle) { const { basePath } = compilation.config; - const { apisDir } = compilation.context; + const { apisDir, outputDir } = compilation.context; // map rollup bundle names back to original SSR pages for syncing input <> output bundle names Object.keys(bundle).forEach((key) => { @@ -206,7 +206,7 @@ function greenwoodSyncApiRoutesOutputPath(compilation) { compilation.manifest.apis.set(route, { ...api, - outputPath: `/api/${key}` + outputHref: new URL(`./api/${key}`, outputDir).href }); } } @@ -353,8 +353,9 @@ function greenwoodImportMetaUrl(compilation) { if (`${compilation.context.apisDir.pathname}${idAssetName}`.indexOf(normalizedId) >= 0) { for (const entry of compilation.manifest.apis.keys()) { const apiRoute = compilation.manifest.apis.get(entry); + const pagePath = apiRoute.pageHref.replace(`${compilation.context.pagesDir}api/`, ''); - if (normalizedId.endsWith(apiRoute.path)) { + if (normalizedId.endsWith(pagePath)) { const assets = apiRoute.assets || []; assets.push(assetUrl.url.href); @@ -643,21 +644,21 @@ const getRollupConfigForBrowserScripts = async (compilation) => { }; const getRollupConfigForApiRoutes = async (compilation) => { - const { outputDir, pagesDir, apisDir } = compilation.context; + const { outputDir } = compilation.context; return [...compilation.manifest.apis.values()] - .map(api => normalizePathnameForWindows(new URL(`.${api.path}`, pagesDir))) - .map((filepath) => { - // account for windows pathname shenanigans by "casting" filepath to a URL first - const ext = filepath.split('.').pop(); - const entryName = new URL(`file://${filepath}`).pathname.replace(apisDir.pathname, '').replace(/\//g, '-').replace(`.${ext}`, ''); + .map((api) => { + const { id, pageHref } = api; + return { id, inputPath: normalizePathnameForWindows(new URL(pageHref)) }; + }) + .map(({ id, inputPath }) => { return { - input: filepath, + input: inputPath, output: { dir: `${normalizePathnameForWindows(outputDir)}/api`, - entryFileNames: `${entryName}.js`, - chunkFileNames: `${entryName}.[hash].js` + entryFileNames: `${id}.js`, + chunkFileNames: `${id}.[hash].js` }, plugins: [ greenwoodResourceLoader(compilation), @@ -696,20 +697,16 @@ const getRollupConfigForApiRoutes = async (compilation) => { }); }; -const getRollupConfigForSsrPages = async (compilation, input) => { +const getRollupConfigForSsrPages = async (compilation, inputs) => { const { outputDir } = compilation.context; - return input.map((filepath) => { - const ext = filepath.split('.').pop(); - // account for windows pathname shenanigans by "casting" filepath to a URL first - const entryName = new URL(`file://${filepath}`).pathname.replace(compilation.context.scratchDir.pathname, '').replace('/', '-').replace(`.${ext}`, ''); - + return inputs.map(({ id, inputPath }) => { return { - input: filepath, + input: inputPath, output: { dir: normalizePathnameForWindows(outputDir), - entryFileNames: `${entryName}.route.js`, - chunkFileNames: `${entryName}.route.chunk.[hash].js` + entryFileNames: `${id}.route.js`, + chunkFileNames: `${id}.route.chunk.[hash].js` }, plugins: [ greenwoodResourceLoader(compilation), diff --git a/packages/cli/src/data/client.js b/packages/cli/src/data/client.js new file mode 100644 index 000000000..2d96b1b8d --- /dev/null +++ b/packages/cli/src/data/client.js @@ -0,0 +1,25 @@ +const CONTENT_STATE = globalThis.__CONTENT_AS_DATA_STATE__ ?? false; // eslint-disable-line no-underscore-dangle +const PORT = globalThis?.__CONTENT_SERVER__?.PORT ?? 1985; // eslint-disable-line no-underscore-dangle +const BASE_PATH = globalThis?.__GWD_BASE_PATH__ ?? ''; // eslint-disable-line no-underscore-dangle + +async function getContentAsData(key = '') { + return CONTENT_STATE + ? await fetch(`${window.location.origin}${BASE_PATH}/data-${key.replace(/\//g, '_')}.json`) + .then(resp => resp.json()) + : await fetch(`http://localhost:${PORT}${BASE_PATH}/___graph.json`, { headers: { 'X-CONTENT-KEY': key } }) + .then(resp => resp.json()); +} + +async function getContent() { + return await getContentAsData('graph'); +} + +async function getContentByCollection(collection = '') { + return await getContentAsData(`collection-${collection}`); +} + +async function getContentByRoute(route = '') { + return await getContentAsData(`route-${route}`); +} + +export { getContent, getContentByCollection, getContentByRoute }; \ No newline at end of file diff --git a/packages/cli/src/lib/content-utils.js b/packages/cli/src/lib/content-utils.js new file mode 100644 index 000000000..6fa16af21 --- /dev/null +++ b/packages/cli/src/lib/content-utils.js @@ -0,0 +1,23 @@ +const activeFrontmatterKeys = ['route', 'label', 'title', 'id']; + +function cleanContentCollection(collection = []) { + return collection.map((page) => { + let prunedPage = {}; + + Object.keys(page).forEach((key) => { + if ([...activeFrontmatterKeys, 'data'].includes(key)) { + prunedPage[key] = page[key]; + } + }); + + return { + ...prunedPage, + title: prunedPage.title || prunedPage.label + }; + }); +} + +export { + activeFrontmatterKeys, + cleanContentCollection +}; \ No newline at end of file diff --git a/packages/cli/src/lib/layout-utils.js b/packages/cli/src/lib/layout-utils.js index 8dbf28197..1f65f9263 100644 --- a/packages/cli/src/lib/layout-utils.js +++ b/packages/cli/src/lib/layout-utils.js @@ -1,11 +1,10 @@ +/* eslint-disable complexity */ import fs from 'fs/promises'; import htmlparser from 'node-html-parser'; import { checkResourceExists } from './resource-utils.js'; import { Worker } from 'worker_threads'; async function getCustomPageLayoutsFromPlugins(compilation, layoutName) { - // TODO confirm context plugins work for SSR - // TODO support context plugins for more than just HTML files const contextPlugins = compilation.config.plugins.filter((plugin) => { return plugin.type === 'context'; }).map((plugin) => { @@ -30,10 +29,10 @@ async function getCustomPageLayoutsFromPlugins(compilation, layoutName) { return customLayoutLocations; } -async function getPageLayout(filePath, compilation, layout) { +async function getPageLayout(pageHref = '', compilation, layout) { const { config, context } = compilation; - const { layoutsDir, userLayoutsDir, pagesDir, projectDirectory } = context; - const filePathUrl = new URL(`${filePath}`, projectDirectory); + const { layoutsDir, userLayoutsDir, pagesDir } = context; + const filePathUrl = pageHref && pageHref !== '' ? new URL(pageHref) : pageHref; const customPageFormatPlugins = config.plugins .filter(plugin => plugin.type === 'resource' && !plugin.isGreenwoodDefaultPlugin) .map(plugin => plugin.provider(compilation)); @@ -43,13 +42,13 @@ async function getPageLayout(filePath, compilation, layout) { && await customPageFormatPlugins[0].shouldServe(filePathUrl); const customPluginDefaultPageLayouts = await getCustomPageLayoutsFromPlugins(compilation, 'page'); const customPluginPageLayouts = await getCustomPageLayoutsFromPlugins(compilation, layout); - const extension = filePath.split('.').pop(); - const is404Page = filePath.startsWith('404') && extension === 'html'; + const extension = pageHref?.split('.')?.pop(); + const is404Page = pageHref?.endsWith('404.html') && extension === 'html'; const hasCustomStaticLayout = await checkResourceExists(new URL(`./${layout}.html`, userLayoutsDir)); const hasCustomDynamicLayout = await checkResourceExists(new URL(`./${layout}.js`, userLayoutsDir)); const hasPageLayout = await checkResourceExists(new URL('./page.html', userLayoutsDir)); const hasCustom404Page = await checkResourceExists(new URL('./404.html', pagesDir)); - const isHtmlPage = extension === 'html' && await checkResourceExists(new URL(`./${filePath}`, projectDirectory)); + const isHtmlPage = extension === 'html' && await checkResourceExists(new URL(pageHref)); let contents; if (layout && (customPluginPageLayouts.length > 0 || hasCustomStaticLayout)) { @@ -108,11 +107,11 @@ async function getPageLayout(filePath, compilation, layout) { } /* eslint-disable-next-line complexity */ -async function getAppLayout(pageLayoutContents, compilation, customImports = [], frontmatterTitle) { +async function getAppLayout(pageLayoutContents, compilation, customImports = [], matchingRoute) { + const activeFrontmatterTitleKey = '${globalThis.page.title}'; const enableHud = compilation.config.devServer.hud; const { layoutsDir, userLayoutsDir } = compilation.context; const userStaticAppLayoutUrl = new URL('./app.html', userLayoutsDir); - // TODO support more than just .js files const userDynamicAppLayoutUrl = new URL('./app.js', userLayoutsDir); const userHasStaticAppLayout = await checkResourceExists(userStaticAppLayoutUrl); const userHasDynamicAppLayout = await checkResourceExists(userDynamicAppLayoutUrl); @@ -193,20 +192,25 @@ async function getAppLayout(pageLayoutContents, compilation, customImports = [], const appBody = appRoot.querySelector('body') ? appRoot.querySelector('body').innerHTML : ''; const pageBody = pageRoot && pageRoot.querySelector('body') ? pageRoot.querySelector('body').innerHTML : ''; const pageTitle = pageRoot && pageRoot.querySelector('head title'); - const hasInterpolatedFrontmatter = pageTitle && pageTitle.rawText.indexOf('${globalThis.page.title}') >= 0 - || appTitle && appTitle.rawText.indexOf('${globalThis.page.title}') >= 0; + const hasActiveFrontmatterTitle = compilation.config.activeContent && (pageTitle && pageTitle.rawText.indexOf(activeFrontmatterTitleKey) >= 0 + || appTitle && appTitle.rawText.indexOf(activeFrontmatterTitleKey) >= 0); + let title; - const title = hasInterpolatedFrontmatter // favor frontmatter interpolation first - ? pageTitle && pageTitle.rawText + if (hasActiveFrontmatterTitle) { + const text = pageTitle && pageTitle.rawText.indexOf(activeFrontmatterTitleKey) >= 0 ? pageTitle.rawText - : appTitle.rawText - : frontmatterTitle // otherwise, work in order of specificity from page -> page layout -> app layout - ? frontmatterTitle + : appTitle.rawText; + + title = text.replace(activeFrontmatterTitleKey, matchingRoute.title || matchingRoute.label); + } else { + title = matchingRoute.title + ? matchingRoute.title : pageTitle && pageTitle.rawText ? pageTitle.rawText : appTitle && appTitle.rawText ? appTitle.rawText - : 'My App'; + : matchingRoute.label; + } const mergedHtml = pageRoot && pageRoot.querySelector('html').rawAttrs !== '' ? `` diff --git a/packages/cli/src/lib/walker-package-ranger.js b/packages/cli/src/lib/walker-package-ranger.js index 5ce30c114..a92bb3d29 100644 --- a/packages/cli/src/lib/walker-package-ranger.js +++ b/packages/cli/src/lib/walker-package-ranger.js @@ -215,17 +215,33 @@ async function walkPackageJson(packageJson = {}) { return importMap; } -function mergeImportMap(html = '', map = {}) { - // es-modules-shims breaks on dangling commas in an importMap :/ - const danglingComma = html.indexOf('"imports": {}') > 0 ? '' : ','; - const importMap = JSON.stringify(map).replace('}', '').replace('{', ''); - - const merged = html.replace('"imports": {', ` - "imports": { - ${importMap}${danglingComma} - `); +function mergeImportMap(html = '', map = {}, shouldShim = false) { + const importMapType = shouldShim ? 'importmap-shim' : 'importmap'; + const hasImportMap = html.indexOf(`script type="${importMapType}"`) > 0; + const danglingComma = hasImportMap ? ',' : ''; + const importMap = JSON.stringify(map, null, 2).replace('}', '').replace('{', ''); + + if (Object.entries(map).length === 0) { + return html; + } - return merged; + if (hasImportMap) { + return html.replace('"imports": {', ` + "imports": { + ${importMap}${danglingComma} + `); + } else { + return html.replace('', ` + + + `); + } } export { diff --git a/packages/cli/src/lifecycles/bundle.js b/packages/cli/src/lifecycles/bundle.js index 9c963f786..814d1b946 100644 --- a/packages/cli/src/lifecycles/bundle.js +++ b/packages/cli/src/lifecycles/bundle.js @@ -83,10 +83,10 @@ async function optimizeStaticPages(compilation, plugins) { return Promise.all(compilation.graph .filter(page => !page.isSSR || (page.isSSR && page.prerender) || (page.isSSR && compilation.config.prerender)) .map(async (page) => { - const { route, outputPath } = page; - const outputDirUrl = new URL(`.${outputPath.replace('index.html', '').replace('404.html', '')}`, outputDir); + const { route, outputHref } = page; + const outputDirUrl = new URL(outputHref.replace('index.html', '').replace('404.html', '')); const url = new URL(`http://localhost:${compilation.config.port}${route}`); - const contents = await fs.readFile(new URL(`./${outputPath}`, scratchDir), 'utf-8'); + const contents = await fs.readFile(new URL(`./${outputHref.replace(outputDir.href, '')}`, scratchDir), 'utf-8'); const headers = new Headers({ 'Content-Type': 'text/html' }); let response = new Response(contents, { headers }); @@ -107,7 +107,7 @@ async function optimizeStaticPages(compilation, plugins) { // clean up optimization markers const body = (await response.text()).replace(/data-gwd-opt=".*?[a-z]"/g, ''); - await fs.writeFile(new URL(`.${outputPath}`, outputDir), body); + await fs.writeFile(new URL(outputHref), body); }) ); } @@ -243,16 +243,14 @@ async function bundleSsrPages(compilation, optimizePlugins) { // and before we optimize so that all bundled assets can tracked up front // would be nice to see if this can be done in a single pass though... for (const page of ssrPages) { - const { imports, route, layout, title, relativeWorkspacePagePath } = page; - const moduleUrl = new URL(`.${relativeWorkspacePagePath}`, pagesDir); + const { imports, route, layout, pageHref } = page; + const moduleUrl = new URL(pageHref); const request = new Request(moduleUrl); - // TODO getLayout has to be static (for now?) - // https://github.com/ProjectEvergreen/greenwood/issues/955 const data = await executeRouteModule({ moduleUrl, compilation, page, prerender: false, htmlContents: null, scripts: [], request }); let staticHtml = ''; - staticHtml = data.layout ? data.layout : await getPageLayout(staticHtml, compilation, layout); - staticHtml = await getAppLayout(staticHtml, compilation, imports, title); + staticHtml = data.layout ? data.layout : await getPageLayout(pageHref, compilation, layout); + staticHtml = await getAppLayout(staticHtml, compilation, imports, page); staticHtml = await getUserScripts(staticHtml, compilation); staticHtml = await (await interceptPage(new URL(`http://localhost:8080${route}`), new Request(new URL(`http://localhost:8080${route}`)), getPluginInstances(compilation), staticHtml)).text(); @@ -268,14 +266,14 @@ async function bundleSsrPages(compilation, optimizePlugins) { // second pass to link all bundled assets to their resources before optimizing and generating SSR bundles for (const page of ssrPages) { - const { filename, route, relativeWorkspacePagePath } = page; - const entryFileUrl = new URL(`.${relativeWorkspacePagePath}`, scratchDir); - const outputPathRootUrl = new URL(`file://${path.dirname(entryFileUrl.pathname)}`); + const { id, route, pageHref } = page; + const pagePath = new URL(pageHref).pathname.replace(pagesDir.pathname, './'); + const entryFileUrl = new URL(pageHref); + const entryFileOutputUrl = new URL(`file://${entryFileUrl.pathname.replace(pagesDir.pathname, scratchDir.pathname)}`); + const outputPathRootUrl = new URL(`file://${path.dirname(entryFileOutputUrl.pathname)}/`); const htmlOptimizer = config.plugins.find(plugin => plugin.name === 'plugin-standard-html').provider(compilation); const pagesPathDiff = context.pagesDir.pathname.replace(context.projectDirectory.pathname, ''); - const relativeDepth = relativeWorkspacePagePath.replace(`/${filename}`, '') === '' - ? '../' - : '../'.repeat(relativeWorkspacePagePath.replace(`/${filename}`, '').split('/').length); + const relativeDepth = '../'.repeat(pagePath.split('/').length - 1); let staticHtml = ssrPrerenderPagesRouteMapper[route]; staticHtml = await (await htmlOptimizer.optimize(new URL(`http://localhost:8080${route}`), new Response(staticHtml))).text(); @@ -288,10 +286,10 @@ async function bundleSsrPages(compilation, optimizePlugins) { } // better way to write out this inline code? - await fs.writeFile(entryFileUrl, ` + await fs.writeFile(entryFileOutputUrl, ` import { executeRouteModule } from '${normalizePathnameForWindows(executeModuleUrl)}'; - const moduleUrl = new URL('${relativeDepth}${pagesPathDiff}${relativeWorkspacePagePath.replace('/', '')}', import.meta.url); + const moduleUrl = new URL('${relativeDepth}${pagesPathDiff}${pagePath.replace('./', '')}', import.meta.url); export async function handler(request) { const compilation = JSON.parse('${JSON.stringify(compilation)}'); @@ -311,7 +309,10 @@ async function bundleSsrPages(compilation, optimizePlugins) { } `); - input.push(normalizePathnameForWindows(entryFileUrl)); + input.push({ + id, + inputPath: normalizePathnameForWindows(entryFileOutputUrl) + }); } const ssrConfigs = await getRollupConfigForSsrPages(compilation, input); diff --git a/packages/cli/src/lifecycles/config.js b/packages/cli/src/lifecycles/config.js index 9c31a230a..37b0621a8 100644 --- a/packages/cli/src/lifecycles/config.js +++ b/packages/cli/src/lifecycles/config.js @@ -46,7 +46,7 @@ const defaultConfig = { port: 8080, basePath: '', optimization: optimizations[0], - interpolateFrontmatter: false, + activeContent: false, plugins: greenwoodPlugins, markdown: { plugins: [], settings: {} }, prerender: false, @@ -82,7 +82,7 @@ const readAndMergeConfig = async() => { if (hasConfigFile) { const userCfgFile = (await import(configUrl)).default; // eslint-disable-next-line max-len - const { workspace, devServer, markdown, optimization, plugins, port, prerender, basePath, staticRouter, pagesDirectory, layoutsDirectory, interpolateFrontmatter, isolation, polyfills } = userCfgFile; + const { workspace, devServer, markdown, optimization, plugins, port, prerender, basePath, staticRouter, pagesDirectory, layoutsDirectory, activeContent, isolation, polyfills } = userCfgFile; // workspace validation if (workspace) { @@ -103,11 +103,11 @@ const readAndMergeConfig = async() => { reject(`Error: provided optimization "${optimization}" is not supported. Please use one of: ${optimizations.join(', ')}.`); } - if (interpolateFrontmatter) { - if (typeof interpolateFrontmatter !== 'boolean') { - reject('Error: greenwood.config.js interpolateFrontmatter must be a boolean'); + if (activeContent) { + if (typeof activeContent !== 'boolean') { + reject('Error: greenwood.config.js activeContent must be a boolean'); } - customConfig.interpolateFrontmatter = interpolateFrontmatter; + customConfig.activeContent = activeContent; } if (plugins && plugins.length > 0) { diff --git a/packages/cli/src/lifecycles/graph.js b/packages/cli/src/lifecycles/graph.js index 08055f0ae..3441b55ab 100644 --- a/packages/cli/src/lifecycles/graph.js +++ b/packages/cli/src/lifecycles/graph.js @@ -2,28 +2,53 @@ import fs from 'fs/promises'; import fm from 'front-matter'; import { checkResourceExists, requestAsObject } from '../lib/resource-utils.js'; +import { activeFrontmatterKeys } from '../lib/content-utils.js'; import toc from 'markdown-toc'; import { Worker } from 'worker_threads'; +function getLabelFromRoute(_route) { + let route = _route; + + if (route === '/index/') { + return 'Home'; + } else if (route.endsWith('/index/')) { + route = route.replace('index/', ''); + } + + return route + .split('/') + .filter(part => part !== '') + .pop() + .split('-') + .map((routePart) => { + return `${routePart.charAt(0).toUpperCase()}${routePart.substring(1)}`; + }) + .join(' '); +} + +function getIdFromRelativePathPath(relativePathPath, extension) { + return relativePathPath.replace(extension, '').replace('./', '').replace(/\//g, '-'); +} + const generateGraph = async (compilation) => { return new Promise(async (resolve, reject) => { try { const { context, config } = compilation; const { basePath } = config; - const { pagesDir, projectDirectory, userWorkspace } = context; + const { pagesDir, userWorkspace, outputDir } = context; + const collections = {}; const customPageFormatPlugins = config.plugins .filter(plugin => plugin.type === 'resource' && !plugin.isGreenwoodDefaultPlugin) .map(plugin => plugin.provider(compilation)); let apis = new Map(); let graph = [{ - outputPath: '/index.html', - filename: 'index.html', - path: '/', - route: `${basePath}/`, id: 'index', - label: 'Index', + outputHref: new URL('./index.html', outputDir).href, + route: `${basePath}/`, + label: 'Home', + title: null, data: {}, imports: [], resources: [], @@ -32,7 +57,7 @@ const generateGraph = async (compilation) => { }]; const walkDirectoryForPages = async function(directory, pages = [], apiRoutes = new Map()) { - const files = await fs.readdir(directory); + const files = (await fs.readdir(directory)).filter(file => !file.startsWith('.')); for (const filename of files) { const filenameUrl = new URL(`./${filename}`, directory); @@ -46,9 +71,8 @@ const generateGraph = async (compilation) => { apiRoutes = nextPages.apiRoutes; } else { const extension = `.${filenameUrl.pathname.split('.').pop()}`; - const relativePagePath = filenameUrl.pathname.replace(pagesDir.pathname, '/'); - const relativeWorkspacePath = directory.pathname.replace(projectDirectory.pathname, ''); - const isApiRoute = relativePagePath.startsWith('/api'); + const relativePagePath = filenameUrl.pathname.replace(pagesDir.pathname, './'); + const isApiRoute = relativePagePath.startsWith('./api'); const req = isApiRoute ? new Request(filenameUrl) : new Request(filenameUrl, { headers: { 'Accept': 'text/html' } }); @@ -64,6 +88,8 @@ const generateGraph = async (compilation) => { const isStatic = isCustom === 'static' || extension === '.md' || extension === '.html'; const isDynamic = isCustom === 'dynamic' || extension === '.js'; const isPage = isStatic || isDynamic; + let route = `${relativePagePath.replace('.', '').replace(`${extension}`, '')}`; + let fileContents; if (isApiRoute) { const extension = filenameUrl.pathname.split('.').pop(); @@ -73,36 +99,34 @@ const generateGraph = async (compilation) => { return; } - const relativeApiPath = filenameUrl.pathname.replace(pagesDir.pathname, '/'); - const route = `${basePath}${relativeApiPath.replace(`.${extension}`, '')}`; - // TODO should this be run in isolation like SSR pages? + // should this be run in isolation like SSR pages? // https://github.com/ProjectEvergreen/greenwood/issues/991 const { isolation } = await import(filenameUrl).then(module => module); /* * API Properties (per route) *---------------------- - * filename: base filename of the page - * outputPath: the filename to write to when generating a build - * path: path to the file relative to the workspace + * id: unique hyphen delimited string of the filename, relative to the page/api directory + * pageHref: href to the page's filesystem file + * outputHref: href of the filename to write to when generating a build * route: URL route for a given page on outputFilePath * isolation: if this should be run in isolated mode */ - apiRoutes.set(route, { - filename: filename, - outputPath: relativeApiPath, - path: relativeApiPath, - route, + apiRoutes.set(`${basePath}${route}`, { + id: getIdFromRelativePathPath(relativePagePath, `.${extension}`).replace('api-', ''), + pageHref: new URL(relativePagePath, pagesDir).href, + outputHref: new URL(relativePagePath, outputDir).href, + // outputPath: relativePagePath, + route: `${basePath}${route}`, isolation }); } else if (isPage) { - let route = relativePagePath.replace(extension, ''); - let id = filename.split('/')[filename.split('/').length - 1].replace(extension, ''); + let root = filename.split('/')[filename.split('/').length - 1].replace(extension, ''); let layout = extension === '.html' ? null : 'page'; let title = null; + let label = getLabelFromRoute(`${route}/`); let imports = []; let customData = {}; - let filePath; let prerender = true; let isolation = false; let hydration = false; @@ -115,9 +139,9 @@ const generateGraph = async (compilation) => { * - pages/blog/index.{html,md,js} -> /blog/ * - pages/blog/some-post.{html,md,js} -> /blog/some-post/ */ - if (relativePagePath.lastIndexOf('/') > 0) { + if (relativePagePath.lastIndexOf('/index') > 0) { // https://github.com/ProjectEvergreen/greenwood/issues/455 - route = id === 'index' || route.replace('/index', '') === `/${id}` + route = root === 'index' || route.replace('/index', '') === `/${root}` ? route.replace('index', '') : `${route}/`; } else { @@ -127,60 +151,19 @@ const generateGraph = async (compilation) => { } if (isStatic) { - const fileContents = await fs.readFile(filenameUrl, 'utf8'); + fileContents = await fs.readFile(filenameUrl, 'utf8'); const { attributes } = fm(fileContents); layout = attributes.layout || layout; title = attributes.title || title; - id = attributes.label || id; + label = attributes.label || label; imports = attributes.imports || []; - filePath = `${relativeWorkspacePath}${filename}`; - // prune "reserved" attributes that are supported by Greenwood - // https://www.greenwoodjs.io/docs/front-matter customData = attributes; - - delete customData.label; - delete customData.imports; - delete customData.title; - delete customData.layout; - - /* Menu Query - * Custom front matter - Variable Definitions - * -------------------------------------------------- - * menu: the name of the menu in which this item can be listed and queried - * index: the index of this list item within a menu - * linkheadings: flag to tell us where to add page's table of contents as menu items - * tableOfContents: json object containing page's table of contents(list of headings) - */ - // set specific menu to place this page - customData.menu = customData.menu || ''; - - // set specific index list priority of this item within a menu - customData.index = customData.index || ''; - - // set flag whether to gather a list of headings on a page as menu items - customData.linkheadings = customData.linkheadings || 0; - customData.tableOfContents = []; - - if (customData.linkheadings > 0) { - // parse markdown for table of contents and output to json - customData.tableOfContents = toc(fileContents).json; - customData.tableOfContents.shift(); - - // parse table of contents for only the pages user wants linked - if (customData.tableOfContents.length > 0 && customData.linkheadings > 0) { - customData.tableOfContents = customData.tableOfContents - .filter((item) => item.lvl === customData.linkheadings); - } - } - /* ---------End Menu Query-------------------- */ } else if (isDynamic) { const routeWorkerUrl = compilation.config.plugins.filter(plugin => plugin.type === 'renderer')[0].provider(compilation).executeModuleUrl; let ssrFrontmatter; - filePath = route; - await new Promise(async (resolve, reject) => { const worker = new Worker(new URL('../lib/ssr-route-worker.js', import.meta.url)); const request = await requestAsObject(new Request(filenameUrl)); @@ -213,11 +196,8 @@ const generateGraph = async (compilation) => { page: JSON.stringify({ servePage: isCustom, route, - id, - label: id.split('-') - .map((idPart) => { - return `${idPart.charAt(0).toUpperCase()}${idPart.substring(1)}`; - }).join(' ') + root, + label }), request }); @@ -227,68 +207,99 @@ const generateGraph = async (compilation) => { layout = ssrFrontmatter.layout || layout; title = ssrFrontmatter.title || title; imports = ssrFrontmatter.imports || imports; - customData = ssrFrontmatter.data || customData; - - /* Menu Query - * Custom front matter - Variable Definitions - * -------------------------------------------------- - * menu: the name of the menu in which this item can be listed and queried - * index: the index of this list item within a menu - * linkheadings: flag to tell us where to add page's table of contents as menu items - * tableOfContents: json object containing page's table of contents(list of headings) - */ - customData.menu = ssrFrontmatter.menu || ''; - customData.index = ssrFrontmatter.index || ''; + label = ssrFrontmatter.label || label; + customData = ssrFrontmatter || customData; } } /* - * Graph Properties (per page) - *---------------------- - * data: custom page frontmatter - * filename: base filename of the page - * id: filename without the extension - * relativeWorkspacePagePath: the file path relative to the user's workspace directory - * label: "pretty" text representation of the filename - * imports: per page JS or CSS file imports to be included in HTML output from frontmatter - * resources: sum of all resources for the entire page - * outputPath: the filename to write to when generating static HTML - * path: path to the file relative to the workspace - * route: URL route for a given page on outputFilePath - * layout: page layout to use as a base for a generated component - * title: a default value that can be used for - * isSSR: if this is a server side route - * prerender: if this should be statically exported - * isolation: if this should be run in isolated mode - * hydration: if this page needs hydration support - * servePage: signal that this is a custom page file type (static | dynamic) - */ - pages.push({ + * Custom front matter - Variable Definitions + * -------------------------------------------------- + * collection: the name of the collection for the page + * order: the order of this item within the collection + * tocHeading: heading size to use a Table of Contents for a page + * tableOfContents: json object containing page's table of contents (list of headings) + */ + + // prune "reserved" attributes that are supported by Greenwood + [...activeFrontmatterKeys, 'layout'].forEach((key) => { + delete customData[key]; + }); + + // set flag whether to gather a list of headings on a page as menu items + customData.tocHeading = customData.tocHeading || 0; + customData.tableOfContents = []; + + if (fileContents && customData.tocHeading > 0 && customData.tocHeading <= 6) { + // parse markdown for table of contents and output to json + customData.tableOfContents = toc(fileContents).json; + customData.tableOfContents.shift(); + + // parse table of contents for only the pages user wants linked + if (customData.tableOfContents.length > 0 && customData.tocHeading > 0) { + customData.tableOfContents = customData.tableOfContents + .filter((item) => item.lvl === customData.tocHeading); + } + } + + /* + * Page Properties + *---------------------- + * id: unique hyphen delimited string of the filename, relative to the pages directory + * label: Display text for the page inferred, by default is the value of title + * title: used to customize the tag of the page, inferred from the filename + * route: URL for accessing the page from the browser + * layout: the custom layout of the page + * data: custom page frontmatter + * imports: per page JS or CSS file imports specified from frontmatter + * resources: all script, style, etc resources for the entire page as URLs + * outputHref: href to the file in the output folder + * pageHref: href to the page's filesystem file + * isSSR: if this is a server side route + * prerender: if this page should be statically exported + * isolation: if this page should be run in isolated mode + * hydration: if this page needs hydration support + * servePage: signal that this is a custom page file type (static | dynamic) + */ + const page = { + id: getIdFromRelativePathPath(relativePagePath, extension), + label, + title, + route: `${basePath}${route}`, + layout, data: customData || {}, - filename, - id, - relativeWorkspacePagePath: relativePagePath, - label: id.split('-') - .map((idPart) => { - return `${idPart.charAt(0).toUpperCase()}${idPart.substring(1)}`; - }).join(' '), imports, resources: [], - outputPath: route === '/404/' - ? '/404.html' - : `${route}index.html`, - path: filePath, - route: `${basePath}${route}`, - layout, - title, + pageHref: new URL(relativePagePath, pagesDir).href, + outputHref: route === '/404/' + ? new URL('./404.html', outputDir).href + : new URL(`.${route}index.html`, outputDir).href, + // outputPath: route === '/404/' + // ? '/404.html' + // : `${route}index.html`, isSSR: !isStatic, prerender, isolation, hydration, servePage: isCustom - }); + }; + + pages.push(page); + + // handle collections + const pageCollection = customData.collection; + + if (pageCollection) { + if (!collections[pageCollection]) { + collections[pageCollection] = []; + } + + collections[pageCollection].push(page); + } + + compilation.collections = collections; } else { - console.debug(`Unhandled extension (${extension}) for route => ${route}`); + console.warn(`Unsupported format detected for page => ${filename}`); } } } @@ -302,7 +313,7 @@ const generateGraph = async (compilation) => { if (await checkResourceExists(new URL('./index.html', userWorkspace))) { graph = [{ ...graph[0], - path: `${userWorkspace.pathname}index.html`, + pageHref: new URL('./index.html', userWorkspace).href, isSPA: true }]; } else { @@ -325,12 +336,14 @@ const generateGraph = async (compilation) => { ...graph, { ...oldGraph, - outputPath: '/404.html', - filename: '404.html', + id: '404', + // outputPath: '/404.html', + outputHref: new URL('./404.html', outputDir).href, + pageHref: new URL('./404.html', pagesDir).href, route: `${basePath}/404/`, path: '404.html', - id: '404', - label: 'Not Found' + label: 'Not Found', + title: 'Page Not Found' } ]; } @@ -352,12 +365,11 @@ const generateGraph = async (compilation) => { } graph.push({ - filename: null, - path: null, + pageHref: null, data: {}, imports: [], resources: [], - outputPath: `${node.route}index.html`, + outputHref: new URL(`.${node.route}index.html`, outputDir).href, ...node, external: true }); diff --git a/packages/cli/src/lifecycles/prerender.js b/packages/cli/src/lifecycles/prerender.js index 67994a345..d67ffbce4 100644 --- a/packages/cli/src/lifecycles/prerender.js +++ b/packages/cli/src/lifecycles/prerender.js @@ -50,9 +50,15 @@ function getPluginInstances (compilation) { }); } +function toScratchUrl(outputHref, context) { + const { outputDir, scratchDir } = context; + + return new URL(`./${outputHref.replace(outputDir.href, '')}`, scratchDir); +} + async function preRenderCompilationWorker(compilation, workerPrerender) { const pages = compilation.graph.filter(page => !page.isSSR || (page.isSSR && page.prerender) || (page.isSSR && compilation.config.prerender)); - const { scratchDir } = compilation.context; + const { context, config } = compilation; const plugins = getPluginInstances(compilation); console.info('pages to generate', `\n ${pages.map(page => page.route).join('\n ')}`); @@ -60,9 +66,9 @@ async function preRenderCompilationWorker(compilation, workerPrerender) { const pool = new WorkerPool(os.cpus().length, new URL('../lib/ssr-route-worker.js', import.meta.url)); for (const page of pages) { - const { route, outputPath } = page; - const outputPathUrl = new URL(`.${outputPath}`, scratchDir); - const url = new URL(`http://localhost:${compilation.config.port}${route}`); + const { route, outputHref } = page; + const scratchUrl = toScratchUrl(outputHref, context); + const url = new URL(`http://localhost:${config.port}${route}`); const request = new Request(url); let ssrContents; @@ -111,23 +117,23 @@ async function preRenderCompilationWorker(compilation, workerPrerender) { body = body.replace('', ssrContents); } - await createOutputDirectory(route, new URL(outputPathUrl.href.replace('index.html', ''))); - await fs.writeFile(outputPathUrl, body); + await createOutputDirectory(route, new URL(scratchUrl.href.replace('index.html', ''))); + await fs.writeFile(scratchUrl, body); console.info('generated page...', route); } } async function preRenderCompilationCustom(compilation, customPrerender) { - const { scratchDir } = compilation.context; + const { config, context } = compilation; const renderer = (await import(customPrerender.customUrl)).default; - const { importMaps } = compilation.config.polyfills; + const { importMaps } = config.polyfills; console.info('pages to generate', `\n ${compilation.graph.map(page => page.route).join('\n ')}`); await renderer(compilation, async (page, body) => { - const { route, outputPath } = page; - const outputPathUrl = new URL(`.${outputPath}`, scratchDir); + const { route, outputHref } = page; + const scratchUrl = toScratchUrl(outputHref, context); // clean up special Greenwood dev only assets that would come through if prerendering with a headless browser if (importMaps) { @@ -142,32 +148,32 @@ async function preRenderCompilationCustom(compilation, customPrerender) { body = body.replace(/ + `); + + // Greenwood active frontmatter keys + for (const key of activeFrontmatterKeys) { + const interpolatedFrontmatter = '\\$\\{globalThis.page.' + key + '\\}'; + const needle = key === 'title' && !matchingRoute.title + ? matchingRoute.label + : matchingRoute[key]; + + newBody = newBody.replace(new RegExp(interpolatedFrontmatter, 'g'), needle); + } + + // custom user frontmatter data + for (const fm in matchingRoute.data) { + const interpolatedFrontmatter = '\\$\\{globalThis.page.data.' + fm + '\\}'; + const needle = typeof matchingRoute.data[fm] === 'string' ? matchingRoute.data[fm] : JSON.stringify(matchingRoute.data[fm]).replace(/"/g, '"'); + + newBody = newBody.replace(new RegExp(interpolatedFrontmatter, 'g'), needle); + } + + // collections + for (const collection in this.compilation.collections) { + const interpolatedFrontmatter = '\\$\\{globalThis.collection.' + collection + '\\}'; + const cleanedCollections = cleanContentCollection(this.compilation.collections[collection]); + + newBody = newBody.replace(new RegExp(interpolatedFrontmatter, 'g'), JSON.stringify(cleanedCollections).replace(/"/g, '"')); + } + + return new Response(newBody); + } + + async shouldOptimize(url, response) { + const { activeContent } = this.compilation.config; + + return response.headers.get('Content-Type').indexOf(this.contentType[0]) >= 0 && activeContent; + } + + async optimize(url, response) { + let body = await response.text(); + + body = body.replace('', ` + + + `); + + return new Response(body); + } +} + +const greenwoodPluginContentAsData = { + type: 'resource', + name: 'plugin-active-content', + provider: (compilation) => new ContentAsDataResource(compilation) +}; + +export { greenwoodPluginContentAsData }; \ No newline at end of file diff --git a/packages/cli/src/plugins/resource/plugin-node-modules.js b/packages/cli/src/plugins/resource/plugin-node-modules.js index 286c2de08..76faabfef 100644 --- a/packages/cli/src/plugins/resource/plugin-node-modules.js +++ b/packages/cli/src/plugins/resource/plugin-node-modules.js @@ -10,7 +10,7 @@ import replace from '@rollup/plugin-replace'; import { getNodeModulesLocationForPackage, getPackageJson, getPackageNameFromUrl } from '../../lib/node-modules-utils.js'; import { resolveForRelativeUrl } from '../../lib/resource-utils.js'; import { ResourceInterface } from '../../lib/resource-interface.js'; -import { walkPackageJson } from '../../lib/walker-package-ranger.js'; +import { walkPackageJson, mergeImportMap } from '../../lib/walker-package-ranger.js'; let importMap; @@ -75,7 +75,6 @@ class NodeModulesResource extends ResourceInterface { async intercept(url, request, response) { const { context, config } = this.compilation; const { importMaps } = config.polyfills; - const importMapType = importMaps ? 'importmap-shim' : 'importmap'; const importMapShimScript = importMaps ? '' : ''; let body = await response.text(); const hasHead = body.match(/\(.*)<\/head>/s); @@ -97,15 +96,10 @@ class NodeModulesResource extends ResourceInterface { ? await walkPackageJson(userPackageJson) : importMap || {}; - // apply import map and shim for users + body = mergeImportMap(body, importMap, importMaps); body = body.replace('', ` ${importMapShimScript} - `); return new Response(body); diff --git a/packages/cli/src/plugins/resource/plugin-standard-html.js b/packages/cli/src/plugins/resource/plugin-standard-html.js index 06223cf3d..199379075 100644 --- a/packages/cli/src/plugins/resource/plugin-standard-html.js +++ b/packages/cli/src/plugins/resource/plugin-standard-html.js @@ -5,7 +5,6 @@ * This is a Greenwood default plugin. * */ -import frontmatter from 'front-matter'; import fs from 'fs/promises'; import rehypeStringify from 'rehype-stringify'; import rehypeRaw from 'rehype-raw'; @@ -37,18 +36,15 @@ class StandardHtmlResource extends ResourceInterface { async serve(url, request) { const { config, context } = this.compilation; - const { pagesDir, userWorkspace } = context; - const { interpolateFrontmatter } = config; + const { userWorkspace } = context; const { pathname } = url; const isSpaRoute = this.compilation.graph.find(node => node.isSPA); const matchingRoute = this.compilation.graph.find((node) => node.route === pathname) || {}; - const filePath = !matchingRoute.external ? matchingRoute.path : ''; - const isMarkdownContent = (matchingRoute?.filename || '').split('.').pop() === 'md'; - + const { pageHref } = matchingRoute; + const filePath = !matchingRoute.external && pageHref ? new URL(pageHref).pathname.replace(userWorkspace.pathname, './') : ''; + const isMarkdownContent = (filePath || '').split('.').pop() === 'md'; let body = ''; - let title = matchingRoute.title || null; let layout = matchingRoute.layout || null; - let frontMatter = matchingRoute.data || {}; let customImports = matchingRoute.imports || []; let ssrBody; let ssrLayout; @@ -59,7 +55,7 @@ class StandardHtmlResource extends ResourceInterface { } if (isMarkdownContent) { - const markdownContents = await fs.readFile(filePath, 'utf-8'); + const markdownContents = await fs.readFile(new URL(pageHref), 'utf-8'); const rehypePlugins = []; const remarkPlugins = []; @@ -74,7 +70,6 @@ class StandardHtmlResource extends ResourceInterface { } const settings = config.markdown.settings || {}; - const fm = frontmatter(markdownContents); processedMarkdown = await unified() .use(remarkParse, settings) // parse markdown into AST @@ -85,27 +80,10 @@ class StandardHtmlResource extends ResourceInterface { .use(rehypePlugins) // apply userland rehype plugins .use(rehypeStringify) // convert AST to HTML string .process(markdownContents); - - // configure via frontmatter - if (fm.attributes) { - frontMatter = fm.attributes; - - if (frontMatter.title) { - title = frontMatter.title; - } - - if (frontMatter.layout) { - layout = frontMatter.layout; - } - - if (frontMatter.imports) { - customImports = frontMatter.imports; - } - } } if (matchingRoute.isSSR) { - const routeModuleLocationUrl = new URL(`.${matchingRoute.relativeWorkspacePagePath}`, pagesDir); + const routeModuleLocationUrl = new URL(pageHref); const routeWorkerUrl = this.compilation.config.plugins.find(plugin => plugin.type === 'renderer').provider().executeModuleUrl; await new Promise(async (resolve, reject) => { @@ -139,12 +117,12 @@ class StandardHtmlResource extends ResourceInterface { } if (isSpaRoute) { - body = await fs.readFile(new URL(`./${isSpaRoute.filename}`, userWorkspace), 'utf-8'); + body = await fs.readFile(new URL(isSpaRoute.pageHref), 'utf-8'); } else { - body = ssrLayout ? ssrLayout : await getPageLayout(filePath, this.compilation, layout); + body = ssrLayout ? ssrLayout : await getPageLayout(pageHref, this.compilation, layout); } - body = await getAppLayout(body, this.compilation, customImports, title); + body = await getAppLayout(body, this.compilation, customImports, matchingRoute); body = await getUserScripts(body, this.compilation); if (processedMarkdown) { @@ -171,15 +149,7 @@ class StandardHtmlResource extends ResourceInterface { body = body.replace(/\(.*)<\/content-outlet>/s, `${ssrBody.replace(/\$/g, '$$$')}`); } - if (interpolateFrontmatter) { - for (const fm in frontMatter) { - const interpolatedFrontmatter = '\\$\\{globalThis.page.' + fm + '\\}'; - - body = body.replace(new RegExp(interpolatedFrontmatter, 'g'), frontMatter[fm]); - } - } - - // clean up placeholder content-outlet + // clean up any empty placeholder content-outlet if (body.indexOf('') > 0) { body = body.replace('', ''); } @@ -198,7 +168,7 @@ class StandardHtmlResource extends ResourceInterface { async optimize(url, response) { const { optimization, basePath } = this.compilation.config; const { pathname } = url; - const pageResources = this.compilation.graph.find(page => page.outputPath === pathname || page.route === pathname).resources; + const pageResources = this.compilation.graph.find(page => page.route === pathname).resources; let body = await response.text(); const root = htmlparser.parse(body, { diff --git a/packages/cli/src/plugins/resource/plugin-standard-json.js b/packages/cli/src/plugins/resource/plugin-standard-json.js index 0610eb7ca..834db864a 100644 --- a/packages/cli/src/plugins/resource/plugin-standard-json.js +++ b/packages/cli/src/plugins/resource/plugin-standard-json.js @@ -17,22 +17,14 @@ class StandardJsonResource extends ResourceInterface { async shouldServe(url) { const { protocol, pathname } = url; - const { basePath } = this.compilation.config; const isJson = pathname.split('.').pop() === this.extensions[0]; - const isGraphJson = pathname === `${basePath}/graph.json`; - const isWorkspaceFile = protocol === 'file:' && await checkResourceExists(url); + const isLocalFile = protocol === 'file:' && await checkResourceExists(url); - return isJson && (isWorkspaceFile || isGraphJson); + return isJson && isLocalFile; } async serve(url) { - const { pathname } = url; - const { scratchDir } = this.compilation.context; - const { basePath } = this.compilation.config; - const finalUrl = pathname === `${basePath}/graph.json` - ? new URL('./graph.json', scratchDir) - : url; - const contents = await fs.readFile(finalUrl, 'utf-8'); + const contents = await fs.readFile(url, 'utf-8'); return new Response(contents, { headers: new Headers({ diff --git a/packages/cli/src/plugins/resource/plugin-static-router.js b/packages/cli/src/plugins/resource/plugin-static-router.js index 4145ec595..4975c1811 100644 --- a/packages/cli/src/plugins/resource/plugin-static-router.js +++ b/packages/cli/src/plugins/resource/plugin-static-router.js @@ -69,7 +69,7 @@ class StaticRouterResource extends ResourceInterface { .filter(page => !page.isSSR) .filter(page => !page.route.endsWith('/404/')) .map((page) => { - const layout = page.filename && page.filename.split('.').pop() === this.extensions[0] + const layout = page.pageHref && page.pageHref.split('.').pop() === this.extensions[0] ? page.route : page.layout; const key = page.route === '/' diff --git a/packages/cli/test/cases/build.config.active-frontmatter/build.config.active-frontmatter.spec.js b/packages/cli/test/cases/build.config.active-frontmatter/build.config.active-frontmatter.spec.js new file mode 100644 index 000000000..b01574947 --- /dev/null +++ b/packages/cli/test/cases/build.config.active-frontmatter/build.config.active-frontmatter.spec.js @@ -0,0 +1,139 @@ +/* + * Use Case + * Run Greenwood with activeContent configuration enabled for validating active frontmatter. + * + * User Result + * Should generate a bare bones Greenwood build with correctly interpolated frontmatter variables in markdown and HTML. + * + * User Command + * greenwood build + * + * User Config + * { + * activeContent: true + * } + * + * User Workspace + * Greenwood default + * src/ + * pages/ + * blog/ + * first-post.md + * second-post.md + * layouts/ + * blog.html + */ +import { JSDOM } from 'jsdom'; +import path from 'path'; +import chai from 'chai'; +import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Build Greenwood With: ', function() { + const LABEL = 'Active Frontmatter'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public') + }; + runner = new Runner(); + }); + + describe(LABEL, function() { + + before(function() { + runner.setup(outputPath, getSetupFiles(outputPath)); + runner.runCommand(cliPath, 'build'); + }); + + describe('Default Greenwood frontmatter should be interpolated in the correct places for the home page', function() { + let dom; + + before(async function() { + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './index.html')); + }); + + it('should have the correct value for the tag in the <head> for the home page', function() { + const title = dom.window.document.querySelector('head title').textContent; + + expect(title).to.be.equal('Home'); + }); + + it('should have the correct graph value for id in a <span> tag', function() { + const id = dom.window.document.querySelector('body span.id').textContent; + + expect(id).to.be.equal('index'); + }); + + it('should have the correct graph value for route in a <span> tag', function() { + const route = dom.window.document.querySelector('body span.route').textContent; + + expect(route).to.be.equal('/'); + }); + + it('should have the correct graph value for label in a <span> tag', function() { + const label = dom.window.document.querySelector('body span.label').textContent; + + expect(label).to.be.equal('Home'); + }); + }); + + describe('Simple frontmatter should be interpolated in the correct places', function() { + let dom; + + before(async function() { + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './blog/first-post/index.html')); + }); + + it('should have the correct value for author <meta> tag in the <head>', function() { + const authorMeta = dom.window.document.querySelector('head meta[name=author]').getAttribute('content'); + + expect(authorMeta).to.be.equal('Owen Buckley'); + }); + + it('should have the correct value for published in the <h3> tag', function() { + const heading = dom.window.document.querySelector('body h3').textContent; + + expect(heading).to.be.equal('Published: 11/11/2022'); + }); + + it('should have the correct value for author in the <h4> tag', function() { + const heading = dom.window.document.querySelector('body h4').textContent; + + expect(heading).to.be.equal('Author: Owen Buckley'); + }); + }); + + describe('Rich frontmatter should be interpolated in the correct places', function() { + let dom; + + before(async function() { + dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './blog/second-post/index.html')); + }); + + it('should have the correct songs frontmatter data in the page output', function() { + const contents = dom.window.document.querySelector('body span').innerHTML; + const songs = JSON.parse(contents); + + expect(songs.length).to.equal(2); + + songs.forEach((song, idx) => { + const num = idx += 1; + + expect(song.title).to.equal(`Song ${num}`); + expect(song.url).to.equal(`song${num}.mp3`); + }); + }); + }); + }); + + after(function() { + runner.teardown(getOutputTeardownFiles(outputPath)); + }); +}); \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.active-frontmatter/greenwood.config.js b/packages/cli/test/cases/build.config.active-frontmatter/greenwood.config.js new file mode 100644 index 000000000..99fe04d09 --- /dev/null +++ b/packages/cli/test/cases/build.config.active-frontmatter/greenwood.config.js @@ -0,0 +1,3 @@ +export default { + activeContent: true +}; \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.interpolate-frontmatter/src/layouts/blog.html b/packages/cli/test/cases/build.config.active-frontmatter/src/layouts/blog.html similarity index 69% rename from packages/cli/test/cases/build.config.interpolate-frontmatter/src/layouts/blog.html rename to packages/cli/test/cases/build.config.active-frontmatter/src/layouts/blog.html index 58ce8c069..3377e2722 100644 --- a/packages/cli/test/cases/build.config.interpolate-frontmatter/src/layouts/blog.html +++ b/packages/cli/test/cases/build.config.active-frontmatter/src/layouts/blog.html @@ -2,7 +2,7 @@ <html lang="en" prefix="og:http://ogp.me/ns#"> <head> - <meta name="author" content="${globalThis.page.author}"> + <meta name="author" content="${globalThis.page.data.author}"> </head> <body> diff --git a/packages/cli/test/cases/build.config.interpolate-frontmatter/src/pages/blog/first-post.md b/packages/cli/test/cases/build.config.active-frontmatter/src/pages/blog/first-post.md similarity index 55% rename from packages/cli/test/cases/build.config.interpolate-frontmatter/src/pages/blog/first-post.md rename to packages/cli/test/cases/build.config.active-frontmatter/src/pages/blog/first-post.md index fa488585c..5f953abf6 100644 --- a/packages/cli/test/cases/build.config.interpolate-frontmatter/src/pages/blog/first-post.md +++ b/packages/cli/test/cases/build.config.active-frontmatter/src/pages/blog/first-post.md @@ -7,7 +7,7 @@ author: Owen Buckley # My First Post -### Published: ${globalThis.page.published} -#### Author: ${globalThis.page.author} +### Published: ${globalThis.page.data.published} +#### Author: ${globalThis.page.data.author} Lorum Ipsum. \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.active-frontmatter/src/pages/blog/second-post.md b/packages/cli/test/cases/build.config.active-frontmatter/src/pages/blog/second-post.md new file mode 100644 index 000000000..b80a11dea --- /dev/null +++ b/packages/cli/test/cases/build.config.active-frontmatter/src/pages/blog/second-post.md @@ -0,0 +1,17 @@ +--- +title: Ny First Post +layout: blog +published: 11/11/2022 +author: Owen Buckley +songs: + - title: Song 1 + url: song1.mp3 + - title: Song 2 + url: song2.mp3 +--- + +# My Second Post + +## Playlist + +<span>${globalThis.page.data.songs}</span> \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.active-frontmatter/src/pages/index.html b/packages/cli/test/cases/build.config.active-frontmatter/src/pages/index.html new file mode 100644 index 000000000..2eddebcd2 --- /dev/null +++ b/packages/cli/test/cases/build.config.active-frontmatter/src/pages/index.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html lang="en" prefix="og:http://ogp.me/ns#"> + + <head> + <title>${globalThis.page.title} + + + + ${globalThis.page.id} + ${globalThis.page.label} + ${globalThis.page.route} + + + \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.interpolate-frontmatter/build.config.interpolate-frontmatter.spec.js b/packages/cli/test/cases/build.config.interpolate-frontmatter/build.config.interpolate-frontmatter.spec.js deleted file mode 100644 index b39193067..000000000 --- a/packages/cli/test/cases/build.config.interpolate-frontmatter/build.config.interpolate-frontmatter.spec.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Use Case - * Run Greenwood with interpolateFrontmatter configuration enabled. - * - * User Result - * Should generate a bare bones Greenwood build with correctly interpolated frontmatter variables in markdown and HTML. - * - * User Command - * greenwood build - * - * User Config - * { - * interpolateFrontmatter: true - * } - * - * User Workspace - * Greenwood default - * src/ - * pages/ - * blog/ - * first-post.md - * layouts/ - * blog.html - */ -import { JSDOM } from 'jsdom'; -import path from 'path'; -import chai from 'chai'; -import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; -import { Runner } from 'gallinago'; -import { fileURLToPath, URL } from 'url'; - -const expect = chai.expect; - -describe('Build Greenwood With: ', function() { - const LABEL = 'Frontmatter Interpolation'; - const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); - const outputPath = fileURLToPath(new URL('.', import.meta.url)); - let runner; - - before(function() { - this.context = { - publicDir: path.join(outputPath, 'public') - }; - runner = new Runner(); - }); - - describe(LABEL, function() { - - before(function() { - runner.setup(outputPath, getSetupFiles(outputPath)); - runner.runCommand(cliPath, 'build'); - }); - - describe('Frontmatter should be interpolated in the correct places', function() { - let dom; - - before(async function() { - dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, './blog/first-post/index.html')); - }); - - it('should have the correct value for author tag in the ', function() { - const authorMeta = dom.window.document.querySelector('head meta[name=author]').getAttribute('content'); - - expect(authorMeta).to.be.equal('Owen Buckley'); - }); - - it('should have the correct value for published in the

tag', function() { - const heading = dom.window.document.querySelector('body h3').textContent; - - expect(heading).to.be.equal('Published: 11/11/2022'); - }); - - it('should have the correct value for author in the

tag', function() { - const heading = dom.window.document.querySelector('body h4').textContent; - - expect(heading).to.be.equal('Author: Owen Buckley'); - }); - }); - }); - - after(function() { - runner.teardown(getOutputTeardownFiles(outputPath)); - }); -}); \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.interpolate-frontmatter/greenwood.config.js b/packages/cli/test/cases/build.config.interpolate-frontmatter/greenwood.config.js deleted file mode 100644 index 41bc6bc88..000000000 --- a/packages/cli/test/cases/build.config.interpolate-frontmatter/greenwood.config.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - interpolateFrontmatter: true -}; \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.optimization-default/build.config-optimization-default.spec.js b/packages/cli/test/cases/build.config.optimization-default/build.config-optimization-default.spec.js index c88c8cc7e..8edcd7de7 100644 --- a/packages/cli/test/cases/build.config.optimization-default/build.config-optimization-default.spec.js +++ b/packages/cli/test/cases/build.config.optimization-default/build.config-optimization-default.spec.js @@ -83,7 +83,7 @@ describe('Build Greenwood With: ', function() { }); it('should have the expected + + + +

Blog Posts

+ + + \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.prerender-collections/src/pages/blog/second-post.md b/packages/cli/test/cases/build.config.prerender-collections/src/pages/blog/second-post.md new file mode 100644 index 000000000..ba4dc1e04 --- /dev/null +++ b/packages/cli/test/cases/build.config.prerender-collections/src/pages/blog/second-post.md @@ -0,0 +1,3 @@ +# Second Post + +Lorum Ipsum \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.prerender-collections/src/pages/index.html b/packages/cli/test/cases/build.config.prerender-collections/src/pages/index.html new file mode 100644 index 000000000..68a7cf2b4 --- /dev/null +++ b/packages/cli/test/cases/build.config.prerender-collections/src/pages/index.html @@ -0,0 +1,15 @@ +--- +collection: nav +order: 1 +--- + + + + + + + + ${globalThis.collection.nav} +

Home Page

+ + \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.prerender-collections/src/pages/toc.html b/packages/cli/test/cases/build.config.prerender-collections/src/pages/toc.html new file mode 100644 index 000000000..dea911cd1 --- /dev/null +++ b/packages/cli/test/cases/build.config.prerender-collections/src/pages/toc.html @@ -0,0 +1,15 @@ +--- +collection: nav +order: 3 +label: Table of Contents +--- + + + + + + +

Table of Contents Page

+ + + \ No newline at end of file diff --git a/packages/cli/test/cases/build.config.prerender/build.config.prerender.spec.js b/packages/cli/test/cases/build.config.prerender/build.config.prerender.spec.js index 881d19886..bd9626299 100644 --- a/packages/cli/test/cases/build.config.prerender/build.config.prerender.spec.js +++ b/packages/cli/test/cases/build.config.prerender/build.config.prerender.spec.js @@ -3,7 +3,7 @@ * Run Greenwood build command with prerender config set to true. * * User Result - * Should generate a Greenwood build with puppeteer generated output for Web Components. + * Should generate a Greenwood build with the expected generated output using custom elements. * * User Command * greenwood build diff --git a/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js b/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js index 762f5fe1d..a7f484293 100644 --- a/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js +++ b/packages/cli/test/cases/build.default.ssr-static-export/build.default.ssr-static-export.spec.js @@ -195,9 +195,9 @@ describe('Build Greenwood With: ', function() { expect(artistsPageGraphData).to.not.be.undefined; }); - it('should have the expected menu and index values in the graph', function() { - expect(artistsPageGraphData.data.menu).to.equal('navigation'); - expect(artistsPageGraphData.data.index).to.equal(7); + it('should have the expected collection and order values in the graph', function() { + expect(artistsPageGraphData.data.collection).to.equal('navigation'); + expect(artistsPageGraphData.data.order).to.equal(7); }); it('should have expected custom data values in its graph data', function() { diff --git a/packages/cli/test/cases/build.default.ssr-static-export/src/pages/artists.js b/packages/cli/test/cases/build.default.ssr-static-export/src/pages/artists.js index 8a80b59ad..5ef61a70b 100644 --- a/packages/cli/test/cases/build.default.ssr-static-export/src/pages/artists.js +++ b/packages/cli/test/cases/build.default.ssr-static-export/src/pages/artists.js @@ -70,15 +70,13 @@ async function getBody(compilation) { async function getFrontmatter() { return { - menu: 'navigation', - index: 7, + collection: 'navigation', + order: 7, imports: [ '/components/counter.js' ], - data: { - author: 'Project Evergreen', - date: '01-01-2021' - } + author: 'Project Evergreen', + date: '01-01-2021' }; } diff --git a/packages/cli/test/cases/build.default.workspace-404/build.default.workspace-404.spec.js b/packages/cli/test/cases/build.default.workspace-404/build.default.workspace-404.spec.js index 0aa500278..e515bcbbb 100644 --- a/packages/cli/test/cases/build.default.workspace-404/build.default.workspace-404.spec.js +++ b/packages/cli/test/cases/build.default.workspace-404/build.default.workspace-404.spec.js @@ -71,7 +71,7 @@ describe('Build Greenwood With: ', function() { }); before(function() { - scriptTags = Array.from(dom.window.document.querySelectorAll('head script')).filter(tag => !tag.getAttribute('data-gwd')); + scriptTags = Array.from(dom.window.document.querySelectorAll('head script[type="module"]')).filter(tag => !tag.getAttribute('data-gwd')); linkTags = dom.window.document.querySelectorAll('head > link[rel="stylesheet"'); }); diff --git a/packages/cli/test/cases/build.default.workspace-layouts-page-and-app/build.default.workspace-layouts-page-and-app.spec.js b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app/build.default.workspace-layouts-page-and-app.spec.js index 1f57e0481..3e98f8b06 100644 --- a/packages/cli/test/cases/build.default.workspace-layouts-page-and-app/build.default.workspace-layouts-page-and-app.spec.js +++ b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app/build.default.workspace-layouts-page-and-app.spec.js @@ -77,7 +77,7 @@ describe('Build Greenwood With: ', function() { let styleTags; before(function() { - scriptTags = Array.from(dom.window.document.querySelectorAll('head script')).filter(tag => !tag.getAttribute('data-gwd')); + scriptTags = Array.from(dom.window.document.querySelectorAll('head script[type="module"]')).filter(tag => !tag.getAttribute('data-gwd')); linkTags = dom.window.document.querySelectorAll('head > link[rel="stylesheet"'); styleTags = dom.window.document.querySelectorAll('head > style'); }); diff --git a/packages/cli/test/cases/build.default.workspace-layouts-page-bare-merging/build.default.workspace-layouts-page-bare-merging.spec.js b/packages/cli/test/cases/build.default.workspace-layouts-page-bare-merging/build.default.workspace-layouts-page-bare-merging.spec.js index efc58645f..79795a65f 100644 --- a/packages/cli/test/cases/build.default.workspace-layouts-page-bare-merging/build.default.workspace-layouts-page-bare-merging.spec.js +++ b/packages/cli/test/cases/build.default.workspace-layouts-page-bare-merging/build.default.workspace-layouts-page-bare-merging.spec.js @@ -29,7 +29,7 @@ import { fileURLToPath, URL } from 'url'; const expect = chai.expect; describe('Build Greenwood With: ', function() { - const LABEL = 'Default Greenwood Configuration and Workspace for Quick Start'; + const LABEL = 'Default Greenwood Configuration w/ Bare Page Merging'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); const outputPath = fileURLToPath(new URL('.', import.meta.url)); let runner; diff --git a/packages/cli/test/cases/build.default.workspace-layouts-page/build.default.workspace-layouts-page.spec.js b/packages/cli/test/cases/build.default.workspace-layouts-page/build.default.workspace-layouts-page.spec.js index 6cce97f24..7b658a80d 100644 --- a/packages/cli/test/cases/build.default.workspace-layouts-page/build.default.workspace-layouts-page.spec.js +++ b/packages/cli/test/cases/build.default.workspace-layouts-page/build.default.workspace-layouts-page.spec.js @@ -65,7 +65,7 @@ describe('Build Greenwood With: ', function() { let styleTags; before(function() { - scriptTags = Array.from(dom.window.document.querySelectorAll('head script')).filter(tag => !tag.getAttribute('data-gwd')); + scriptTags = Array.from(dom.window.document.querySelectorAll('head script[type="module"]')).filter(tag => !tag.getAttribute('data-gwd')); linkTags = dom.window.document.querySelectorAll('head > link[rel="stylesheet"]'); styleTags = dom.window.document.querySelectorAll('head > style'); }); diff --git a/packages/cli/test/cases/build.default.workspace-nested/build.default.workspace-nested.spec.js b/packages/cli/test/cases/build.default.workspace-nested/build.default.workspace-nested.spec.js index f14766089..d3b872f05 100644 --- a/packages/cli/test/cases/build.default.workspace-nested/build.default.workspace-nested.spec.js +++ b/packages/cli/test/cases/build.default.workspace-nested/build.default.workspace-nested.spec.js @@ -51,6 +51,10 @@ import { fileURLToPath, URL } from 'url'; const expect = chai.expect; +function generatePageHref(pagePath) { + return new URL(`./src/pages/${pagePath}`, import.meta.url).href; +} + describe('Build Greenwood With: ', function() { const LABEL = 'Default Greenwood Configuration and Default Workspace w/ Nested Directories'; const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); @@ -76,38 +80,38 @@ describe('Build Greenwood With: ', function() { let graph; before(async function() { - graph = JSON.parse(await fs.promises.readFile(path.join(this.context.publicDir, 'graph.json'), 'utf-8')) - .map(item => { - return { - ...item, - path: item.path.replace(/\\/g, '/') - }; - }); + graph = JSON.parse(await fs.promises.readFile(path.join(this.context.publicDir, 'graph.json'), 'utf-8')); }); it('should have the expected ordering of pages in graph.json', function() { expect(graph.length).to.equal(21); - expect(graph[0].path).to.be.equal('src/pages/blog/2017/03/26/index.md'); - expect(graph[1].path).to.be.equal('src/pages/blog/2017/03/30/index.md'); - expect(graph[2].path).to.be.equal('src/pages/blog/2017/04/10/index.md'); - expect(graph[3].path).to.be.equal('src/pages/blog/2017/04/22/index.md'); - expect(graph[4].path).to.be.equal('src/pages/blog/2017/05/05/index.md'); - expect(graph[5].path).to.be.equal('src/pages/blog/2017/06/07/index.md'); - expect(graph[6].path).to.be.equal('src/pages/blog/2017/09/10/index.md'); - expect(graph[7].path).to.be.equal('src/pages/blog/2017/10/15/index.md'); - expect(graph[8].path).to.be.equal('src/pages/blog/2018/01/24/index.md'); - expect(graph[9].path).to.be.equal('src/pages/blog/2018/05/16/index.md'); - expect(graph[10].path).to.be.equal('src/pages/blog/2018/06/06/index.md'); - expect(graph[11].path).to.be.equal('src/pages/blog/2018/09/26/index.md'); - expect(graph[12].path).to.be.equal('src/pages/blog/2018/10/28/index.md'); - expect(graph[13].path).to.be.equal('src/pages/blog/2018/11/19/index.md'); - expect(graph[14].path).to.be.equal('src/pages/blog/2019/11/11/index.md'); - expect(graph[15].path).to.be.equal('src/pages/blog/2020/04/07/index.md'); - expect(graph[16].path).to.be.equal('src/pages/blog/2020/08/15/index.md'); - expect(graph[17].path).to.be.equal('src/pages/blog/2020/10/28/index.md'); - expect(graph[18].path).to.be.equal('src/pages/blog/index.md'); - expect(graph[19].path).to.be.equal('src/pages/index.html'); - expect(graph[20].path).to.be.equal('404.html'); + + // expect(graph[0].pageHref.endsWith('/blog/2017/03/26/index.md')).to.be.equal(true); + expect(graph[0].pageHref).to.equal(generatePageHref('blog/2017/03/26/index.md')); + expect(graph[0].id).to.be.equal('blog-2017-03-26-index'); + expect(graph[1].pageHref).to.equal(generatePageHref('blog/2017/03/30/index.md')); + expect(graph[2].pageHref).to.equal(generatePageHref('blog/2017/04/10/index.md')); + expect(graph[3].pageHref).to.equal(generatePageHref('blog/2017/04/22/index.md')); + expect(graph[4].pageHref).to.equal(generatePageHref('blog/2017/05/05/index.md')); + expect(graph[5].pageHref).to.equal(generatePageHref('blog/2017/06/07/index.md')); + expect(graph[6].pageHref).to.equal(generatePageHref('blog/2017/09/10/index.md')); + expect(graph[7].pageHref).to.equal(generatePageHref('blog/2017/10/15/index.md')); + expect(graph[8].pageHref).to.equal(generatePageHref('blog/2018/01/24/index.md')); + expect(graph[9].pageHref).to.equal(generatePageHref('blog/2018/05/16/index.md')); + expect(graph[10].pageHref).to.equal(generatePageHref('blog/2018/06/06/index.md')); + expect(graph[11].pageHref).to.equal(generatePageHref('blog/2018/09/26/index.md')); + expect(graph[12].pageHref).to.equal(generatePageHref('blog/2018/10/28/index.md')); + expect(graph[13].pageHref).to.equal(generatePageHref('blog/2018/11/19/index.md')); + expect(graph[14].pageHref).to.equal(generatePageHref('blog/2019/11/11/index.md')); + expect(graph[15].pageHref).to.equal(generatePageHref('blog/2020/04/07/index.md')); + expect(graph[16].pageHref).to.equal(generatePageHref('blog/2020/08/15/index.md')); + expect(graph[17].pageHref).to.equal(generatePageHref('blog/2020/10/28/index.md')); + expect(graph[18].pageHref).to.equal(generatePageHref('blog/index.md')); + expect(graph[18].id).to.be.equal('blog-index'); + expect(graph[19].pageHref).to.equal(generatePageHref('index.html')); + expect(graph[19].id).to.be.equal('index'); + expect(graph[20].pageHref).to.equal(generatePageHref('404.html')); + expect(graph[20].id).to.be.equal('404'); }); it('should create a top level blog pages directory', function() { @@ -125,8 +129,8 @@ describe('Build Greenwood With: ', function() { graph.filter((page) => { return page.route.indexOf('2017') > 0; }).forEach((page) => { - const outputpath = path.join(this.context.publicDir, page.route, 'index.html'); - expect(fs.existsSync(outputpath)).to.be.true; + const outputPath = path.join(this.context.publicDir, page.route, 'index.html'); + expect(fs.existsSync(outputPath)).to.be.true; }); }); @@ -134,8 +138,8 @@ describe('Build Greenwood With: ', function() { graph.filter((page) => { return page.route.indexOf('2018') > 0; }).forEach((page) => { - const outputpath = path.join(this.context.publicDir, page.route, 'index.html'); - expect(fs.existsSync(outputpath)).to.be.true; + const outputPath = path.join(this.context.publicDir, page.route, 'index.html'); + expect(fs.existsSync(outputPath)).to.be.true; }); }); @@ -143,8 +147,8 @@ describe('Build Greenwood With: ', function() { graph.filter((page) => { return page.route.indexOf('2019') > 0; }).forEach((page) => { - const outputpath = path.join(this.context.publicDir, page.route, 'index.html'); - expect(fs.existsSync(outputpath)).to.be.true; + const outputPath = path.join(this.context.publicDir, page.route, 'index.html'); + expect(fs.existsSync(outputPath)).to.be.true; }); }); @@ -152,8 +156,8 @@ describe('Build Greenwood With: ', function() { graph.filter((page) => { return page.route.indexOf('2020') > 0; }).forEach((page) => { - const outputpath = path.join(this.context.publicDir, page.route, 'index.html'); - expect(fs.existsSync(outputpath)).to.be.true; + const outputPath = path.join(this.context.publicDir, page.route, 'index.html'); + expect(fs.existsSync(outputPath)).to.be.true; }); }); diff --git a/packages/cli/test/cases/build.default.workspace-top-level-pages/src/pages/something.owen b/packages/cli/test/cases/build.default.workspace-top-level-pages/src/pages/something.owen new file mode 100644 index 000000000..31382bef9 --- /dev/null +++ b/packages/cli/test/cases/build.default.workspace-top-level-pages/src/pages/something.owen @@ -0,0 +1 @@ +foo bar baz \ No newline at end of file diff --git a/packages/cli/test/cases/build.default.workspace-user-directory-mapping/build.default.workspace-user-directory-mapping.spec.js b/packages/cli/test/cases/build.default.workspace-user-directory-mapping/build.default.workspace-user-directory-mapping.spec.js index a5a71dce6..40c0c9744 100644 --- a/packages/cli/test/cases/build.default.workspace-user-directory-mapping/build.default.workspace-user-directory-mapping.spec.js +++ b/packages/cli/test/cases/build.default.workspace-user-directory-mapping/build.default.workspace-user-directory-mapping.spec.js @@ -83,7 +83,7 @@ describe('Build Greenwood With: ', function() { }); it('should have one + + + +

Blog Posts

+ + + \ No newline at end of file diff --git a/packages/cli/test/cases/develop.config.active-content/src/pages/blog/second-post.md b/packages/cli/test/cases/develop.config.active-content/src/pages/blog/second-post.md new file mode 100644 index 000000000..ba4dc1e04 --- /dev/null +++ b/packages/cli/test/cases/develop.config.active-content/src/pages/blog/second-post.md @@ -0,0 +1,3 @@ +# Second Post + +Lorum Ipsum \ No newline at end of file diff --git a/packages/cli/test/cases/develop.config.active-content/src/pages/contact.html b/packages/cli/test/cases/develop.config.active-content/src/pages/contact.html new file mode 100644 index 000000000..fa7e87527 --- /dev/null +++ b/packages/cli/test/cases/develop.config.active-content/src/pages/contact.html @@ -0,0 +1,10 @@ +--- +collection: nav +order: 4 +label: Contact +--- + + +

Contact Page

+ + \ No newline at end of file diff --git a/packages/cli/test/cases/develop.config.active-content/src/pages/index.html b/packages/cli/test/cases/develop.config.active-content/src/pages/index.html new file mode 100644 index 000000000..68a7cf2b4 --- /dev/null +++ b/packages/cli/test/cases/develop.config.active-content/src/pages/index.html @@ -0,0 +1,15 @@ +--- +collection: nav +order: 1 +--- + + + + + + + + ${globalThis.collection.nav} +

Home Page

+ + \ No newline at end of file diff --git a/packages/cli/test/cases/develop.config.active-content/src/pages/toc.html b/packages/cli/test/cases/develop.config.active-content/src/pages/toc.html new file mode 100644 index 000000000..dea911cd1 --- /dev/null +++ b/packages/cli/test/cases/develop.config.active-content/src/pages/toc.html @@ -0,0 +1,15 @@ +--- +collection: nav +order: 3 +label: Table of Contents +--- + + + + + + +

Table of Contents Page

+ + + \ No newline at end of file diff --git a/packages/cli/test/cases/develop.config.base-path/develop.config.base-path.spec.js b/packages/cli/test/cases/develop.config.base-path/develop.config.base-path.spec.js index e939b86d2..cc88c9b73 100644 --- a/packages/cli/test/cases/develop.config.base-path/develop.config.base-path.spec.js +++ b/packages/cli/test/cases/develop.config.base-path/develop.config.base-path.spec.js @@ -287,31 +287,6 @@ describe('Develop Greenwood With: ', function() { expect(cards.length).to.be.greaterThan(0); }); }); - - describe('Fetching graph.json client side', function() { - let response; - let graph; - - before(async function() { - response = await fetch(`${hostname}:${port}${basePath}/graph.json`); - graph = await response.clone().json(); - }); - - it('should return the correct content type', function(done) { - expect(response.headers.get('content-type')).to.contain('application/json'); - done(); - }); - - it('should return a 200', function(done) { - expect(response.status).to.equal(200); - done(); - }); - - it('should have the expected length for all content', function(done) { - expect(graph.length).to.equal(3); - done(); - }); - }); }); after(function() { diff --git a/packages/cli/test/cases/develop.config.polyfills-import-maps/develop.config.polyfills-import-maps.spec.js b/packages/cli/test/cases/develop.config.polyfills-import-maps/develop.config.polyfills-import-maps.spec.js index 4a7bda62b..6af664a28 100644 --- a/packages/cli/test/cases/develop.config.polyfills-import-maps/develop.config.polyfills-import-maps.spec.js +++ b/packages/cli/test/cases/develop.config.polyfills-import-maps/develop.config.polyfills-import-maps.spec.js @@ -26,7 +26,7 @@ import chai from 'chai'; import { JSDOM } from 'jsdom'; import path from 'path'; -import { getSetupFiles } from '../../../../../test/utils.js'; +import { getSetupFiles, getDependencyFiles } from '../../../../../test/utils.js'; import { Runner } from 'gallinago'; import { fileURLToPath, URL } from 'url'; @@ -50,9 +50,99 @@ describe('Develop Greenwood With: ', function() { describe(LABEL, function() { before(async function() { + const lit = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/*.js`, + `${outputPath}/node_modules/lit/` + ); + const litDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/decorators/*.js`, + `${outputPath}/node_modules/lit/decorators/` + ); + const litDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/directives/*.js`, + `${outputPath}/node_modules/lit/directives/` + ); + const litPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/package.json`, + `${outputPath}/node_modules/lit/` + ); + const litSsrPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/package.json`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); + const litElement = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/*.js`, + `${outputPath}/node_modules/lit-element/` + ); + const litElementPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/package.json`, + `${outputPath}/node_modules/lit-element/` + ); + const litElementDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/decorators/*.js`, + `${outputPath}/node_modules/lit-element/decorators/` + ); + const litHtml = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/*.js`, + `${outputPath}/node_modules/lit-html/` + ); + const litHtmlPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/package.json`, + `${outputPath}/node_modules/lit-html/` + ); + const litHtmlDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/directives/*.js`, + `${outputPath}/node_modules/lit-html/directives/` + ); + const litReactiveElement = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/` + ); + const litReactiveElementDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/decorators/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/decorators/` + ); + const litReactiveElementPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/package.json`, + `${outputPath}/node_modules/@lit/reactive-element/` + ); + const litHtmlSourceMap = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/lit-html.js.map`, + `${outputPath}/node_modules/lit-html/` + ); + const trustedTypesPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@types/trusted-types/package.json`, + `${outputPath}/node_modules/@types/trusted-types/` + ); + const tslibPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/tslib/package.json`, + `${outputPath}/node_modules/tslib/` + ); + const tslibLibs = await getDependencyFiles( + `${process.cwd()}/node_modules/tslib/*.js`, + `${outputPath}/node_modules/tslib/` + ); runner.setup(outputPath, [ - ...getSetupFiles(outputPath) + ...getSetupFiles(outputPath), + ...lit, + ...litPackageJson, + ...litSsrPackageJson, + ...litDirectives, + ...litDecorators, + ...litElementPackageJson, + ...litElement, + ...litElementDecorators, + ...litHtmlPackageJson, + ...litHtml, + ...litHtmlDirectives, + ...litReactiveElement, + ...litReactiveElementDecorators, + ...litReactiveElementPackageJson, + ...litHtmlSourceMap, + ...tslibPackageJson, + ...tslibLibs, + ...trustedTypesPackageJson ]); return new Promise((resolve) => { diff --git a/packages/cli/test/cases/develop.config.polyfills-import-maps/package.json b/packages/cli/test/cases/develop.config.polyfills-import-maps/package.json index 6e4d02164..533206392 100644 --- a/packages/cli/test/cases/develop.config.polyfills-import-maps/package.json +++ b/packages/cli/test/cases/develop.config.polyfills-import-maps/package.json @@ -1,4 +1,7 @@ { "name": "test-develop-command-config-import-maps", - "type": "module" + "type": "module", + "dependencies": { + "lit": "^3.0.0" + } } \ No newline at end of file diff --git a/packages/cli/test/cases/develop.default/develop.default.spec.js b/packages/cli/test/cases/develop.default/develop.default.spec.js index 3d236e7ea..3965a562c 100644 --- a/packages/cli/test/cases/develop.default/develop.default.spec.js +++ b/packages/cli/test/cases/develop.default/develop.default.spec.js @@ -56,7 +56,6 @@ import { fileURLToPath, URL } from 'url'; const expect = chai.expect; -// TODO good first issue, to abstract along with copy plugin async function rreaddir (dir, allFiles = []) { const files = (await fs.promises.readdir(dir)).map(f => path.join(dir, f)); @@ -207,7 +206,7 @@ describe('Develop Greenwood With: ', function() { `${process.cwd()}/node_modules/@lion/localize/src/*.js`, `${outputPath}/node_modules/@lion/localize/src/` ); - const owcDepupLibPackageJson = await getDependencyFiles( + const owcDedupeLibPackageJson = await getDependencyFiles( `${process.cwd()}/node_modules/@open-wc/dedupe-mixin/package.json`, `${outputPath}/node_modules/@open-wc/dedupe-mixin/` ); @@ -413,7 +412,7 @@ describe('Develop Greenwood With: ', function() { ...lionLocalizeLibsPackageJson, ...lionLocalizeTesterLibs, ...lionLocalizeSrcLibs, - ...owcDepupLibPackageJson, + ...owcDedupeLibPackageJson, ...owcScopedLibPackageJson, ...messageFormatLibs, ...messageFormatLibsPackageJson, @@ -488,9 +487,12 @@ describe('Develop Greenwood With: ', function() { }); it('should return an import map shim - - - - - - - \ No newline at end of file diff --git a/packages/plugin-graphql/test/cases/query-menu/package.json b/packages/plugin-graphql/test/cases/query-custom-schema/package.json similarity index 58% rename from packages/plugin-graphql/test/cases/query-menu/package.json rename to packages/plugin-graphql/test/cases/query-custom-schema/package.json index 3d07f5b69..a48c936f7 100644 --- a/packages/plugin-graphql/test/cases/query-menu/package.json +++ b/packages/plugin-graphql/test/cases/query-custom-schema/package.json @@ -1,5 +1,5 @@ { - "name": "plugin-graphql-test-menu-query", + "name": "plugin-graphql-test-custom-schema", "type": "module", "dependencies": { "lit": "^3.1.0" diff --git a/packages/plugin-graphql/test/cases/query-custom-schema/query-custom-schema.spec.js b/packages/plugin-graphql/test/cases/query-custom-schema/query-custom-schema.spec.js index a93cedd18..229402c51 100644 --- a/packages/plugin-graphql/test/cases/query-custom-schema/query-custom-schema.spec.js +++ b/packages/plugin-graphql/test/cases/query-custom-schema/query-custom-schema.spec.js @@ -54,10 +54,97 @@ describe('Build Greenwood With: ', function() { `${process.cwd()}/packages/plugin-graphql/src/core/*.js`, `${outputPath}/node_modules/@greenwood/plugin-graphql/src/core/` ); + const greenwoodGraphqlQueryLibs = await getDependencyFiles( + `${process.cwd()}/packages/plugin-graphql/src/queries/*.gql`, + `${outputPath}/node_modules/@greenwood/plugin-graphql/src/queries/` + ); + const greenwoodLibs = await getDependencyFiles( + `${process.cwd()}/packages/cli/src/lib/*.js`, + `${outputPath}/node_modules/cli/src/lib/` + ); + const lit = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/*.js`, + `${outputPath}/node_modules/lit/` + ); + const litDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/decorators/*.js`, + `${outputPath}/node_modules/lit/decorators/` + ); + const litDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/directives/*.js`, + `${outputPath}/node_modules/lit/directives/` + ); + const litPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/package.json`, + `${outputPath}/node_modules/lit/` + ); + const litSsrPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/package.json`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); + const litElement = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/*.js`, + `${outputPath}/node_modules/lit-element/` + ); + const litElementPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/package.json`, + `${outputPath}/node_modules/lit-element/` + ); + const litElementDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/decorators/*.js`, + `${outputPath}/node_modules/lit-element/decorators/` + ); + const litHtml = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/*.js`, + `${outputPath}/node_modules/lit-html/` + ); + const litHtmlPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/package.json`, + `${outputPath}/node_modules/lit-html/` + ); + const litHtmlDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/directives/*.js`, + `${outputPath}/node_modules/lit-html/directives/` + ); + // lit-html has a dependency on this + // https://github.com/lit/lit/blob/main/packages/lit-html/package.json#L82 + const trustedTypes = await getDependencyFiles( + `${process.cwd()}/node_modules/@types/trusted-types/package.json`, + `${outputPath}/node_modules/@types/trusted-types/` + ); + const litReactiveElement = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/` + ); + const litReactiveElementDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/decorators/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/decorators/` + ); + const litReactiveElementPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/package.json`, + `${outputPath}/node_modules/@lit/reactive-element/` + ); runner.setup(outputPath, [ ...getSetupFiles(outputPath), - ...greenwoodGraphqlCoreLibs + ...greenwoodGraphqlCoreLibs, + ...greenwoodGraphqlQueryLibs, + ...lit, + ...litPackageJson, + ...litSsrPackageJson, + ...litDirectives, + ...litDecorators, + ...litElementPackageJson, + ...litElement, + ...litElementDecorators, + ...litHtmlPackageJson, + ...litHtml, + ...litHtmlDirectives, + ...trustedTypes, + ...litReactiveElement, + ...litReactiveElementDecorators, + ...litReactiveElementPackageJson, + ...greenwoodLibs ]); runner.runCommand(cliPath, 'build'); }); @@ -96,37 +183,26 @@ describe('Build Greenwood With: ', function() { }); }); - describe(' tag output from query', function() { + describe('content output from the component', function() { const title = 'Home Page Logos'; - let images; - before(function() { - images = dom.window.document.querySelectorAll('body img'); - }); + it('should have a expected image tag content', function() { + const images = dom.window.document.querySelectorAll('photo-gallery img'); - it('should have three tags in the ', function() { expect(images.length).to.be.equal(3); - }); - it('should have a expected src attribute value for all three tags', function() { images.forEach((image, i) => { const count = i += 1; expect(image.src).to.contain(`/assets/logo${count}.png`); - }); - }); - - it('should have a expected title attribute value for all three tags', function() { - images.forEach((image, i) => { - const count = i += 1; expect(image.title).to.contain(`${title} - Logo #${count}`); }); }); it('should have a expected title content in the

tag', function() { - const h2 = dom.window.document.querySelectorAll('body h2'); + const heading = dom.window.document.querySelectorAll('photo-gallery h2'); - expect(h2.length).to.be.equal(1); - expect(h2[0].textContent).to.be.equal(title); + expect(heading.length).to.be.equal(1); + expect(heading[0].textContent).to.be.equal(title); }); }); }); diff --git a/packages/plugin-graphql/test/cases/query-custom-schema/src/pages/index.html b/packages/plugin-graphql/test/cases/query-custom-schema/src/pages/index.html index 3d0ce48d9..d064c7120 100644 --- a/packages/plugin-graphql/test/cases/query-custom-schema/src/pages/index.html +++ b/packages/plugin-graphql/test/cases/query-custom-schema/src/pages/index.html @@ -3,33 +3,65 @@ -

- - - + \ No newline at end of file diff --git a/packages/plugin-graphql/test/cases/query-graph/query-graph.spec.js b/packages/plugin-graphql/test/cases/query-graph/query-graph.spec.js index 11209a801..386e390ff 100644 --- a/packages/plugin-graphql/test/cases/query-graph/query-graph.spec.js +++ b/packages/plugin-graphql/test/cases/query-graph/query-graph.spec.js @@ -185,14 +185,14 @@ describe('Build Greenwood With: ', function() { expect(lists.length).to.be.equal(1); }); - it('should have a expected navigation output in the
based on pages with menu: navigation frontmatter', function() { + it('should have a expected navigation output in the
based on pages in the graph', function() { const listItems = dom.window.document.querySelectorAll('body ul li'); expect(listItems.length).to.be.equal(4); expect(listItems[0].innerHTML).to.contain('First Post'); expect(listItems[1].innerHTML).to.contain('Second Post'); - expect(listItems[2].innerHTML).to.contain('Index'); + expect(listItems[2].innerHTML).to.contain('Home'); expect(listItems[3].innerHTML).to.contain('Not Found'); }); }); diff --git a/packages/plugin-graphql/test/cases/query-graph/src/pages/index.html b/packages/plugin-graphql/test/cases/query-graph/src/pages/index.html index d1523ca91..be419b661 100644 --- a/packages/plugin-graphql/test/cases/query-graph/src/pages/index.html +++ b/packages/plugin-graphql/test/cases/query-graph/src/pages/index.html @@ -2,7 +2,7 @@ - + diff --git a/packages/plugin-graphql/test/cases/query-menu/greenwood.config.js b/packages/plugin-graphql/test/cases/query-menu/greenwood.config.js deleted file mode 100644 index c38b0c8dd..000000000 --- a/packages/plugin-graphql/test/cases/query-menu/greenwood.config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { greenwoodPluginGraphQL } from '../../../src/index.js'; -import { greenwoodPluginRendererPuppeteer } from '@greenwood/plugin-renderer-puppeteer'; - -export default { - - plugins: [ - ...greenwoodPluginGraphQL(), - ...greenwoodPluginRendererPuppeteer() // automatically invokes prerendering - ] - -}; \ No newline at end of file diff --git a/packages/plugin-graphql/test/unit/common.spec.js b/packages/plugin-graphql/test/unit/common.spec.js index b50a96b44..94a3c2b99 100644 --- a/packages/plugin-graphql/test/unit/common.spec.js +++ b/packages/plugin-graphql/test/unit/common.spec.js @@ -14,11 +14,9 @@ describe('Unit Test: Data', function() { const query = ` query { graph { - id, title, route, path, - filename, layout, __typename } @@ -26,7 +24,7 @@ describe('Unit Test: Data', function() { `; const hash = getQueryHash(query); - expect(hash).to.be.equal('1291879437'); + expect(hash).to.be.equal('309961297'); }); it('should return the expected hash for a custom graph query with custom data', function () { @@ -51,11 +49,9 @@ describe('Unit Test: Data', function() { const query = ` query($parent: String!) { children(parent: $parent) { - id, title, route, path, - filename, layout } } @@ -64,7 +60,7 @@ describe('Unit Test: Data', function() { parent: '/docs/' }); - expect(hash).to.be.equal('2106154137'); + expect(hash).to.be.equal('1893453381'); }); }); diff --git a/packages/plugin-graphql/test/unit/mocks/config.js b/packages/plugin-graphql/test/unit/mocks/config.js deleted file mode 100644 index b2a8a5ba8..000000000 --- a/packages/plugin-graphql/test/unit/mocks/config.js +++ /dev/null @@ -1,12 +0,0 @@ -const MOCK_CONFIG = { - config: { - devServer: { - port: 1984 - }, - workspace: 'src' - } -}; - -export { - MOCK_CONFIG -}; \ No newline at end of file diff --git a/packages/plugin-graphql/test/unit/mocks/graph.js b/packages/plugin-graphql/test/unit/mocks/graph.js index 9d15b7afb..75ae1e1d6 100644 --- a/packages/plugin-graphql/test/unit/mocks/graph.js +++ b/packages/plugin-graphql/test/unit/mocks/graph.js @@ -3,18 +3,18 @@ const MOCK_GRAPH = { graph: [ { data: { - menu: "", - index: "", - linkheadings: 0, + collection: "", + order: "", + tocHeading: 0, tableOfContents: [], }, - filename: "./index.md", - id: "index", - label: "Index", + workspacePath: "./order.md", + id: "order", + label: "order", route: "/", layout: "home", path: - "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/index.md", + "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/order.md", title: "", meta: [ { @@ -64,12 +64,12 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 3, - linkheadings: 0, + collection: "side", + order: 3, + tocHeading: 0, tableOfContents: [], }, - filename: "./about/community.md", + workspacePath: "./about/community.md", id: "community", label: "Community", route: "/about/community", @@ -125,12 +125,12 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 2, - linkheadings: 0, + collection: "side", + order: 2, + tocHeading: 0, tableOfContents: [], }, - filename: "./about/features.md", + workspacePath: "./about/features.md", id: "features", label: "Features", route: "/about/features", @@ -186,12 +186,12 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: "", - linkheadings: 0, + collection: "side", + order: "", + tocHeading: 0, tableOfContents: [], }, - filename: "./about/goals.md", + workspacePath: "./about/goals.md", id: "Goals", label: "Goals", route: "/about/goals", @@ -247,9 +247,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 1, - linkheadings: 3, + collection: "side", + order: 1, + tocHeading: 3, tableOfContents: [ { content: "CLI", @@ -281,7 +281,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./about/how-it-works.md", + workspacePath: "./about/how-it-works.md", id: "how-it-works", label: "How It Works", route: "/about/how-it-works", @@ -337,18 +337,18 @@ const MOCK_GRAPH = { }, { data: { - menu: "navigation", - index: "", - linkheadings: 0, + collection: "navigation", + order: "", + tocHeading: 0, tableOfContents: [], }, - filename: "./about/index.md", + workspacePath: "./about/order.md", id: "about", label: "About", route: "/about/", layout: "page", path: - "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/about/index.md", + "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/about/order.md", title: "About", meta: [ { @@ -398,12 +398,12 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 1, - linkheadings: 0, + collection: "side", + order: 1, + tocHeading: 0, tableOfContents: [], }, - filename: "./docs/component-model.md", + workspacePath: "./docs/component-model.md", id: "component-model", label: "Component Model", route: "/docs/component-model", @@ -459,9 +459,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 2, - linkheadings: 3, + collection: "side", + order: 2, + tocHeading: 3, tableOfContents: [ { content: "Dev Server", @@ -535,7 +535,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./docs/configuration.md", + workspacePath: "./docs/configuration.md", id: "configuration", label: "Configuration", route: "/docs/configuration", @@ -591,9 +591,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 5, - linkheadings: 3, + collection: "side", + order: 5, + tocHeading: 3, tableOfContents: [ { content: "Theming", @@ -639,7 +639,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./docs/css-and-images.md", + workspacePath: "./docs/css-and-images.md", id: "css-and-images", label: "CSS and Images", route: "/docs/css-and-images", @@ -695,9 +695,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 7, - linkheadings: 3, + collection: "side", + order: 7, + tocHeading: 3, tableOfContents: [ { content: "Internal Sources", @@ -749,8 +749,8 @@ const MOCK_GRAPH = { seen: 0, }, { - content: "Menu Query", - slug: "menu-query", + content: "collection Query", + slug: "collection-query", lvl: 5, i: 8, seen: 0, @@ -841,14 +841,14 @@ const MOCK_GRAPH = { }, ], }, - filename: "./docs/data.md", + workspacePath: "./docs/data.md", id: "data-sources", label: "Data Sources", route: "/docs/data", layout: "page", path: "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/docs/data.md", - fileName: "data", + workspacePath: "data", relativeExpectedPath: "'../docs/data/data.js'", title: "Data Sources", meta: [ @@ -899,9 +899,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 3, - linkheadings: 3, + collection: "side", + order: 3, + tocHeading: 3, tableOfContents: [ { content: "Element Label", @@ -975,7 +975,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./docs/front-matter.md", + workspacePath: "./docs/front-matter.md", id: "front-matter", label: "Front Matter", route: "/docs/front-matter", @@ -1031,18 +1031,18 @@ const MOCK_GRAPH = { }, { data: { - menu: "navigation", - index: "", - linkheadings: 0, + collection: "navigation", + order: "", + tocHeading: 0, tableOfContents: [], }, - filename: "./docs/index.md", + workspacePath: "./docs/order.md", id: "docs", label: "Docs", route: "/docs/", layout: "page", path: - "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/docs/index.md", + "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/docs/order.md", title: "Docs", meta: [ { @@ -1092,9 +1092,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 6, - linkheadings: 3, + collection: "side", + order: 6, + tocHeading: 3, tableOfContents: [ { content: "Page Layout", @@ -1126,7 +1126,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./docs/layouts.md", + workspacePath: "./docs/layouts.md", id: "layouts", label: "Layouts", route: "/docs/layouts", @@ -1182,9 +1182,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 4, - linkheadings: 3, + collection: "side", + order: 4, + tocHeading: 3, tableOfContents: [ { content: "Syntax Highlighting", @@ -1209,7 +1209,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./docs/markdown.md", + workspacePath: "./docs/markdown.md", id: "markdown", label: "Markdown", route: "/docs/markdown", @@ -1265,20 +1265,20 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 5, - linkheadings: 3, + collection: "side", + order: 5, + tocHeading: 3, tableOfContents: [ { - content: "Declare Menu", - slug: "declare-menu", + content: "Declare collection", + slug: "declare-collection", lvl: 3, i: 1, seen: 0, }, { - content: "Retrieve Menu", - slug: "retrieve-menu", + content: "Retrieve collection", + slug: "retrieve-collection", lvl: 3, i: 2, seen: 0, @@ -1299,14 +1299,14 @@ const MOCK_GRAPH = { }, ], }, - filename: "./docs/menus.md", - id: "menus", - label: "Menus", - route: "/docs/menus", + workspacePath: "./docs/collections.md", + id: "collections", + label: "collections", + route: "/docs/collections", layout: "page", path: - "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/docs/menus.md", - title: "Menus", + "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/docs/collections.md", + title: "collections", meta: [ { name: "description", @@ -1355,9 +1355,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 8, - linkheadings: 3, + collection: "side", + order: 8, + tocHeading: 3, tableOfContents: [ { content: "NodeJS", @@ -1389,7 +1389,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./docs/tech-stack.md", + workspacePath: "./docs/tech-stack.md", id: "tech-stack", label: "Tech Stack", route: "/docs/tech-stack", @@ -1445,9 +1445,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 5, - linkheadings: 3, + collection: "side", + order: 5, + tocHeading: 3, tableOfContents: [ { content: "Web Components", @@ -1465,7 +1465,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./getting-started/branding.md", + workspacePath: "./getting-started/branding.md", id: "branding", label: "Branding", route: "/getting-started/branding", @@ -1521,12 +1521,12 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 6, - linkheadings: 0, + collection: "side", + order: 6, + tocHeading: 0, tableOfContents: [], }, - filename: "./getting-started/build-and-deploy.md", + workspacePath: "./getting-started/build-and-deploy.md", id: "build-and-deploy", label: "Build And Deploy", route: "/getting-started/build-and-deploy", @@ -1582,9 +1582,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 4, - linkheadings: 3, + collection: "side", + order: 4, + tocHeading: 3, tableOfContents: [ { content: "Objectives", @@ -1623,7 +1623,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./getting-started/creating-content.md", + workspacePath: "./getting-started/creating-content.md", id: "creating-content", label: "Creating Content", route: "/getting-started/creating-content", @@ -1679,18 +1679,18 @@ const MOCK_GRAPH = { }, { data: { - menu: "navigation", - index: "", - linkheadings: 0, + collection: "navigation", + order: "", + tocHeading: 0, tableOfContents: [], }, - filename: "./getting-started/index.md", + workspacePath: "./getting-started/order.md", id: "getting-started", label: "Getting Started", route: "/getting-started/", layout: "page", path: - "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/getting-started/index.md", + "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/getting-started/order.md", title: "Getting Started", meta: [ { @@ -1740,9 +1740,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 2, - linkheadings: 3, + collection: "side", + order: 2, + tocHeading: 3, tableOfContents: [ { content: "Workspace", @@ -1767,7 +1767,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./getting-started/key-concepts.md", + workspacePath: "./getting-started/key-concepts.md", id: "key-concepts", label: "Key Concepts", route: "/getting-started/key-concepts", @@ -1823,12 +1823,12 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 7, - linkheadings: 0, + collection: "side", + order: 7, + tocHeading: 0, tableOfContents: [], }, - filename: "./getting-started/next-steps.md", + workspacePath: "./getting-started/next-steps.md", id: "next-steps", label: "Next Steps", route: "/getting-started/next-steps", @@ -1884,9 +1884,9 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 2, - linkheadings: 3, + collection: "side", + order: 2, + tocHeading: 3, tableOfContents: [ { content: "Installing Greenwood", @@ -1911,7 +1911,7 @@ const MOCK_GRAPH = { }, ], }, - filename: "./getting-started/project-setup.md", + workspacePath: "./getting-started/project-setup.md", id: "project-setup", label: "Project Setup", route: "/getting-started/project-setup", @@ -1967,12 +1967,12 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 1, - linkheadings: 0, + collection: "side", + order: 1, + tocHeading: 0, tableOfContents: [], }, - filename: "./getting-started/quick-start.md", + workspacePath: "./getting-started/quick-start.md", id: "quick-start", label: "Quick Start", route: "/getting-started/quick-start", @@ -2028,12 +2028,12 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 1, - linkheadings: 0, + collection: "side", + order: 1, + tocHeading: 0, tableOfContents: [], }, - filename: "./plugins/composite-plugins.md", + workspacePath: "./plugins/composite-plugins.md", id: "composite-plugins", label: "Composite Plugins", route: "/plugins/composite-plugins", @@ -2089,19 +2089,19 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 2, - linkheadings: 0, + collection: "side", + order: 2, + tocHeading: 0, tableOfContents: [], }, - filename: "./plugins/index-hooks.md", - id: "index-hooks", - label: "Index Hooks", - route: "/plugins/index-hooks", + workspacePath: "./plugins/order-hooks.md", + id: "order-hooks", + label: "order Hooks", + route: "/plugins/order-hooks", layout: "page", path: - "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/plugins/index-hooks.md", - title: "Index Hooks", + "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/plugins/order-hooks.md", + title: "order Hooks", meta: [ { name: "description", @@ -2150,18 +2150,18 @@ const MOCK_GRAPH = { }, { data: { - menu: "navigation", - index: "", - linkheadings: 0, + collection: "navigation", + order: "", + tocHeading: 0, tableOfContents: [], }, - filename: "./plugins/index.md", + workspacePath: "./plugins/order.md", id: "plugins", label: "Plugins", route: "/plugins/", layout: "page", path: - "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/plugins/index.md", + "/media/skynet/DATA/workspace/evergreen/greenwood/www/pages/plugins/order.md", title: "Plugins", meta: [ { @@ -2211,12 +2211,12 @@ const MOCK_GRAPH = { }, { data: { - menu: "side", - index: 3, - linkheadings: 0, + collection: "side", + order: 3, + tocHeading: 0, tableOfContents: [], }, - filename: "./plugins/webpack.md", + workspacePath: "./plugins/webpack.md", id: "webpack", label: "Webpack", route: "/plugins/webpack", diff --git a/packages/plugin-graphql/test/unit/schema/config.spec.js b/packages/plugin-graphql/test/unit/schema/config.spec.js deleted file mode 100644 index ac1019b1d..000000000 --- a/packages/plugin-graphql/test/unit/schema/config.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -import chai from 'chai'; -import { configResolvers } from '../../../src/schema/config.js'; -import { MOCK_CONFIG } from '../mocks/config.js'; - -const expect = chai.expect; - -describe('Unit Test: Data', function() { - - describe('Schema', function() { - - describe('Config', function() { - let config = {}; - - before(async function() { - config = await configResolvers.Query.config(undefined, {}, MOCK_CONFIG); - }); - - describe('Dev Server', function() { - const { devServer } = MOCK_CONFIG.config; - - it('should have the expected devServer.port', function() { - expect(config.devServer.port).to.equal(devServer.port); - }); - }); - - describe('Optimization', function() { - - it('should have a default optimization setting of default', function() { - expect(config.optimization).to.equal(MOCK_CONFIG.config.optimization); - }); - - }); - - describe('Prerender', function() { - - it('should have a default prerender setting of false', function() { - expect(config.optimization).to.equal(MOCK_CONFIG.config.prerender); - }); - - }); - - describe('Workspace', function() { - const { workspace } = MOCK_CONFIG.config; - - it('should have the expected title', function() { - expect(workspace).to.equal(config.workspace); - }); - }); - - }); - - }); -}); \ No newline at end of file diff --git a/packages/plugin-graphql/test/unit/schema/graph.children.spec.js b/packages/plugin-graphql/test/unit/schema/graph.children.spec.js new file mode 100644 index 000000000..8bf3c4a8e --- /dev/null +++ b/packages/plugin-graphql/test/unit/schema/graph.children.spec.js @@ -0,0 +1,85 @@ +import chai from 'chai'; +import { graphResolvers } from '../../../src/schema/graph.js'; +import { MOCK_GRAPH } from '../mocks/graph.js'; + +const expect = chai.expect; + +describe('Unit Test: Data', function() { + + describe('Schema', function() { + + describe('Graph', function() { + + describe('getChildrenFromGraph', function() { + + describe('with default sort', function() { + let data = []; + + before(async function() { + data = await graphResolvers.Query.children(undefined, { + parent: '/getting-started' + }, { + graph: MOCK_GRAPH.graph, + config: { + basePath: '/my-app' + } + }); + }); + + it('should have 7 children', function() { + expect(data.length).to.equal(7); + }); + + it('should have Branding as the first item', function() { + const item = data[0]; + + expect(item.label).to.be.equal('Branding'); + expect(item.route).to.be.equal('/getting-started/branding'); + }); + + it('should have Build and Deploy as the second item', function() { + const item = data[1]; + + expect(item.label).to.be.equal('Build And Deploy'); + expect(item.route).to.be.equal('/getting-started/build-and-deploy'); + }); + + it('should have Creating Content as the third item', function() { + const item = data[2]; + + expect(item.label).to.be.equal('Creating Content'); + expect(item.route).to.be.equal('/getting-started/creating-content'); + }); + + it('should have Key Concepts as the fourth item', function() { + const item = data[3]; + + expect(item.label).to.be.equal('Key Concepts'); + expect(item.route).to.be.equal('/getting-started/key-concepts'); + }); + + it('should have Next Steps as the fifth item', function() { + const item = data[4]; + + expect(item.label).to.be.equal('Next Steps'); + expect(item.route).to.be.equal('/getting-started/next-steps'); + }); + + it('should have Project Setup as the sixth item', function() { + const item = data[5]; + + expect(item.label).to.be.equal('Project Setup'); + expect(item.route).to.be.equal('/getting-started/project-setup'); + }); + + it('should have Quick Start as the seventh item', function() { + const item = data[6]; + + expect(item.label).to.be.equal('Quick Start'); + expect(item.route).to.be.equal('/getting-started/quick-start'); + }); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/packages/plugin-graphql/test/unit/schema/graph.collection.spec.js b/packages/plugin-graphql/test/unit/schema/graph.collection.spec.js new file mode 100644 index 000000000..68d67e67f --- /dev/null +++ b/packages/plugin-graphql/test/unit/schema/graph.collection.spec.js @@ -0,0 +1,65 @@ +import chai from 'chai'; +import { graphResolvers } from '../../../src/schema/graph.js'; +import { MOCK_GRAPH } from '../mocks/graph.js'; + +const expect = chai.expect; + +describe('Unit Test: Data', function() { + + describe('Schema', function() { + + describe('Graph', function() { + + describe('getCollection navigation menu', function() { + + describe('with default sort', function() { + let collection = []; + + before(async function() { + collection = await graphResolvers.Query.collection(undefined, { + name: 'navigation' + }, { + graph: MOCK_GRAPH.graph, + config: { + basePath: '' + } + }); + }); + + it('should have 4 children', function() { + expect(collection.length).to.equal(4); + }); + + it('should have About as the first item', function() { + const item = collection[0]; + + expect(item.label).to.be.equal('About'); + expect(item.route).to.be.equal('/about/'); + }); + + it('should have Docs as the second item', function() { + const item = collection[1]; + + expect(item.label).to.be.equal('Docs'); + expect(item.route).to.be.equal('/docs/'); + }); + + it('should have Getting Started as the third item', function() { + const item = collection[2]; + + expect(item.label).to.be.equal('Getting Started'); + expect(item.route).to.be.equal('/getting-started/'); + }); + + it('should have Plugins as the fourth item', function() { + const item = collection[3]; + + expect(item.label).to.be.equal('Plugins'); + expect(item.route).to.be.equal('/plugins/'); + }); + }); + }); + + }); + }); +}); \ No newline at end of file diff --git a/packages/plugin-graphql/test/unit/schema/graph.menu.spec.js b/packages/plugin-graphql/test/unit/schema/graph.menu.spec.js deleted file mode 100644 index d1d6a2abd..000000000 --- a/packages/plugin-graphql/test/unit/schema/graph.menu.spec.js +++ /dev/null @@ -1,456 +0,0 @@ -import chai from 'chai'; -import { graphResolvers } from '../../../src/schema/graph.js'; -import { MOCK_GRAPH } from '../mocks/graph.js'; - -const expect = chai.expect; - -describe('Unit Test: Data', function() { - - describe('Schema', function() { - - describe('Graph', function() { - - describe('getMenuFromGraph navigation menu', function() { - - describe('with default sort', function() { - let navigation = []; - - before(async function() { - navigation = await graphResolvers.Query.menu(undefined, { - name: 'navigation' - }, { - graph: MOCK_GRAPH.graph, - config: { - basePath: '' - } - }); - }); - - it('should have 4 children', function() { - expect(navigation.children.length).to.equal(4); - }); - - it('should have About as the first item', function() { - const item = navigation.children[0].item; - - expect(item.label).to.be.equal('About'); - expect(item.route).to.be.equal('/about/'); - }); - - it('should have Docs as the second item', function() { - const item = navigation.children[1].item; - - expect(item.label).to.be.equal('Docs'); - expect(item.route).to.be.equal('/docs/'); - }); - - it('should have Getting Started as the third item', function() { - const item = navigation.children[2].item; - - expect(item.label).to.be.equal('Getting Started'); - expect(item.route).to.be.equal('/getting-started/'); - }); - - it('should have Plugins as the fourth item', function() { - const item = navigation.children[3].item; - - expect(item.label).to.be.equal('Plugins'); - expect(item.route).to.be.equal('/plugins/'); - }); - }); - }); - - describe('getMenuFromGraph filtering by side menu from path /getting-started', function() { - describe('with no sorting (default)', function() { - let shelf = []; - - before(async function() { - shelf = await graphResolvers.Query.menu(undefined, { - pathname: '/getting-started/', - name: 'side' - }, { - graph: MOCK_GRAPH.graph, - config: { - basePath: '/my-app' - } - }); - }); - - it('should have 7 children', function() { - expect(shelf.children.length).to.equal(7); - }); - - describe('the first item:', function() { - it('should be labeled and linked to Styles and Web Components', function() { - const item = shelf.children[0].item; - - expect(item.label).to.be.equal('Branding'); - expect(item.route).to.be.equal('/getting-started/branding'); - }); - - it('should have the correct sub items', function() { - const subitem = shelf.children[0].children; - - expect(subitem[0].item.label).to.be.equal('Web Components'); - expect(subitem[0].item.route).to.be.equal('#web-components'); - expect(subitem[1].item.label).to.be.equal('CSS'); - expect(subitem[1].item.route).to.be.equal('#css'); - }); - }); - - describe('the second item:', function() { - it('should be labeled and linked to Build And Deploy', function() { - const item = shelf.children[1].item; - - expect(item.label).to.be.equal('Build And Deploy'); - expect(item.route).to.be.equal('/getting-started/build-and-deploy'); - }); - }); - - describe('the third item:', function() { - it('should be labeled and linked to Creating Content', function() { - const item = shelf.children[2].item; - - expect(item.label).to.be.equal('Creating Content'); - expect(item.route).to.be.equal('/getting-started/creating-content'); - }); - - it('should have the correct sub items', function() { - const subitem = shelf.children[2].children; - expect(subitem[0].item.label).to.be.equal('Objectives'); - expect(subitem[1].item.label).to.be.equal('Home Page Layout'); - expect(subitem[2].item.label).to.be.equal('Blog Posts Layout'); - expect(subitem[3].item.label).to.be.equal('Creating Pages'); - expect(subitem[4].item.label).to.be.equal('Development Server'); - }); - }); - }); - - describe('with custom front matter index ascending', function() { - let shelf = []; - - before(async function() { - shelf = await graphResolvers.Query.menu(undefined, { - pathname: '/getting-started/', - name: 'side', - orderBy: 'index_asc' - }, { - graph: MOCK_GRAPH.graph, - config: { - basePath: '' - } - }); - }); - - it('should have 7 children', function() { - expect(shelf.children.length).to.equal(7); - }); - - describe('the first item:', function() { - it('should be labeled and linked to Quick Start', function() { - const item = shelf.children[0].item; - - expect(item.label).to.be.equal('Quick Start'); - expect(item.route).to.be.equal('/getting-started/quick-start'); - }); - }); - - describe('the second item:', function() { - it('should be labeled and linked to Key Concepts', function() { - const item = shelf.children[1].item; - - expect(item.label).to.be.equal('Key Concepts'); - expect(item.route).to.be.equal('/getting-started/key-concepts'); - }); - - it('should have the correct sub items', function() { - const subitem = shelf.children[1].children; - - expect(subitem[0].item.label).to.be.equal('Workspace'); - expect(subitem[0].item.route).to.be.equal('#workspace'); - expect(subitem[1].item.label).to.be.equal('Layouts'); - expect(subitem[1].item.route).to.be.equal('#layouts'); - expect(subitem[2].item.label).to.be.equal('Pages'); - expect(subitem[2].item.route).to.be.equal('#pages'); - }); - }); - - describe('the third item:', function() { - it('should be labeled and linked to Project Setup', function() { - const item = shelf.children[2].item; - - expect(item.label).to.be.equal('Project Setup'); - expect(item.route).to.be.equal('/getting-started/project-setup'); - }); - - it('should have the correct sub items', function() { - const subitem = shelf.children[2].children; - - expect(subitem[0].item.label).to.be.equal('Installing Greenwood'); - expect(subitem[0].item.route).to.be.equal('#installing-greenwood'); - expect(subitem[1].item.label).to.be.equal('Configuring Workflows'); - expect(subitem[1].item.route).to.be.equal('#configuring-workflows'); - expect(subitem[2].item.label).to.be.equal('Project Structure'); - expect(subitem[2].item.route).to.be.equal('#project-structure'); - }); - }); - }); - - describe('with custom front matter index descending', function() { - let shelf = []; - - before(async function() { - shelf = await graphResolvers.Query.menu(undefined, { - pathname: '/getting-started/', - name: 'side', - orderBy: 'index_desc' - }, { - graph: MOCK_GRAPH.graph, - config: { - basePath: '' - } - }); - }); - - it('should have 7 children', function() { - expect(shelf.children.length).to.equal(7); - }); - - describe('the first item:', function() { - it('should be labeled and linked to Next Steps', function() { - const item = shelf.children[0].item; - - expect(item.label).to.be.equal('Next Steps'); - expect(item.route).to.be.equal('/getting-started/next-steps'); - }); - }); - - describe('the second item:', function() { - it('should be labeled and linked to Build And Deploy', function() { - const item = shelf.children[1].item; - - expect(item.label).to.be.equal('Build And Deploy'); - expect(item.route).to.be.equal('/getting-started/build-and-deploy'); - }); - }); - - describe('the third item:', function() { - it('should be labeled and linked to Styles and Web Components', function() { - const item = shelf.children[2].item; - - expect(item.label).to.be.equal('Branding'); - expect(item.route).to.be.equal('/getting-started/branding'); - }); - - it('should have the correct sub items', function() { - const subitem = shelf.children[2].children; - - expect(subitem[0].item.label).to.be.equal('Web Components'); - expect(subitem[0].item.route).to.be.equal('#web-components'); - expect(subitem[1].item.label).to.be.equal('CSS'); - expect(subitem[1].item.route).to.be.equal('#css'); - }); - }); - }); - - describe('with custom front matter title ascending', function() { - let shelf = []; - - before(async function() { - shelf = await graphResolvers.Query.menu(undefined, { - pathname: '/getting-started/', - name: 'side', - orderBy: 'title_asc' - }, { - graph: MOCK_GRAPH.graph, - config: { - basePath: '' - } - }); - }); - - it('should have 7 children', function() { - expect(shelf.children.length).to.equal(7); - }); - - describe('the first item:', function() { - it('should be labeled and linked to Branding', function() { - const item = shelf.children[0].item; - - expect(item.label).to.be.equal('Branding'); - expect(item.route).to.be.equal('/getting-started/branding'); - }); - }); - - describe('the second item:', function() { - it('should be labeled and linked to Build And Deploy', function() { - const item = shelf.children[1].item; - - expect(item.label).to.be.equal('Build And Deploy'); - expect(item.route).to.be.equal('/getting-started/build-and-deploy'); - }); - }); - - describe('the third item:', function() { - it('should be labeled and linked to Creating Content', function() { - const item = shelf.children[2].item; - - expect(item.label).to.be.equal('Creating Content'); - expect(item.route).to.be.equal('/getting-started/creating-content'); - }); - }); - - describe('the fourth item:', function() { - it('should be labeled and linked to Key Concepts', function() { - const item = shelf.children[3].item; - - expect(item.label).to.be.equal('Key Concepts'); - expect(item.route).to.be.equal('/getting-started/key-concepts'); - }); - - it('should have the correct sub items', function() { - const subitem = shelf.children[3].children; - - expect(subitem[0].item.label).to.be.equal('Workspace'); - expect(subitem[0].item.route).to.be.equal('#workspace'); - expect(subitem[1].item.label).to.be.equal('Layouts'); - expect(subitem[1].item.route).to.be.equal('#layouts'); - expect(subitem[2].item.label).to.be.equal('Pages'); - expect(subitem[2].item.route).to.be.equal('#pages'); - }); - }); - }); - - describe('with custom front matter title descending', function() { - let shelf = []; - - before(async function() { - shelf = await graphResolvers.Query.menu(undefined, { - pathname: '/getting-started/', - name: 'side', - orderBy: 'title_desc' - }, { - graph: MOCK_GRAPH.graph, - config: { - basePath: '' - } - }); - }); - - it('should have 7 children', function() { - expect(shelf.children.length).to.equal(7); - }); - - describe('the first item:', function() { - it('should be labeled and linked to Quick Start', function() { - const item = shelf.children[0].item; - - expect(item.label).to.be.equal('Quick Start'); - expect(item.route).to.be.equal('/getting-started/quick-start'); - }); - }); - - describe('the second item:', function() { - it('should be labeled and linked to Project Setup', function() { - const item = shelf.children[1].item; - - expect(item.label).to.be.equal('Project Setup'); - expect(item.route).to.be.equal('/getting-started/project-setup'); - }); - - it('should have the correct sub items', function() { - const subitem = shelf.children[1].children; - - expect(subitem[0].item.label).to.be.equal('Installing Greenwood'); - expect(subitem[0].item.route).to.be.equal('#installing-greenwood'); - expect(subitem[1].item.label).to.be.equal('Configuring Workflows'); - expect(subitem[1].item.route).to.be.equal('#configuring-workflows'); - expect(subitem[2].item.label).to.be.equal('Project Structure'); - expect(subitem[2].item.route).to.be.equal('#project-structure'); - }); - }); - - describe('the third item:', function() { - it('should be labeled and linked to Next Steps', function() { - const item = shelf.children[2].item; - - expect(item.label).to.be.equal('Next Steps'); - expect(item.route).to.be.equal('/getting-started/next-steps'); - }); - }); - }); - }); - - describe('getChildrenFromGraph', function() { - - describe('with default sort', function() { - let data = []; - - before(async function() { - data = await graphResolvers.Query.children(undefined, { - parent: '/getting-started' - }, { - graph: MOCK_GRAPH.graph, - config: { - basePath: '/my-app' - } - }); - }); - - it('should have 7 children', function() { - expect(data.length).to.equal(7); - }); - - it('should have Branding as the first item', function() { - const item = data[0]; - - expect(item.label).to.be.equal('Branding'); - expect(item.route).to.be.equal('/getting-started/branding'); - }); - - it('should have Build and Deploy as the second item', function() { - const item = data[1]; - - expect(item.label).to.be.equal('Build And Deploy'); - expect(item.route).to.be.equal('/getting-started/build-and-deploy'); - }); - - it('should have Creating Content as the third item', function() { - const item = data[2]; - - expect(item.label).to.be.equal('Creating Content'); - expect(item.route).to.be.equal('/getting-started/creating-content'); - }); - - it('should have Key Concepts as the fourth item', function() { - const item = data[3]; - - expect(item.label).to.be.equal('Key Concepts'); - expect(item.route).to.be.equal('/getting-started/key-concepts'); - }); - - it('should have Next Steps as the fifth item', function() { - const item = data[4]; - - expect(item.label).to.be.equal('Next Steps'); - expect(item.route).to.be.equal('/getting-started/next-steps'); - }); - - it('should have Project Setup as the sixth item', function() { - const item = data[5]; - - expect(item.label).to.be.equal('Project Setup'); - expect(item.route).to.be.equal('/getting-started/project-setup'); - }); - - it('should have Quick Start as the seventh item', function() { - const item = data[6]; - - expect(item.label).to.be.equal('Quick Start'); - expect(item.route).to.be.equal('/getting-started/quick-start'); - }); - }); - }); - }); - }); -}); \ No newline at end of file diff --git a/packages/plugin-graphql/test/unit/schema/graph.spec.js b/packages/plugin-graphql/test/unit/schema/graph.spec.js index e387592bd..f38905699 100644 --- a/packages/plugin-graphql/test/unit/schema/graph.spec.js +++ b/packages/plugin-graphql/test/unit/schema/graph.spec.js @@ -24,9 +24,6 @@ describe('Unit Test: Data', function() { it('should have all expected properties for each page', function() { pages.forEach(function(page) { expect(page.label).to.exist; - expect(page.id).to.exist; - expect(page.path).to.exist; - expect(page.filename).to.exist; expect(page.layout).to.exist; expect(page.title).to.exist; expect(page.route).to.exist; @@ -50,20 +47,14 @@ describe('Unit Test: Data', function() { expect(children.length).to.equal(7); }); - it('should have the expected value for id for each child', function() { - expect(children[0].id).to.equal('branding'); - expect(children[1].id).to.equal('build-and-deploy'); - expect(children[2].id).to.equal('creating-content'); - expect(children[3].id).to.equal('key-concepts'); - expect(children[4].id).to.equal('next-steps'); - expect(children[5].id).to.equal('project-setup'); - expect(children[6].id).to.equal('quick-start'); - }); - it('should have the expected route for each child', function() { - children.forEach(function(child) { - expect(child.route).to.equal(`/getting-started/${child.id}`); - }); + expect(children[0].route).to.equal('/getting-started/branding'); + expect(children[1].route).to.equal('/getting-started/build-and-deploy'); + expect(children[2].route).to.equal('/getting-started/creating-content'); + expect(children[3].route).to.equal('/getting-started/key-concepts'); + expect(children[4].route).to.equal('/getting-started/next-steps'); + expect(children[5].route).to.equal('/getting-started/project-setup'); + expect(children[6].route).to.equal('/getting-started/quick-start'); }); it('should have the expected label for each child', function() { @@ -76,12 +67,6 @@ describe('Unit Test: Data', function() { expect(children[6].label).to.equal('Quick Start'); }); - it('should have the expected path for each child', function() { - children.forEach(function(child) { - expect(child.path).to.contain(`/getting-started/${child.id}.md`); - }); - }); - it('should have "page" as the layout for all children', function() { children.forEach(function(child) { expect(child.layout).to.equal('page'); @@ -99,7 +84,7 @@ describe('Unit Test: Data', function() { }); it('should have expected custom front matter data if it is set', function() { - expect(children[0].data.menu).to.equal('side'); + expect(children[0].data.collection).to.equal('side'); }); }); }); diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js b/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js index e65bd24a9..d90ffc1cf 100644 --- a/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js +++ b/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js @@ -178,13 +178,13 @@ describe('Serve Greenwood With: ', function() { let dom; let usersPageDom; let usersPageHtml; - let aboutPageGraphData; + let artistsPageGraphData; before(async function() { const graph = JSON.parse(await fs.promises.readFile(path.join(outputPath, 'public/graph.json'), 'utf-8')); artists = JSON.parse(await fs.promises.readFile(new URL('./artists.json', import.meta.url), 'utf-8')); - aboutPageGraphData = graph.filter(page => page.route === '/artists/')[0]; + artistsPageGraphData = graph.filter(page => page.route === '/artists/')[0]; response = await fetch(`${hostname}/artists/`); data = await response.text(); @@ -252,17 +252,17 @@ describe('Serve Greenwood With: ', function() { }); it('should be a part of graph.json', function() { - expect(aboutPageGraphData).to.not.be.undefined; + expect(artistsPageGraphData).to.not.be.undefined; }); it('should have the expected menu and index values in the graph', function() { - expect(aboutPageGraphData.data.menu).to.equal('navigation'); - expect(aboutPageGraphData.data.index).to.equal(7); + expect(artistsPageGraphData.data.collection).to.equal('navigation'); + expect(artistsPageGraphData.data.order).to.equal(7); }); it('should have expected custom data values in its graph data', function() { - expect(aboutPageGraphData.data.author).to.equal('Project Evergreen'); - expect(aboutPageGraphData.data.date).to.equal('01-01-2021'); + expect(artistsPageGraphData.data.author).to.equal('Project Evergreen'); + expect(artistsPageGraphData.data.date).to.equal('01-01-2021'); }); it('should not have the expected lit hydration script in the ', function() { diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/artists.js b/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/artists.js index 19c96e771..5125c5aaf 100644 --- a/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/artists.js +++ b/packages/plugin-renderer-lit/test/cases/serve.default/src/pages/artists.js @@ -67,13 +67,11 @@ async function getBody() { async function getFrontmatter(compilation, { route }) { return { - menu: 'navigation', - index: 7, + collection: 'navigation', + order: 7, title: `My App - ${route}`, - data: { - author: 'Project Evergreen', - date: '01-01-2021' - } + author: 'Project Evergreen', + date: '01-01-2021' }; } diff --git a/packages/plugin-renderer-puppeteer/src/plugins/server.js b/packages/plugin-renderer-puppeteer/src/plugins/server.js index 0a2c62530..ea8232557 100644 --- a/packages/plugin-renderer-puppeteer/src/plugins/server.js +++ b/packages/plugin-renderer-puppeteer/src/plugins/server.js @@ -10,9 +10,10 @@ class PuppeteerServer extends ServerInterface { async start() { if (process.env.__GWD_COMMAND__ === 'build') { // eslint-disable-line no-underscore-dangle const { port } = this.compilation.config.devServer; + const offsetPort = port + 1; // don't try and start the dev server on the same port as the CLI - (await getDevServer(this.compilation)).listen(port, async () => { - console.info(`Started puppeteer prerender server at http://localhost:${port}`); + (await getDevServer(this.compilation)).listen(offsetPort, async () => { + console.info(`Started puppeteer prerender server at http://localhost:${offsetPort}`); }); } else { await Promise.resolve(); diff --git a/packages/plugin-renderer-puppeteer/src/puppeteer-handler.js b/packages/plugin-renderer-puppeteer/src/puppeteer-handler.js index 0a0b7d3d1..ef8945883 100644 --- a/packages/plugin-renderer-puppeteer/src/puppeteer-handler.js +++ b/packages/plugin-renderer-puppeteer/src/puppeteer-handler.js @@ -50,7 +50,8 @@ export default async function(compilation, callback) { try { const pages = compilation.graph.filter(page => !page.isSSR); const port = compilation.config.devServer.port; - const serverAddress = `http://127.0.0.1:${port}`; + const offsetPort = port + 1; // don't try and start the dev server on the same port as the CLI + const serverAddress = `http://127.0.0.1:${offsetPort}`; await runBrowser(serverAddress, pages); browserRunner.close(); diff --git a/www/components/header/header.js b/www/components/header/header.js index 4cc34950a..27e85f856 100644 --- a/www/components/header/header.js +++ b/www/components/header/header.js @@ -1,6 +1,6 @@ import { css, html, LitElement, unsafeCSS } from 'lit'; import client from '@greenwood/plugin-graphql/src/core/client.js'; -import MenuQuery from '@greenwood/plugin-graphql/src/queries/menu.gql'; +import CollectionQuery from '@greenwood/plugin-graphql/src/queries/collection.gql'; import headerCss from './header.css?type=raw'; import '../social-icons/social-icons.js'; @@ -29,14 +29,14 @@ class HeaderComponent extends LitElement { super.connectedCallback(); const response = await client.query({ - query: MenuQuery, + query: CollectionQuery, variables: { name: 'navigation', - order: 'index_asc' + orderBy: 'order_asc' } }); - this.navigation = response.data.menu.children.map(item => item.item); + this.navigation = response.data.collection; } /* eslint-disable indent */ @@ -61,8 +61,8 @@ class HeaderComponent extends LitElement { `; } } - -customElements.define('app-header', HeaderComponent); ``` -> _For more information on using GraphQL with Greenwood, please see our [GraphQL plugin's README](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-graphql)._ -### External Sources +### GraphQL -Using our [Source plugin](/plugins/source/), just as you can get your content as data _out_ of Greenwood, so can you provide your own sources of data _to_ Greenwood. This is great for pulling content from a headless CMS, database, or anything else you can imagine! +For GraphQL support, please see our [**GraphQL plugin**](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-graphql) which in additional to exposing an [Apollo server and playground](https://www.apollographql.com/docs/apollo-server/) locally at `http://localhost:4000`, also provides GraphQL alternatives to our Data Client through a customized (read only) Apollo client based wrapper. -The supported [fields from Greenwood's schema](/docs/data/#internal-sources) are: -```javascript -graph { - body, // REQUIRED (string of your content) - id, - label, - route, // REQUIRED and MUST end in a forward slash - layout, - title, - data -} -``` \ No newline at end of file +![graphql-playground](/assets/graphql-playground.png) \ No newline at end of file diff --git a/www/pages/docs/front-matter.md b/www/pages/docs/front-matter.md index c86114135..5be461449 100644 --- a/www/pages/docs/front-matter.md +++ b/www/pages/docs/front-matter.md @@ -1,16 +1,15 @@ --- -label: 'front-matter' -menu: side -title: 'Front Matter' -index: 3 -linkheadings: 3 +collection: docs +title: Front Matter +order: 3 +tocHeading: 3 --- ## Front Matter "Front matter" is a [YAML](https://yaml.org/) block at the top of any markdown file. It gives you the ability to define variables that are made available to Greenwood's build process and then your code. You can also use it to `import` additional files. -### Element Label +### Label By default Greenwood will aim to create a label for your page based on filename and context and include that in the graph. This can be useful for categorizing or organizing your content when rendering client side, or if you want to create a custom value to display for a link or in your HTML that may be different from what can be inferred from the file name. @@ -23,7 +22,6 @@ label: 'My Blog Post from 3/5/2020' ``` - ### Imports If you want to include files on a _per **page** basis_, you can use the predefined `imports` feature from Greenwood. This is great for one off use cases where you don't want to ship a third party lib in all your layouts, but just for this one particular page. This is effectively a naive form of code splitting. 🤓 @@ -46,7 +44,6 @@ You will then see the following emitted for file > _See our [Markdown Docs](/docs/markdown#imports) for more information about rendering custom elements in markdown files._ - ### Layouts When creating multiple [page layouts](/docs/layouts/), you can use the `layout` front-matter to configure Greenwood to use that layout for a given page. @@ -83,34 +80,35 @@ In this example, the `` tag will be the `title`. <title>My Blog Post ``` -> Note: If you set `title` from your [configuration file](/docs/configuration#title), the output would be -> ```html -> {ConfigTitle} - My Blog Post -> ``` - ### Custom Data -You can also pass custom data from your markdown file and extract that from Greenwood's [_graph.json_ via `fetch` or our GraphQL server](/docs/data/). +You can also pass custom data from your markdown file and that will be made available to Greenwood's [content as data](/docs/data/) or [active frontmatter](/docs/configuration/#active-frontmatter) capabilities. #### Example ```md --- -author: 'Jon Doe' -date: '04/07/2020' +author: Jon Doe +date: 04/07/2020' --- -``` -You would then need to create a `graph` GraphQL query and use that with Greenwood's built in client to get access to that `data`, plus whatever other fields you might want. -```graphql -query { - graph { - data { - author, - date - } - } -} +# First Post + +My first post ``` -> See [our docs](/docs/data#internal-sources) on using GraphQL w/Greenwood for more information on querying for data. \ No newline at end of file +Would then be available in the [`data` property](/docs/data/#page-data). + +### Active Frontmatter + +With [`activeFrontmatter`](/docs/configuration/#active-frontmatter) enabled, any of these properties would be available in your HTML or markdown. + +```md +--- +author: Project Evergreen +--- + +## My Post + +Authored By: ${globalThis.page.data.author} +``` \ No newline at end of file diff --git a/www/pages/docs/index.md b/www/pages/docs/index.md index 9ae7e0dad..187d79a05 100644 --- a/www/pages/docs/index.md +++ b/www/pages/docs/index.md @@ -1,8 +1,6 @@ --- -label: 'docs' -menu: navigation -title: Docs -index: 2 +collection: navigation +order: 2 --- ## Documentation diff --git a/www/pages/docs/layouts.md b/www/pages/docs/layouts.md index 4f863b030..498f8469d 100644 --- a/www/pages/docs/layouts.md +++ b/www/pages/docs/layouts.md @@ -1,9 +1,9 @@ --- -label: 'layouts-and-pages' -menu: side -title: 'Layouts and Pages' -index: 7 -linkheadings: 3 +collection: docs +title: Layouts and Pages +label: Layouts and Pages +order: 7 +tocHeading: 3 --- ## Layouts and Pages diff --git a/www/pages/docs/markdown.md b/www/pages/docs/markdown.md index 05e1b8c38..262298fc8 100644 --- a/www/pages/docs/markdown.md +++ b/www/pages/docs/markdown.md @@ -1,9 +1,7 @@ --- -label: 'markdown' -menu: side -title: 'Markdown' -index: 4 -linkheadings: 3 +collection: docs +order: 4 +tocHeading: 3 --- ## Markdown diff --git a/www/pages/docs/menus.md b/www/pages/docs/menus.md deleted file mode 100644 index bd473da0e..000000000 --- a/www/pages/docs/menus.md +++ /dev/null @@ -1,295 +0,0 @@ ---- -label: 'menus' -menu: side -title: 'Menus' -index: 10 -linkheadings: 3 ---- - -## Menus - -In this section we'll touch on the menu related feature of Greenwood which utilizes [data sources](/docs/data/) within a component to query for [front matter](/docs/front-matter/) declared menus. - -### Declare Menu - -A common example of a menu you might use would be a **navigation** menu. - -To do this we first need to define which pages will be linked in this navigation menu. - -For this example, let's say we want "about", "docs", "contact us", all linked within our navigation menu. Then we need to define the navigation menu, within the [front matter](/docs/front-matter) at the top of each page. The front matter defined variables for menus are: - -| Variable | Description | -|-------------|:--------------------------------------------------| -| title | The title of the page link within the menu | -| menu | The name of the menu, cannot have spaces or special characters. | -| index | The position of the page within a menu. Custom set the position higher or lower than default. You can sort these positions alphabetically or by index | -| linkheadings | Integer. If you want to parse the page for headings and include them as children of the page link, add `linkheadings: 3` to parse for `

` headings. Set integer to the heading level you want to parse. e.g. `h1, h2, h3` | - -e.g. create the following in a new directory within your `/pages` directory. - -`index.md` - -```md ---- -title: 'About' -menu: 'navigation' -index: 1 ---- - -# About -``` - - -`docs.md` - -```md ---- -title: 'Docs' -menu: 'navigation' -index: 2 ---- - -# Documentation -``` - -`contact.md` - -```md ---- -title: 'Contact' -menu: 'navigation' -index: 3 -linkheadings: 3 ---- - -# Contact - -### Online - -### Offline - -### Locations -``` - -> **Note:** the front-matter variable `linkheadings: 3` will add all the `

` headings as children subitems within a menu item. So in this example the menu item `Contact`, will have the children: `Online`(linked to #online), `Offline`(linked to #offline), and `Locations`(linked to #locations). You can set `linkheadings:` to any header level you require not just `3` e.g. `linkheadings: 2` for `

` elements. An example of the [linkheadings query result](#query-result) can be found below. - -### Retrieve Menu - -Now in order to use our navigation menu within a component we need to query it via GraphQL. - -```js -// navigation.js -import { LitElement, html } from 'lit'; -import client from '@greenwood/plugin-graphql/src/core/client.js'; -import MenuQuery from '@greenwood/plugin-graphql/src/queries/menu.gql'; - -class HeaderComponent extends LitElement { - - constructor() { - super(); - this.navigation = []; - } - - async connectedCallback() { - super.connectedCallback(); - - const response = await client.query({ - query: MenuQuery, - variables: { - name: 'navigation' - } - }); - - this.navigation = response.data.menu.children.map(item => item.item); - } - - render() { - const { navigation } = this; - - return html` - - `; - } -} -customElements.define('app-header', HeaderComponent); -``` - -### Query Result - -The query will result in the object(default sort by filename): -```json -"menu": { - "children": [ - { - "children": [ - { "item": {"label": "Online", "route": "#online"}}, - { "item": {"label": "Offline", "route": "#offline"}}, - { "item": {"label": "Locations", "route": "#locations"}}, - ], - "item": {"label": "Contact", "route": "/mydirectory/contact"} - }, - { - "children": [], - "item": {"label": "Docs", "route": "/mydirectory/docs"} - }, - { - "children": [], - "item": {"label": "About", "route": "/mydirectory/"} - } - ], - "item": {"label": "navigation", "route": "na"} -} -``` - - -### Sorting - -The position of items within a menu can be sorted by simply adding the `order` variable to our query. - -```js -const response = await client.query({ - query: MenuQuery, - variables: { - name: 'navigation', - order: 'index_asc' - } -}); - -console.debug(response); -``` - - -The following sorts are available. - -| Sort | Description -|-----------|:---------------| -| | no order declared, sorts by alphabetical file name | -|index_asc | Sort by index, ascending order | -|index_desc | Sort by index, descending order | -|title_asc | Sort by title, ascending order | -|title_desc | Sort by title, descending order | - -### Filtering By Path - -If you only want specific menu items to show within a specific subdirectory. You can also include the `route` variable to specify a specific path the menu will be displaying on. By doing so, only pages with a menu that matches the base path of the route provided would be included in the query. This would be useful for a shelf menu, for example if path is `/docs/somepage` and you only want to include pages within the `/docs` directory in your menu. You would set your `route:` variable to `window.location.pathname`. - -```js -const response = await client.query({ - query: MenuQuery, - variables: { - name: 'shelf', - order: 'index_asc', - route: window.location.pathname - } -}); - -console.debug(response); -``` - -#### Filter By Path Example. - -You have 2 directories: `/docs` and `/about`. - -Each directory has two pages and you have one single menu declared within all your pages front-matter called: **shelf** - -`/docs/index.md`: -```md ---- -title: 'documentation' -menu: 'shelf' ---- - -# Documentation -``` - -`/docs/components.md`: -```md ---- -title: 'components' -menu: 'shelf' ---- - -# components -``` - -`/about/index.md`: -```md ---- -title: 'about' -menu: 'shelf' ---- - -# About -``` - -`/about/stuff.md`: -```md ---- -title: 'stuff' -menu: 'shelf' ---- - -# Stuff -``` - -#### Query the example - -Now when you query by **route** for the **shelf** menu, you will only see menu items associated with the base path of either `/docs` (if you're viewing /docs) or `/about`(if you're viewing /about). - - -```js -const response = await client.query({ - query: MenuQuery, - variables: { - name: 'shelf', - order: 'index_asc', - route: window.location.pathname - } -}); - -console.debug(response); -``` - -Despite having the same menu declared in all 4 pages, by including `route:` variable we're filtering our menus based on the basePath(subdirectory). - -The object result for `/docs` is: - -```json -"menu":{ - "item": {"label": "shelf", "link": "na"}, - "children":[{ - "item":{"label":"Components","route":"/docs/components"}, - "children":[] - }, - { - "item":{"label":"Docs","route":"/docs/"}, - "children":[] - } - ] -} -``` - -The object result for `/about` is: - -```json -"menu":{ - "item": {"label": "shelf", "link": "na"}, - "children":[{ - "item":{"label":"stuff","link":"/about/stuff"}, - "children":[] - }, - { - "item":{"label":"about","link":"/about/"}, - "children":[] - } - ] -} -``` \ No newline at end of file diff --git a/www/pages/docs/scripts.md b/www/pages/docs/scripts.md index 3c94461e5..430cafc26 100644 --- a/www/pages/docs/scripts.md +++ b/www/pages/docs/scripts.md @@ -1,9 +1,9 @@ --- -label: 'scripts-and-imports' -menu: side -title: 'Scripts and Imports' -index: 5 -linkheadings: 3 +collection: docs +title: Scripts and Imports +label: Scripts and Imports +order: 5 +tocHeading: 3 --- ## Scripts and Imports diff --git a/www/pages/docs/server-rendering.md b/www/pages/docs/server-rendering.md index a32e0ea90..e5dad3e81 100644 --- a/www/pages/docs/server-rendering.md +++ b/www/pages/docs/server-rendering.md @@ -1,9 +1,7 @@ --- -label: 'server-rendering' -menu: side -title: 'Server Rendering' -index: 8 -linkheadings: 3 +collection: docs +order: 8 +tocHeading: 3 --- ## Server Rendering (Beta) @@ -29,7 +27,7 @@ The above would serve content in a browser at `/users/`. In your page _.js_ file, Greenwood supports the following functions you can `export` for providing server rendered configuration and content: - `default`: Use a custom element to render your page content. Will take precedence over `getBody`. Will also automatically track your custom element dependencies, in place of having to define [frontmatter imports](/docs/front-matter/#imports) in `getFrontmatter`. -- `getFrontmatter`: Static [frontmatter](/docs/front-matter/), useful in conjunction with [menus](/docs/menus/) or otherwise static configuration / meta data. +- `getFrontmatter`: Static [frontmatter](/docs/front-matter/), useful in conjunction with [content as data](/docs/data/) or otherwise static configuration / metadata. - `getBody`: Effectively anything that you could put into a [``](/docs/layouts/#page-layouts). - `getLayout`: Effectively the same as a [page layout](/docs/layouts/#page-layouts). @@ -93,16 +91,14 @@ A couple of notes: #### Frontmatter -Any Greenwood supported frontmatter can be returned here. _This is only run once when the server is started_ to populate the graph, which is helpful if you want your dynamic route to show up in a menu like in your header for navigation. - -You can even define a `layout` and reuse all your existing [layouts](/docs/layouts/), even for server routes! +Any Greenwood supported frontmatter can be returned here. _This is only run once when the server is started_ to populate the graph, which is helpful if you want your dynamic route to show up in the metadata for your pages. You can even define a `layout` and reuse all your existing [layouts](/docs/layouts/), even for server routes! ```js export async function getFrontmatter(compilation, route) { return { layout: 'user', - menu: 'header', - index: 1, + collection: 'header', + order: 1, title: `${compilation.config.title} - ${route}`, imports: [ '/components/user.js' diff --git a/www/pages/getting-started/branding.md b/www/pages/getting-started/branding.md index d92f39aa4..a48763c26 100644 --- a/www/pages/getting-started/branding.md +++ b/www/pages/getting-started/branding.md @@ -1,9 +1,9 @@ --- -label: 'components-and-styles' -menu: side -title: 'Components and Styles' -index: 5 -linkheadings: 3 +collection: getting-started +title: Components and Styles +label: Components and Styles +order: 5 +tocHeading: 3 --- ## Component and Styles diff --git a/www/pages/getting-started/build-and-deploy.md b/www/pages/getting-started/build-and-deploy.md index 852821b34..6065dedbd 100644 --- a/www/pages/getting-started/build-and-deploy.md +++ b/www/pages/getting-started/build-and-deploy.md @@ -1,8 +1,7 @@ --- -label: 'build-and-deploy' -menu: side +collection: getting-started title: 'Build and Deploy' -index: 6 +order: 6 --- ## Build and Deploy diff --git a/www/pages/getting-started/creating-content.md b/www/pages/getting-started/creating-content.md index 45cb6f428..22a2cf850 100644 --- a/www/pages/getting-started/creating-content.md +++ b/www/pages/getting-started/creating-content.md @@ -1,9 +1,7 @@ --- -label: 'creating-content' -menu: side -title: 'Creating Content' -index: 4 -linkheadings: 3 +collection: getting-started +order: 4 +tocHeading: 3 --- ## Overview diff --git a/www/pages/getting-started/index.md b/www/pages/getting-started/index.md index 3aaff03f9..c44abbe04 100644 --- a/www/pages/getting-started/index.md +++ b/www/pages/getting-started/index.md @@ -1,8 +1,6 @@ --- -label: 'getting-started' -menu: navigation -title: 'Getting Started' -index: 3 +collection: navigation +order: 3 --- ## Introduction diff --git a/www/pages/getting-started/key-concepts.md b/www/pages/getting-started/key-concepts.md index 10107ffee..bbe1f252d 100644 --- a/www/pages/getting-started/key-concepts.md +++ b/www/pages/getting-started/key-concepts.md @@ -1,9 +1,7 @@ --- -label: 'key-concepts' -menu: side -title: 'Key Concepts' -index: 3 -linkheadings: 3 +collection: getting-started +order: 3 +tocHeading: 3 --- ## Overview diff --git a/www/pages/getting-started/next-steps.md b/www/pages/getting-started/next-steps.md index 4445e4aa8..506655a9c 100644 --- a/www/pages/getting-started/next-steps.md +++ b/www/pages/getting-started/next-steps.md @@ -1,8 +1,6 @@ --- -label: 'next-steps' -menu: side -title: 'Next Steps' -index: 8 +collection: getting-started +order: 8 --- ## Next Steps diff --git a/www/pages/getting-started/optimizing.md b/www/pages/getting-started/optimizing.md index d32b9423f..ee1047fff 100644 --- a/www/pages/getting-started/optimizing.md +++ b/www/pages/getting-started/optimizing.md @@ -1,9 +1,7 @@ --- -label: 'optimizing' -menu: side -title: 'Optimizing' -index: 7 -linkheadings: 3 +collection: getting-started +order: 7 +tocHeading: 3 --- ## Optimizing diff --git a/www/pages/getting-started/project-setup.md b/www/pages/getting-started/project-setup.md index 335f2a5a3..74d4e677f 100644 --- a/www/pages/getting-started/project-setup.md +++ b/www/pages/getting-started/project-setup.md @@ -1,9 +1,7 @@ --- -label: 'project-setup' -menu: side -title: 'Project Setup' -index: 2 -linkheadings: 3 +collection: getting-started +order: 2 +tocHeading: 3 --- ## Overview diff --git a/www/pages/getting-started/quick-start.md b/www/pages/getting-started/quick-start.md index 3b05ed66a..7266f9099 100644 --- a/www/pages/getting-started/quick-start.md +++ b/www/pages/getting-started/quick-start.md @@ -1,9 +1,7 @@ --- -label: 'quick-start' -menu: side -title: 'Quick Start' -index: 1 -linkheadings: 3 +collection: getting-started +order: 1 +tocHeading: 3 --- ## Quick Start diff --git a/www/pages/guides/cloudflare-workers-deployment.md b/www/pages/guides/cloudflare-workers-deployment.md index ca469a6e9..ba1072073 100644 --- a/www/pages/guides/cloudflare-workers-deployment.md +++ b/www/pages/guides/cloudflare-workers-deployment.md @@ -1,8 +1,7 @@ --- -title: 'Cloudflare Workers Deployment' -menu: side -linkheadings: 3 -index: 6 +collection: guides +tocHeading: 3 +order: 6 --- ## Cloudflare Workers Deployment diff --git a/www/pages/guides/firebase.md b/www/pages/guides/firebase.md index 2ab662032..6a19afd71 100644 --- a/www/pages/guides/firebase.md +++ b/www/pages/guides/firebase.md @@ -1,8 +1,8 @@ --- title: 'Firebase Deployment' -menu: side -linkheadings: 3 -index: 5 +collection: guides +tocHeading: 3 +order: 5 --- ### Deploy on Firebase diff --git a/www/pages/guides/github-pages.md b/www/pages/guides/github-pages.md index 5bb06df73..080776e50 100644 --- a/www/pages/guides/github-pages.md +++ b/www/pages/guides/github-pages.md @@ -1,8 +1,8 @@ --- title: 'Deploy with GitHub Pages' -menu: side -linkheadings: 3 -index: 2 +collection: guides +tocHeading: 3 +order: 2 --- ## Deploying your site with GitHub Pages diff --git a/www/pages/guides/index.md b/www/pages/guides/index.md index 8d67cb6d0..aae5407f3 100644 --- a/www/pages/guides/index.md +++ b/www/pages/guides/index.md @@ -1,8 +1,6 @@ --- -label: 'guides' -title: 'Guides' -menu: navigation -index: 5 +collection: navigation +order: 5 --- ## Guides diff --git a/www/pages/guides/netlify-cms.md b/www/pages/guides/netlify-cms.md index beaabd2b6..243b4d937 100644 --- a/www/pages/guides/netlify-cms.md +++ b/www/pages/guides/netlify-cms.md @@ -1,8 +1,8 @@ --- title: 'Netlify CMS' -menu: side -linkheadings: 3 -index: 8 +collection: guides +tocHeading: 3 +order: 8 --- ## Using a CMS diff --git a/www/pages/guides/netlify-deploy.md b/www/pages/guides/netlify-deploy.md index 077470868..bb59c8ad6 100644 --- a/www/pages/guides/netlify-deploy.md +++ b/www/pages/guides/netlify-deploy.md @@ -1,8 +1,8 @@ --- title: 'Deploy on Netlify' -menu: side -linkheadings: 3 -index: 1 +collection: guides +tocHeading: 3 +order: 1 --- ## Deploying your site on Netlify diff --git a/www/pages/guides/now.md b/www/pages/guides/now.md index f4783b971..2211623e1 100644 --- a/www/pages/guides/now.md +++ b/www/pages/guides/now.md @@ -1,8 +1,8 @@ --- title: 'Deploy on Now' -menu: side -linkheadings: 3 -index: 4 +collection: guides +tocHeading: 3 +order: 4 --- ## Deploying your site on Now diff --git a/www/pages/guides/s3-cloudfront.md b/www/pages/guides/s3-cloudfront.md index 36bdc2994..03b6c7a85 100644 --- a/www/pages/guides/s3-cloudfront.md +++ b/www/pages/guides/s3-cloudfront.md @@ -1,8 +1,8 @@ --- title: 'AWS S3 & CloudFront Deployment' -menu: side -linkheadings: 3 -index: 7 +collection: guides +tocHeading: 3 +order: 7 --- ## Deployment from AWS S3 & CloudFront diff --git a/www/pages/guides/theme-packs.md b/www/pages/guides/theme-packs.md index 7687f28dd..467454714 100644 --- a/www/pages/guides/theme-packs.md +++ b/www/pages/guides/theme-packs.md @@ -1,8 +1,8 @@ --- title: 'Creating a Theme Pack' -menu: side -linkheadings: 3 -index: 3 +collection: guides +tocHeading: 3 +order: 3 --- ## Creating a Theme Pack diff --git a/www/pages/plugins/adapter.md b/www/pages/plugins/adapter.md index ebd0b8a31..c68d66961 100644 --- a/www/pages/plugins/adapter.md +++ b/www/pages/plugins/adapter.md @@ -1,8 +1,6 @@ --- -label: 'adapter' -menu: side -title: 'Adapter' -index: 1 +collection: plugins +order: 1 --- ## Adapter @@ -75,7 +73,9 @@ import fs from 'fs/promises'; import { checkResourceExists } from '../../../../cli/src/lib/resource-utils.js'; function generateOutputFormat(id, type) { - const path = type === 'page' ? `/${id}.route` : id; + const path = type === 'page' + ? `/${id}.route` + : `/api/${id}`; const ref = id.replace(/-/g, '').replace(/\//g, ''); return ` @@ -102,18 +102,17 @@ async function genericAdapter(compilation) { } for (const page of ssrPages) { - const { outputPath } = page; - const id = outputPath.replace('.route.js', ''); + const { id } = page; const outputFormat = generateOutputFormat(id, 'page'); await fs.writeFile(new URL(`./${id}.js`, adapterOutputUrl), outputFormat); } for (const [key] of apiRoutes) { - const { outputPath } = apiRoutes.get(key); - const outputFormat = generateOutputFormat(outputPath.replace('.js', ''), 'api'); + const { id } = apiRoutes.get(key); + const outputFormat = generateOutputFormat(id, 'api'); - await fs.writeFile(new URL(`.${outputPath.replace('/api/', '/api-')}`, adapterOutputUrl), outputFormat); + await fs.writeFile(new URL(`./api-${id}.js`, adapterOutputUrl), outputFormat); } } diff --git a/www/pages/plugins/context.md b/www/pages/plugins/context.md index a995c2aa8..fe5818874 100644 --- a/www/pages/plugins/context.md +++ b/www/pages/plugins/context.md @@ -1,8 +1,6 @@ --- -label: 'context' -menu: side -title: 'Context' -index: 2 +collection: plugins +order: 2 --- ## Context diff --git a/www/pages/plugins/copy.md b/www/pages/plugins/copy.md index a2c904776..7de52dcdd 100644 --- a/www/pages/plugins/copy.md +++ b/www/pages/plugins/copy.md @@ -1,8 +1,6 @@ --- -label: 'copy' -menu: side -title: 'Copy' -index: 3 +collection: plugins +order: 3 --- ## Copy diff --git a/www/pages/plugins/custom-plugins.md b/www/pages/plugins/custom-plugins.md index 8be242786..ba546b8f2 100644 --- a/www/pages/plugins/custom-plugins.md +++ b/www/pages/plugins/custom-plugins.md @@ -1,8 +1,6 @@ --- -label: 'custom-plugins' -menu: side -title: 'Custom Plugins' -index: 9 +collection: plugins +order: 9 --- ## Custom Plugins diff --git a/www/pages/plugins/index.md b/www/pages/plugins/index.md index a9500ca43..8c28bc492 100644 --- a/www/pages/plugins/index.md +++ b/www/pages/plugins/index.md @@ -1,8 +1,6 @@ --- -label: 'plugins' -menu: navigation -title: Plugins -index: 4 +collection: navigation +order: 4 --- ## Plugins diff --git a/www/pages/plugins/renderer.md b/www/pages/plugins/renderer.md index 4b7f9825b..2d8d85e2c 100644 --- a/www/pages/plugins/renderer.md +++ b/www/pages/plugins/renderer.md @@ -1,8 +1,6 @@ --- -label: 'Renderer' -menu: side -title: 'Renderer' -index: 5 +collection: plugins +order: 5 --- ## Renderer diff --git a/www/pages/plugins/resource.md b/www/pages/plugins/resource.md index fa671a772..21a06fc19 100644 --- a/www/pages/plugins/resource.md +++ b/www/pages/plugins/resource.md @@ -1,8 +1,6 @@ --- -label: 'Resource' -menu: side -title: 'Resource' -index: 4 +collection: plugins +order: 4 --- ## Resource diff --git a/www/pages/plugins/rollup.md b/www/pages/plugins/rollup.md index cc16e4465..9b770671f 100644 --- a/www/pages/plugins/rollup.md +++ b/www/pages/plugins/rollup.md @@ -1,8 +1,6 @@ --- -label: 'Rollup' -menu: side -title: 'Rollup' -index: 6 +collection: plugins +order: 6 --- ## Rollup diff --git a/www/pages/plugins/server.md b/www/pages/plugins/server.md index 80730a885..98edfcfa8 100644 --- a/www/pages/plugins/server.md +++ b/www/pages/plugins/server.md @@ -1,8 +1,6 @@ --- -label: 'Server' -menu: side -title: 'Server' -index: 7 +collection: plugins +order: 7 --- ## Server diff --git a/www/pages/plugins/source.md b/www/pages/plugins/source.md index b5aa03ec5..d0abefe99 100644 --- a/www/pages/plugins/source.md +++ b/www/pages/plugins/source.md @@ -1,8 +1,6 @@ --- -label: 'source' -menu: side -title: 'Source' -index: 8 +collection: plugins +order: 8 --- ## Source