@nutin/cli 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +268 -0
- package/README.md +13 -5
- package/bin/add-feature.mjs +1 -1
- package/bin/cli.mjs +1 -1
- package/bin/create-app.mjs +1 -1
- package/bin/update-app.mjs +1 -1
- package/lib/index.mjs +6 -2
- package/lib/src/add-feature/base-template-patcher.mjs +139 -0
- package/lib/src/add-feature/feature-adder.mjs +20 -9
- package/lib/src/add-feature/feature-context-builder.mjs +1 -0
- package/lib/src/add-feature/nutin-config-updater.mjs +34 -8
- package/lib/src/add-feature/package-json-updater.mjs +7 -9
- package/lib/src/common/feature-registry.mjs +4 -0
- package/lib/src/common/file-generator.mjs +25 -3
- package/lib/src/common/git.mjs +31 -0
- package/lib/src/common/meta-bootstrap-prompt.mjs +28 -2
- package/lib/src/common/package-json-helper.mjs +14 -4
- package/lib/src/common/project-meta.mjs +2 -2
- package/lib/src/common/template-compiler.mjs +2 -0
- package/lib/src/common/transpile-ts.mjs +94 -0
- package/lib/src/create-app/context-builder.mjs +1 -0
- package/lib/src/create-app/git-manager.mjs +12 -2
- package/lib/src/create-app/json-manager.mjs +21 -18
- package/lib/src/create-app/new-app-prompt.mjs +4 -2
- package/lib/src/create-app/success-message.mjs +1 -1
- package/lib/src/update-app/conflict-report-writer.mjs +33 -21
- package/lib/src/update-app/json-drift.mjs +54 -0
- package/lib/src/update-app/project-updater.mjs +123 -14
- package/lib/src/update-app/template-differ.mjs +5 -5
- package/lib/src/update-app/three-way-merge.mjs +42 -0
- package/lib/src/update-app/update-context-builder.mjs +1 -0
- package/lib/src/update-app/update-summary-printer.mjs +10 -2
- package/package.json +9 -8
- package/templates/base/AGENTS.md.hbs +42 -41
- package/templates/base/GETTING_STARTED.md.hbs +26 -20
- package/templates/base/README.md.hbs +7 -4
- package/templates/base/src/app/globals.d.ts.hbs +6 -3
- package/templates/base/src/app/guards.ts.hbs +1 -3
- package/templates/base/src/app/main.ts.hbs +2 -9
- package/templates/base/src/app/routes.ts.hbs +7 -2
- package/templates/base/src/app/views/home/home.view.html.hbs +1 -1
- package/templates/base/src/app/views/home/home.view.ts.hbs +2 -2
- package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +2 -2
- package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +461 -1
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +39 -48
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +36 -1
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +47 -77
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +286 -24
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +144 -26
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +66 -4
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +135 -75
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +85 -4
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +93 -54
- package/templates/base/src/core/base-classes/base-component/helpers/html.helper.test.js.hbs +404 -0
- package/templates/base/src/core/base-classes/base-component/helpers/html.helper.ts.hbs +242 -0
- package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +70 -1
- package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +54 -15
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +12 -1
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.ts.hbs +39 -36
- package/templates/base/src/core/base-classes/base-component/helpers/preservation.helper.test.js.hbs +179 -0
- package/templates/base/src/core/base-classes/base-component/helpers/preservation.helper.ts.hbs +93 -0
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +135 -17
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +199 -81
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +40 -23
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.ts.hbs +67 -65
- package/templates/base/src/core/base-classes/component/component.test.js.hbs +17 -17
- package/templates/base/src/core/base-classes/component/component.ts.hbs +8 -41
- package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +1 -1
- package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +37 -39
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +14 -1
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.ts.hbs +33 -31
- package/templates/base/src/core/base-classes/service/service.test.js.hbs +32 -0
- package/templates/base/src/core/base-classes/service/service.ts.hbs +21 -13
- package/templates/base/src/core/base-classes/view/view.test.js.hbs +4 -0
- package/templates/base/src/core/base-classes/view/view.ts.hbs +11 -8
- package/templates/base/src/core/index.ts.hbs +4 -2
- package/templates/base/src/core/internals.d.ts.hbs +199 -20
- package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +18 -0
- package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +26 -25
- package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +3 -3
- package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +3 -3
- package/templates/base/src/core/services/globals/globals.ts.hbs +7 -32
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +133 -50
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.ts.hbs +147 -39
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +75 -19
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.ts.hbs +73 -47
- package/templates/base/src/core/services/http-client/http-client.test.js.hbs +339 -232
- package/templates/base/src/core/services/http-client/http-client.ts.hbs +37 -25
- package/templates/base/src/core/services/i18n/i18n.test.js.hbs +57 -16
- package/templates/base/src/core/services/i18n/i18n.ts.hbs +42 -42
- package/templates/base/src/core/services/i18n/languages.ts.hbs +0 -2
- package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +4 -6
- package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +10 -0
- package/templates/base/src/core/services/pipe-registry/pipes.ts.hbs +4 -2
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +183 -4
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +207 -84
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +26 -27
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.ts.hbs +38 -60
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +4 -4
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +30 -30
- package/templates/base/src/core/services/router/router.test.js.hbs +306 -0
- package/templates/base/src/core/services/router/router.ts.hbs +113 -53
- package/templates/base/tools/builder/builder.config.js.hbs +6 -0
- package/templates/base/tools/builder/builder.js.hbs +18 -6
- package/templates/base/tools/builder/core/app/compile-ts.js.hbs +81 -6
- package/templates/base/tools/builder/core/app/copy-static.js.hbs +12 -1
- package/templates/base/tools/builder/core/app/handler-refs.js.hbs +114 -0
- package/templates/base/tools/builder/core/app/handler-refs.test.js.hbs +70 -0
- package/templates/base/tools/builder/core/app/paths.js.hbs +17 -0
- package/templates/base/tools/builder/core/app/route-keys.js.hbs +47 -0
- package/templates/base/tools/builder/core/app/route-keys.test.js.hbs +97 -0
- package/templates/base/tools/builder/core/app/ts-errors.js.hbs +58 -0
- package/templates/base/tools/builder/core/app/ts-errors.test.js.hbs +84 -0
- package/templates/base/tools/builder/core/app/validate-routes.js.hbs +21 -55
- package/templates/base/tools/builder/core/finalize-build.js.hbs +6 -0
- package/templates/base/tools/builder/core/html-index/validate-html.js.hbs +10 -2
- package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +111 -23
- package/templates/base/tools/builder/core/html-templates/minify-templates.js.hbs +8 -0
- package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +5 -3
- package/templates/base/tools/builder/core/html-templates/template-literal.js.hbs +193 -0
- package/templates/base/tools/builder/core/html-templates/template-literal.test.js.hbs +69 -0
- package/templates/base/tools/builder/core/i18n/build-i18n.js.hbs +14 -6
- package/templates/base/tools/builder/core/prod-bundle/bundle-options.js.hbs +82 -0
- package/templates/base/tools/builder/core/prod-bundle/bundle-options.test.js.hbs +65 -0
- package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +6 -6
- package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +15 -7
- package/templates/base/tools/builder/core/prod-bundle/hash-files.js.hbs +4 -4
- package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +31 -13
- package/templates/base/tools/builder/core/seo/generate-robots-txt.test.js.hbs +48 -0
- package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +3 -3
- package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +43 -13
- package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +22 -7
- package/templates/base/tools/builder/core/seo/generate-sitemap-xml.test.js.hbs +30 -0
- package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +28 -21
- package/templates/base/tools/builder/core/seo/html-substitutions.test.js.hbs +45 -0
- package/templates/base/tools/builder/core/seo/route-output.js.hbs +24 -6
- package/templates/base/tools/builder/core/seo/route-output.test.js.hbs +31 -0
- package/templates/base/tools/builder/core/seo/seo-routes.js.hbs +29 -0
- package/templates/base/tools/builder/core/seo/seo-routes.test.js.hbs +21 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +6 -7
- package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +34 -5
- package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.test.js.hbs +48 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +24 -11
- package/templates/base/tools/builder/core/styles/sass.js.hbs +3 -2
- package/templates/base/tools/builder/core/styles/tailwind.js.hbs +19 -59
- package/templates/base/tools/dev/dev-serve.js.hbs +30 -5
- package/templates/base/tools/dev/serve.js.hbs +152 -22
- package/templates/base/tools/dev/watcher.js.hbs +34 -3
- package/templates/base/tools/generator/generator.js.hbs +1 -1
- package/templates/base/tools/generator/handle-file.js.hbs +4 -2
- package/templates/base/tools/generator/handle-file.test.js.hbs +3 -3
- package/templates/base/tools/generator/templates/component/component.template.js.hbs +3 -3
- package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +3 -3
- package/templates/base/tools/generator/templates/service/service.template.js.hbs +1 -1
- package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +1 -1
- package/templates/base/tools/generator/templates/view/view.template.js.hbs +2 -2
- package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +2 -2
- package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +1 -1
- package/templates/base/tools/testin-nutin/core/globals/assertion-lib.js.hbs +2 -2
- package/templates/base/tools/testin-nutin/core/{tests/assertions.test.js.hbs → globals/assertion-lib.test.js.hbs} +6 -0
- package/templates/base/tools/testin-nutin/core/globals/register-test-globals.js.hbs +6 -7
- package/templates/base/tools/testin-nutin/core/{tests/globals-each-all.test.js.hbs → globals/register-test-globals.test.js.hbs} +12 -0
- package/templates/base/tools/testin-nutin/core/printer.js.hbs +10 -4
- package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +8 -8
- package/templates/base/tools/testin-nutin/mocks/mock-event-bus.js.hbs +27 -25
- package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +30 -22
- package/templates/base/tools/testin-nutin/mocks/mock-http-client.js.hbs +7 -10
- package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +12 -0
- package/templates/base/tools/testin-nutin/mocks/mock-i18n.js.hbs +47 -22
- package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +47 -26
- package/templates/base/tools/testin-nutin/mocks/mock-router.js.hbs +19 -11
- package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +24 -7
- package/templates/base/tools/testin-nutin/runner.js.hbs +12 -3
- package/templates/base/tools/testin-nutin/watch-tests.js.hbs +45 -20
- package/templates/base/tools/utils/build-lock.js.hbs +30 -2
- package/templates/base/tools/utils/ensure-deps.js.hbs +72 -0
- package/templates/base/tools/utils/ensure-deps.test.js.hbs +17 -0
- package/templates/base/tools/utils/format.js.hbs +1 -1
- package/templates/base/tools/utils/index.js.hbs +2 -1
- package/templates/base/tools/utils/print.js.hbs +2 -2
- package/templates/base/tools/utils/resolve-port.js.hbs +30 -0
- package/templates/base/tools/utils/resolve-port.test.js.hbs +47 -0
- package/templates/base/tools/utils/run-command.js.hbs +30 -1
- package/templates/base/tools/utils/run-command.test.js.hbs +12 -1
- package/templates/base/tools/utils/run-script.js.hbs +2 -2
- package/templates/features/docker/tools/docker/Dockerfile.template.hbs +3 -0
- package/templates/features/docker/tools/docker/nginx.conf.template +6 -4
- package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +1 -1
- package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +2 -2
- package/templates/features/docker/tools/docker/scripts/locale-redirects.js.hbs +28 -0
- package/templates/features/docker/tools/docker/scripts/locale-redirects.test.js.hbs +34 -0
- package/templates/features/docker/tools/docker/scripts/markdown-sources-copy.js.hbs +25 -0
- package/templates/features/docker/tools/docker/scripts/markdown-sources-copy.test.js.hbs +34 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +3 -1
- package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +12 -0
- package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +21 -4
- package/templates/features/markdown/markdown-content/getting-started.md.hbs +28 -0
- package/templates/features/markdown/markdown-content/writing-pages.md.hbs +19 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.scss.hbs +75 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.test.js.hbs +40 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-content/markdown-content.component.ts.hbs +65 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-landing/markdown-landing.component.test.js.hbs +67 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-landing/markdown-landing.component.ts.hbs +87 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.scss.hbs +42 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.test.js.hbs +59 -0
- package/templates/features/markdown/src/app/markdown/components/markdown-nav/markdown-nav.component.ts.hbs +72 -0
- package/templates/features/markdown/src/app/markdown/locales/en.json.hbs +6 -0
- package/templates/features/markdown/src/app/markdown/markdown-guards.test.js.hbs +63 -0
- package/templates/features/markdown/src/app/markdown/markdown-guards.ts.hbs +31 -0
- package/templates/features/markdown/src/app/markdown/markdown-i18n.test.js.hbs +78 -0
- package/templates/features/markdown/src/app/markdown/markdown-i18n.ts.hbs +31 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifest.test.js.hbs +98 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifest.ts.hbs +157 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifests.service.test.js.hbs +87 -0
- package/templates/features/markdown/src/app/markdown/markdown-manifests.service.ts.hbs +50 -0
- package/templates/features/markdown/src/app/markdown/markdown-routes.test.js.hbs +74 -0
- package/templates/features/markdown/src/app/markdown/markdown-routes.ts.hbs +26 -0
- package/templates/features/markdown/src/app/markdown/markdown-sources.test.js.hbs +22 -0
- package/templates/features/markdown/src/app/markdown/markdown-sources.ts.hbs +42 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.html.hbs +4 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.scss.hbs +19 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.test.js.hbs +125 -0
- package/templates/features/markdown/src/app/markdown/view/markdown.view.ts.hbs +146 -0
- package/templates/features/markdown/tools/builder/core/markdown/generate-markdown-pages.js.hbs +71 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-compiler.js.hbs +582 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-compiler.test.js.hbs +390 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-seo-routes.js.hbs +155 -0
- package/templates/features/markdown/tools/builder/core/markdown/markdown-seo-routes.test.js.hbs +112 -0
- /package/templates/base/tools/testin-nutin/core/{tests → globals}/clock.test.js.hbs +0 -0
|
@@ -4,6 +4,7 @@ const SRC_FOLDERNAME = 'src';
|
|
|
4
4
|
const APP_FOLDERNAME = 'app';
|
|
5
5
|
const TMP_FOLDERNAME = 'dist-build';
|
|
6
6
|
const BLD_FOLDERNAME = 'dist';
|
|
7
|
+
const CHUNKS_FOLDERNAME = 'chunks';
|
|
7
8
|
|
|
8
9
|
const BASE_PATHS = {
|
|
9
10
|
source: path.resolve(SRC_FOLDERNAME),
|
|
@@ -16,4 +17,20 @@ export const PATHS = {
|
|
|
16
17
|
sourceApp: path.join(BASE_PATHS.source, APP_FOLDERNAME),
|
|
17
18
|
tempSource: path.join(BASE_PATHS.temp, SRC_FOLDERNAME),
|
|
18
19
|
tempApp: path.join(BASE_PATHS.temp, SRC_FOLDERNAME, APP_FOLDERNAME),
|
|
20
|
+
tempChunks: path.join(BASE_PATHS.temp, SRC_FOLDERNAME, CHUNKS_FOLDERNAME),
|
|
19
21
|
};
|
|
22
|
+
|
|
23
|
+
// Copied sources that only feed the bundles — finalize-build.js removes them in prod,
|
|
24
|
+
// so hashing/compressing them is wasted work (and renaming them breaks the SSR bundle).
|
|
25
|
+
const BUNDLED_SOURCE_DIRS = [PATHS.tempApp, path.join(PATHS.tempSource, 'core')];
|
|
26
|
+
|
|
27
|
+
export function isBundledSource(file) {
|
|
28
|
+
return BUNDLED_SOURCE_DIRS.some((dir) => file.startsWith(dir + path.sep));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// esbuild's code-split chunks (dynamic import()s and the code they share). Already
|
|
32
|
+
// content-hashed by esbuild, and imported by name from bundle.js and from each other,
|
|
33
|
+
// so hash-files.js must not rename them.
|
|
34
|
+
export function isChunk(file) {
|
|
35
|
+
return file.startsWith(PATHS.tempChunks + path.sep);
|
|
36
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import esbuild from 'esbuild';
|
|
4
|
+
import { getFilesRecursive } from '../../../utils/index.js';
|
|
5
|
+
|
|
6
|
+
const DUPLICATE_KEY_TEXT = /^Duplicate key "(.*)" in object literal$/;
|
|
7
|
+
|
|
8
|
+
// esbuild warns on every duplicate key of an object literal — appRoutes' included — and
|
|
9
|
+
// treats '/x', "/x" and ['/x'] as the same key. Scope is the whole file, not only appRoutes.
|
|
10
|
+
// Rejects (with esbuild's own message) when the source doesn't parse.
|
|
11
|
+
export async function findDuplicateRouteKeys(source, { loader, sourcefile }) {
|
|
12
|
+
const { warnings } = await esbuild.transform(source, { loader, sourcefile });
|
|
13
|
+
const duplicates = new Map();
|
|
14
|
+
|
|
15
|
+
for (const warning of warnings) {
|
|
16
|
+
if (warning.id !== 'duplicate-object-key') continue;
|
|
17
|
+
const key = warning.text.match(DUPLICATE_KEY_TEXT)?.[1] ?? warning.text;
|
|
18
|
+
if (!duplicates.has(key)) duplicates.set(key, warning.location?.line);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return [...duplicates].map(([key, line]) => ({ key, line }));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Static, bare and dynamic imports: from '…', import '…', import('…').
|
|
25
|
+
const IMPORT_SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(?\s*)(['"`])([^'"`]+)\1/g;
|
|
26
|
+
const CODE_EXTENSION = /\.(ts|js)$/;
|
|
27
|
+
|
|
28
|
+
// The app files that import the routes module. Without one, the app doesn't use the router
|
|
29
|
+
// (e.g. components mounted into a page), so routes.ts and #app aren't required.
|
|
30
|
+
// files: [{ path, content }], absolute paths; routesFile: the routes module's absolute path.
|
|
31
|
+
export function findRoutesImporters(files, routesFile) {
|
|
32
|
+
const routesModule = routesFile.replace(CODE_EXTENSION, '');
|
|
33
|
+
return files
|
|
34
|
+
.filter(({ path: file, content }) => file !== routesFile && [...content.matchAll(IMPORT_SPECIFIER)].some(
|
|
35
|
+
([, , specifier]) => specifier.startsWith('.') &&
|
|
36
|
+
path.resolve(path.dirname(file), specifier).replace(CODE_EXTENSION, '') === routesModule
|
|
37
|
+
))
|
|
38
|
+
.map(({ path: file }) => file);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// findRoutesImporters() over the app's code files (tests and .d.ts left out).
|
|
42
|
+
export function readRoutesImporters(appDir, routesFile) {
|
|
43
|
+
const files = getFilesRecursive(appDir, ['.ts', '.js'])
|
|
44
|
+
.filter((file) => !/\.test\.(ts|js)$/.test(file) && !file.endsWith('.d.ts'))
|
|
45
|
+
.map((file) => ({ path: file, content: fs.readFileSync(file, 'utf-8') }));
|
|
46
|
+
return findRoutesImporters(files, routesFile);
|
|
47
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { findDuplicateRouteKeys, findRoutesImporters } from './route-keys.js';
|
|
3
|
+
|
|
4
|
+
const TS = { loader: 'ts', sourcefile: 'routes.ts' };
|
|
5
|
+
|
|
6
|
+
describe('findDuplicateRouteKeys', () => {
|
|
7
|
+
it('returns nothing for distinct keys, TypeScript syntax included', async () => {
|
|
8
|
+
const source = `
|
|
9
|
+
export const appRoutes: Nutin.Routes = {
|
|
10
|
+
'/': () => new HomeView(),
|
|
11
|
+
'/about': () => new AboutView() as any,
|
|
12
|
+
'/users/:id': () => new UserView(),
|
|
13
|
+
};`;
|
|
14
|
+
|
|
15
|
+
expect(await findDuplicateRouteKeys(source, TS)).toEqual([]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('reports a duplicate across quote styles and computed keys, with its line', async () => {
|
|
19
|
+
const source = [
|
|
20
|
+
'export const appRoutes: Nutin.Routes = {',
|
|
21
|
+
" '/': () => new HomeView(),",
|
|
22
|
+
' "/": () => new OtherView(),',
|
|
23
|
+
" ['/c']: () => new C(),",
|
|
24
|
+
" ['/c']: () => new D(),",
|
|
25
|
+
'};',
|
|
26
|
+
].join('\n');
|
|
27
|
+
|
|
28
|
+
expect(await findDuplicateRouteKeys(source, TS)).toEqual([
|
|
29
|
+
{ key: '/', line: 3 },
|
|
30
|
+
{ key: '/c', line: 5 },
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('reports a duplicated :param route', async () => {
|
|
35
|
+
const source = `export const appRoutes = {
|
|
36
|
+
'/users/:id': () => new A(),
|
|
37
|
+
'/users/:id': () => new B(),
|
|
38
|
+
};`;
|
|
39
|
+
|
|
40
|
+
expect((await findDuplicateRouteKeys(source, { loader: 'js', sourcefile: 'routes.js' })).map((d) => d.key)).toEqual(['/users/:id']);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('does not flag the view/guards keys shared by several guarded routes', async () => {
|
|
44
|
+
const source = `export const appRoutes: Nutin.Routes = {
|
|
45
|
+
'/admin': { view: () => new AdminView(), guards: [() => true] },
|
|
46
|
+
'/settings': { view: () => new SettingsView(), guards: [() => '/'] },
|
|
47
|
+
};`;
|
|
48
|
+
|
|
49
|
+
expect(await findDuplicateRouteKeys(source, TS)).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('flags a plain and a guarded route sharing a path', async () => {
|
|
53
|
+
const source = `export const appRoutes: Nutin.Routes = {
|
|
54
|
+
'/admin': () => new AdminView(),
|
|
55
|
+
'/admin': { view: () => new AdminView(), guards: [() => true] },
|
|
56
|
+
};`;
|
|
57
|
+
|
|
58
|
+
expect((await findDuplicateRouteKeys(source, TS)).map((d) => d.key)).toEqual(['/admin']);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('checks the whole file, not only appRoutes', async () => {
|
|
62
|
+
const source = `const other = { a: 1, a: 2 };
|
|
63
|
+
export const appRoutes: Nutin.Routes = { '/': () => new HomeView() };`;
|
|
64
|
+
|
|
65
|
+
expect(await findDuplicateRouteKeys(source, TS)).toEqual([{ key: 'a', line: 1 }]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('rejects when the source does not parse', async () => {
|
|
69
|
+
await expect(() => findDuplicateRouteKeys('export const appRoutes = {', TS)).toThrow();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('findRoutesImporters', () => {
|
|
74
|
+
const APP = path.resolve('/project/src/app');
|
|
75
|
+
const ROUTES = path.join(APP, 'routes.ts');
|
|
76
|
+
const file = (rel, content) => ({ path: path.join(APP, rel), content });
|
|
77
|
+
|
|
78
|
+
it('finds static, bare and dynamic imports of the routes module, from any folder', () => {
|
|
79
|
+
const files = [
|
|
80
|
+
file('main.ts', "import { appRoutes } from './routes.js';"),
|
|
81
|
+
file('setup.ts', "import './routes.js';"),
|
|
82
|
+
file('lazy/boot.ts', "const r = await import('../routes.js');"),
|
|
83
|
+
];
|
|
84
|
+
expect(findRoutesImporters(files, ROUTES)).toEqual(files.map((f) => f.path));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('ignores other modules named routes, packages, and the routes file itself', () => {
|
|
88
|
+
const files = [
|
|
89
|
+
file('main.ts', "import { CounterComponent } from './components/counter/counter.component.js';"),
|
|
90
|
+
file('admin/main.ts', "import { adminRoutes } from './routes.js';"),
|
|
91
|
+
file('x.ts', "import routes from 'routes.js';"),
|
|
92
|
+
file('routes.ts', "import { markdownRoutes } from './markdown/markdown-routes.js';"),
|
|
93
|
+
];
|
|
94
|
+
expect(findRoutesImporters(files, ROUTES)).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
// tsc reports errors in dist-build's merged copies. This maps each `file(line,col): …` line
|
|
4
|
+
// back to the project's own file: the .html template when the position falls inside a merged
|
|
5
|
+
// template, the source .ts otherwise (its lines after the template shifted back up).
|
|
6
|
+
// templateMap: { 'src/…/x.component.ts': { html, line, col, lines } } (see merge-templates)
|
|
7
|
+
// handlerRefs: { 'src/…/x.component.ts': { className, line, col, segment, handlers } } (see handler-refs):
|
|
8
|
+
// an error in the injected handler references is reported where the handler is named in the template.
|
|
9
|
+
const LOCATION = /^(.+?)\((\d+),(\d+)\)(: .*)$/;
|
|
10
|
+
|
|
11
|
+
export function remapTscOutput(output, { cwd, tempDir, templateMap, handlerRefs = {} }) {
|
|
12
|
+
return output
|
|
13
|
+
.split('\n')
|
|
14
|
+
.map((line) => {
|
|
15
|
+
const match = LOCATION.exec(line);
|
|
16
|
+
if (!match) return line;
|
|
17
|
+
const [, file, lineNo, colNo, rest] = match;
|
|
18
|
+
const absolute = path.resolve(cwd, file);
|
|
19
|
+
if (!absolute.startsWith(tempDir + path.sep)) return line;
|
|
20
|
+
|
|
21
|
+
const relPath = path.relative(tempDir, absolute);
|
|
22
|
+
const handlerRef = mapHandlerRef(handlerRefs[toPosix(relPath)], Number(lineNo), Number(colNo));
|
|
23
|
+
const { file: mappedFile, line: mappedLine, col: mappedCol } = mapPosition(relPath, handlerRef.line, handlerRef.col, templateMap);
|
|
24
|
+
// A private handler declared in a parent class: TS2341 names the parent; say what to do.
|
|
25
|
+
const hint = handlerRef.note && rest.includes('TS2341') ? ' Declare it protected to use it in a subclass template.' : '';
|
|
26
|
+
return `${toPosix(mappedFile)}(${mappedLine},${mappedCol})${rest}${handlerRef.note}${hint}`;
|
|
27
|
+
})
|
|
28
|
+
.join('\n');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function mapPosition(relPath, line, col, templateMap) {
|
|
32
|
+
const entry = templateMap[toPosix(relPath)] ?? templateMap[relPath];
|
|
33
|
+
if (!entry || line < entry.line) return { file: relPath, line, col };
|
|
34
|
+
|
|
35
|
+
const lastTemplateLine = entry.line + entry.lines - 1;
|
|
36
|
+
if (line > lastTemplateLine) return { file: relPath, line: line - (entry.lines - 1), col };
|
|
37
|
+
|
|
38
|
+
// Inside the template: its first line starts where the placeholder was.
|
|
39
|
+
const htmlLine = line - entry.line + 1;
|
|
40
|
+
return { file: entry.html, line: htmlLine, col: htmlLine === 1 ? col - entry.col + 1 : col };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// A position on a line where handler references were injected: inside them, the handler's place
|
|
44
|
+
// in the template (or the class, for an error about the injection itself); after them, shifted back.
|
|
45
|
+
export function mapHandlerRef(entry, line, col) {
|
|
46
|
+
if (!entry || line !== entry.line || col < entry.col) return { line, col, note: '' };
|
|
47
|
+
if (col >= entry.col + entry.segment.length) return { line, col: col - entry.segment.length, note: '' };
|
|
48
|
+
|
|
49
|
+
const handler = entry.handlers.find((h) => col >= h.col && col < h.col + h.name.length);
|
|
50
|
+
if (!handler) return { line, col: Math.max(entry.col - 1, 1), note: ` (data-event handlers of ${entry.className})` };
|
|
51
|
+
return {
|
|
52
|
+
line: handler.templateLine,
|
|
53
|
+
col: handler.templateCol,
|
|
54
|
+
note: ` (data-event handler "${handler.name}" of ${entry.className})`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const toPosix = (p) => p.split(path.sep).join('/');
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { remapTscOutput, mapPosition, mapHandlerRef } from './ts-errors.js';
|
|
3
|
+
|
|
4
|
+
// A view whose placeholder sat on line 4, column 33, replaced by a 3-line .html template.
|
|
5
|
+
const MAP = {
|
|
6
|
+
'src/app/views/home/home.view.ts': { html: 'src/app/views/home/home.view.html', line: 4, col: 33, lines: 3 },
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
describe('ts-errors', () => {
|
|
10
|
+
it('maps a position before the template to the same line of the source file', () => {
|
|
11
|
+
expect(mapPosition('src/app/views/home/home.view.ts', 2, 5, MAP)).toEqual({ file: 'src/app/views/home/home.view.ts', line: 2, col: 5 });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('maps a position inside the template to the .html file, the first line\'s column shifted', () => {
|
|
15
|
+
expect(mapPosition('src/app/views/home/home.view.ts', 4, 40, MAP)).toEqual({ file: 'src/app/views/home/home.view.html', line: 1, col: 8 });
|
|
16
|
+
expect(mapPosition('src/app/views/home/home.view.ts', 6, 12, MAP)).toEqual({ file: 'src/app/views/home/home.view.html', line: 3, col: 12 });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('shifts positions after the template back up by the lines it added', () => {
|
|
20
|
+
expect(mapPosition('src/app/views/home/home.view.ts', 9, 3, MAP)).toEqual({ file: 'src/app/views/home/home.view.ts', line: 7, col: 3 });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('leaves files without a merged template as they are', () => {
|
|
24
|
+
expect(mapPosition('src/core/index.ts', 9, 3, MAP)).toEqual({ file: 'src/core/index.ts', line: 9, col: 3 });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('rewrites tsc lines from dist-build to the project\'s files and leaves other lines alone', () => {
|
|
28
|
+
const cwd = path.resolve('/project');
|
|
29
|
+
const tempDir = path.join(cwd, 'dist-build');
|
|
30
|
+
const output = [
|
|
31
|
+
"dist-build/src/app/views/home/home.view.ts(5,9): error TS2339: Property 'nmae' does not exist on type 'ITask'.",
|
|
32
|
+
'dist-build/src/core/index.ts(1,1): error TS1000: Something.',
|
|
33
|
+
' continued message line',
|
|
34
|
+
"node_modules/x/index.d.ts(3,1): error TS2000: Elsewhere.",
|
|
35
|
+
].join('\n');
|
|
36
|
+
|
|
37
|
+
expect(remapTscOutput(output, { cwd, tempDir, templateMap: MAP }).split('\n')).toEqual([
|
|
38
|
+
"src/app/views/home/home.view.html(2,9): error TS2339: Property 'nmae' does not exist on type 'ITask'.",
|
|
39
|
+
'src/core/index.ts(1,1): error TS1000: Something.',
|
|
40
|
+
' continued message line',
|
|
41
|
+
"node_modules/x/index.d.ts(3,1): error TS2000: Elsewhere.",
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Handler references injected on line 9 from column 31 (see handler-refs.js): "_svae" sits at
|
|
46
|
+
// column 80 of that line, and is written on line 5, column 26 of the merged template.
|
|
47
|
+
const SEGMENT = ' static { void ((c: InstanceType<typeof HomeView>) => [c._svae]); }';
|
|
48
|
+
const REFS = {
|
|
49
|
+
'src/app/views/home/home.view.ts': {
|
|
50
|
+
className: 'HomeView', line: 9, col: 31, segment: SEGMENT,
|
|
51
|
+
handlers: [{ name: '_svae', col: 31 + SEGMENT.indexOf('_svae'), templateLine: 5, templateCol: 26 }],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
it('maps an error on an injected handler reference to where the template names it', () => {
|
|
56
|
+
const entry = REFS['src/app/views/home/home.view.ts'];
|
|
57
|
+
const col = entry.handlers[0].col;
|
|
58
|
+
expect(mapHandlerRef(entry, 9, col)).toEqual({ line: 5, col: 26, note: ' (data-event handler "_svae" of HomeView)' });
|
|
59
|
+
expect(mapHandlerRef(entry, 9, col + 4).line).toBe(5);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('shifts positions after the injected references back, and leaves other lines alone', () => {
|
|
63
|
+
const entry = REFS['src/app/views/home/home.view.ts'];
|
|
64
|
+
expect(mapHandlerRef(entry, 9, 31 + SEGMENT.length + 2)).toEqual({ line: 9, col: 33, note: '' });
|
|
65
|
+
expect(mapHandlerRef(entry, 9, 12)).toEqual({ line: 9, col: 12, note: '' });
|
|
66
|
+
expect(mapHandlerRef(entry, 10, 40)).toEqual({ line: 10, col: 40, note: '' });
|
|
67
|
+
expect(mapHandlerRef(undefined, 9, 40)).toEqual({ line: 9, col: 40, note: '' });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('reports a handler error at the .html line, with a hint for a parent\'s private handler', () => {
|
|
71
|
+
const cwd = path.resolve('/project');
|
|
72
|
+
const tempDir = path.join(cwd, 'dist-build');
|
|
73
|
+
const col = REFS['src/app/views/home/home.view.ts'].handlers[0].col;
|
|
74
|
+
const output = [
|
|
75
|
+
`dist-build/src/app/views/home/home.view.ts(9,${col}): error TS2551: Property '_svae' does not exist on type 'HomeView'. Did you mean '_save'?`,
|
|
76
|
+
`dist-build/src/app/views/home/home.view.ts(9,${col}): error TS2341: Property '_svae' is private and only accessible within class 'Base'.`,
|
|
77
|
+
].join('\n');
|
|
78
|
+
|
|
79
|
+
expect(remapTscOutput(output, { cwd, tempDir, templateMap: MAP, handlerRefs: REFS }).split('\n')).toEqual([
|
|
80
|
+
"src/app/views/home/home.view.html(2,26): error TS2551: Property '_svae' does not exist on type 'HomeView'. Did you mean '_save'? (data-event handler \"_svae\" of HomeView)",
|
|
81
|
+
"src/app/views/home/home.view.html(2,26): error TS2341: Property '_svae' is private and only accessible within class 'Base'. (data-event handler \"_svae\" of HomeView) Declare it protected to use it in a subclass template.",
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -1,77 +1,43 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import
|
|
4
|
-
import { print, errorExit } from '../../../utils/index.js';
|
|
3
|
+
import { errorExit } from '../../../utils/index.js';
|
|
5
4
|
import { PATHS } from './paths.js';
|
|
5
|
+
import { builderConfig } from '../../builder.config.js';
|
|
6
|
+
import { findDuplicateRouteKeys, readRoutesImporters } from './route-keys.js';
|
|
6
7
|
|
|
7
|
-
const ROUTES_FILE = path.join(PATHS.sourceApp, 'routes.ts');
|
|
8
|
+
const ROUTES_FILE = path.join(PATHS.sourceApp, 'routes.{{#if (eq lang "js")}}js{{else}}ts{{/if}}');
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
// Only when the routes are used: imported by app code, or prerendered (generateSEOFiles bundles
|
|
11
|
+
// appRoutes). An app without the router (components mounted into a page) needs no routes file.
|
|
12
|
+
async function validateRoutes() {
|
|
13
|
+
const importers = readRoutesImporters(PATHS.sourceApp, ROUTES_FILE);
|
|
14
|
+
if (importers.length === 0 && !builderConfig.generateSEO) return;
|
|
11
15
|
|
|
12
|
-
ts.forEachChild(sourceFile, function visit(node) {
|
|
13
|
-
if (
|
|
14
|
-
ts.isVariableDeclaration(node) &&
|
|
15
|
-
ts.isIdentifier(node.name) &&
|
|
16
|
-
node.name.text === 'appRoutes' &&
|
|
17
|
-
node.initializer &&
|
|
18
|
-
ts.isObjectLiteralExpression(node.initializer)
|
|
19
|
-
) {
|
|
20
|
-
objectLiteral = node.initializer;
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
ts.forEachChild(node, visit);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
return objectLiteral;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getRouteKeys(objectLiteral) {
|
|
30
|
-
return objectLiteral.properties
|
|
31
|
-
.filter((prop) => ts.isPropertyAssignment(prop) && ts.isStringLiteralLike(prop.name))
|
|
32
|
-
.map((prop) => prop.name.text);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function findDuplicates(keys) {
|
|
36
|
-
const seen = new Set();
|
|
37
|
-
const duplicates = new Set();
|
|
38
|
-
|
|
39
|
-
for (const key of keys) {
|
|
40
|
-
if (seen.has(key)) duplicates.add(key);
|
|
41
|
-
seen.add(key);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return [...duplicates];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function validateRoutes() {
|
|
48
16
|
let source;
|
|
49
17
|
try {
|
|
50
18
|
source = readFileSync(ROUTES_FILE, 'utf-8');
|
|
51
19
|
} catch {
|
|
52
|
-
|
|
20
|
+
const usedBy = importers.length
|
|
21
|
+
? `imported by ${importers.map((file) => path.relative(process.cwd(), file)).join(', ')}`
|
|
22
|
+
: 'needed by generateSEOFiles';
|
|
23
|
+
errorExit(`Failed to read ${ROUTES_FILE} (${usedBy})`, 'validate-routes');
|
|
53
24
|
}
|
|
54
25
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (!objectLiteral) {
|
|
59
|
-
errorExit(`Could not find an "appRoutes" object literal in ${ROUTES_FILE}`);
|
|
26
|
+
if (!/\bappRoutes\b/.test(source)) {
|
|
27
|
+
errorExit(`Could not find "appRoutes" in ${ROUTES_FILE}`, 'validate-routes');
|
|
60
28
|
}
|
|
61
29
|
|
|
62
|
-
const duplicates =
|
|
30
|
+
const duplicates = await findDuplicateRouteKeys(source, { loader: '{{#if (eq lang "js")}}js{{else}}ts{{/if}}', sourcefile: ROUTES_FILE });
|
|
63
31
|
|
|
64
32
|
if (duplicates.length > 0) {
|
|
65
|
-
const list = duplicates.map((key) => `"${key}"`).join(', ');
|
|
33
|
+
const list = duplicates.map(({ key, line }) => `"${key}"${line ? ` (line ${line})` : ''}`).join(', ');
|
|
66
34
|
errorExit(
|
|
67
|
-
`Duplicate route key(s) in ${ROUTES_FILE}: ${list} — an earlier route with this path is silently unreachable
|
|
35
|
+
`Duplicate route key(s) in ${ROUTES_FILE}: ${list} — an earlier route with this path is silently unreachable.`,
|
|
36
|
+
'validate-routes'
|
|
68
37
|
);
|
|
69
38
|
}
|
|
70
|
-
|
|
71
39
|
}
|
|
72
40
|
|
|
73
|
-
|
|
74
|
-
validateRoutes();
|
|
75
|
-
} catch(err) {
|
|
41
|
+
validateRoutes().catch((err) => {
|
|
76
42
|
errorExit(err, 'validate-routes');
|
|
77
|
-
}
|
|
43
|
+
});
|
|
@@ -5,11 +5,17 @@ import { builderConfig } from '../builder.config.js';
|
|
|
5
5
|
import { print, errorExit } from "../../utils/index.js";
|
|
6
6
|
|
|
7
7
|
function finalizeBuild() {
|
|
8
|
+
removeBuildFiles();
|
|
8
9
|
if (builderConfig.isProd) removeFoldersAfterBundle();
|
|
9
10
|
if (builderConfig.isProd) removeNutinConfig();
|
|
10
11
|
replaceDir(PATHS.temp, PATHS.build);
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
// Only used during the build: compile-ts's tsconfig and merge-templates' template map.
|
|
15
|
+
function removeBuildFiles() {
|
|
16
|
+
['tsconfig.json', '.template-map.json'].forEach((file) => fs.rmSync(path.join(PATHS.temp, file), { force: true }));
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
function removeFoldersAfterBundle() {
|
|
14
20
|
const foldersToRemove = [ 'core', 'app', path.join('..', 'config') ];
|
|
15
21
|
const pathToFolder = (folder) => path.join(PATHS.tempSource, folder);
|
|
@@ -2,8 +2,12 @@ import { readFile } from 'fs/promises';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { print, errorExit } from '../../../utils/index.js';
|
|
4
4
|
import { PATHS } from '../app/paths.js';
|
|
5
|
+
import { readRoutesImporters } from '../app/route-keys.js';
|
|
6
|
+
import { builderConfig } from '../../builder.config.js';
|
|
5
7
|
import { addTags } from './add-tags.js';
|
|
6
8
|
|
|
9
|
+
const ROUTES_FILE = path.join(PATHS.sourceApp, 'routes.{{#if (eq lang "js")}}js{{else}}ts{{/if}}');
|
|
10
|
+
|
|
7
11
|
async function validateHtml() {
|
|
8
12
|
const filePath = path.join(PATHS.tempSource, 'index.html');
|
|
9
13
|
|
|
@@ -14,8 +18,12 @@ async function validateHtml() {
|
|
|
14
18
|
errorExit(`Failed to read ${filePath}`);
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
// The router mounts views into #app, and generateSEOFiles prerenders into it. An app without
|
|
22
|
+
// the router mounts its components wherever it wants: a missing target is reported at runtime.
|
|
23
|
+
const needsApp = builderConfig.generateSEO || readRoutesImporters(PATHS.sourceApp, ROUTES_FILE).length > 0;
|
|
24
|
+
if (needsApp && !htmlContent.includes('id="app"')) {
|
|
25
|
+
const reason = builderConfig.generateSEO ? 'generateSEOFiles prerenders pages into it' : 'the router mounts views into it';
|
|
26
|
+
errorExit(`Missing #app container in index.html: ${reason}.`);
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
htmlContent = await addTags(htmlContent, filePath).catch(err => errorExit(err.message));
|