@nutin/cli 2.1.0 → 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 +322 -0
- package/README.md +14 -6
- 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 +12 -11
- package/templates/base/.gitignore.hbs +1 -0
- package/templates/base/AGENTS.md.hbs +45 -43
- package/templates/base/GETTING_STARTED.md.hbs +32 -23
- package/templates/base/README.md.hbs +7 -4
- package/templates/base/nutin.config.js.hbs +1 -1
- package/templates/base/src/app/components/.gitkeep +0 -0
- 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 +9 -3
- package/templates/base/src/app/services/.gitkeep +0 -0
- 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 +307 -21
- 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 +105 -4
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +136 -62
- 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 +101 -51
- 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 +217 -4
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +210 -65
- 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 +321 -0
- package/templates/base/src/core/services/router/router.ts.hbs +122 -54
- package/templates/base/src/index.html.hbs +1 -0
- package/templates/base/src/styles/_styles.scss.hbs +1 -113
- package/templates/base/src/styles/main.scss.hbs +1 -1
- package/templates/base/tools/builder/builder.config.js.hbs +6 -0
- package/templates/base/tools/builder/builder.js.hbs +26 -8
- 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/add-tags.js.hbs +1 -4
- package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +3 -3
- 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 +25 -8
- 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 +61 -15
- 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 +48 -18
- 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 +26 -8
- 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 +26 -9
- package/templates/base/tools/builder/core/styles/sass.js.hbs +7 -5
- package/templates/base/tools/builder/core/styles/tailwind.js.hbs +19 -59
- package/templates/base/tools/dev/dev-serve.js.hbs +71 -7
- package/templates/base/tools/dev/serve.js.hbs +152 -22
- package/templates/base/tools/dev/watcher.js.hbs +75 -28
- package/templates/base/tools/generator/generator.js.hbs +23 -18
- package/templates/base/tools/generator/handle-file.js.hbs +4 -24
- package/templates/base/tools/generator/handle-file.test.js.hbs +4 -23
- 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 +97 -0
- 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 +3 -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 +5 -2
- package/templates/features/docker/tools/docker/nginx.conf.template +19 -2
- 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 +29 -0
- 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/src/app/components/index.ts.hbs +0 -1
- package/templates/base/src/app/services/index.ts.hbs +0 -1
- package/templates/base/src/app/views/index.ts.hbs +0 -3
- /package/templates/base/tools/testin-nutin/core/{tests → globals}/clock.test.js.hbs +0 -0
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
import { View
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import type { View } from '../../base-classes/view/view.js';
|
|
2
|
+
import { Service } from '../../base-classes/service/service.js';
|
|
3
|
+
import { Navigation } from '../event-bus/navigation.facade.js';
|
|
4
|
+
import { AppEventBus } from '../event-bus/event-bus.js';
|
|
5
|
+
import { I18nService } from '../i18n/i18n.js';
|
|
6
|
+
import * as RouteGuardsManager from './helpers/route-guard-manager.helper.js';
|
|
7
|
+
import type { GuardResult } from './helpers/route-guard-manager.helper.js';
|
|
8
|
+
import * as ViewRenderManager from './helpers/view-render-manager.helper.js';
|
|
9
|
+
import * as NavigationManager from './helpers/navigation-manager.helper.js';
|
|
10
|
+
import type { HistoryMode } from './helpers/navigation-manager.helper.js';
|
|
11
|
+
import { CONFIG } from '../../config.js';
|
|
6
12
|
|
|
7
13
|
// centralized export
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* IRouter is a type alias for the instance of Router, not a true interface
|
|
12
|
-
*/
|
|
13
|
-
export type IRouter = InstanceType<typeof Router>;
|
|
14
|
-
|
|
15
|
-
export interface RouteMatch {
|
|
16
|
-
route: RouteConfig;
|
|
17
|
-
params: Record<string, string>;
|
|
18
|
-
pattern: string;
|
|
19
|
-
}
|
|
14
|
+
export * as NavigationManager from './helpers/navigation-manager.helper.js';
|
|
20
15
|
|
|
21
16
|
class Router extends Service<Router> {
|
|
22
17
|
private _currentView: View | null = null;
|
|
@@ -25,11 +20,16 @@ class Router extends Service<Router> {
|
|
|
25
20
|
private onNavigate = (data: { path: string }) => this.navigate(data.path);
|
|
26
21
|
private onReload = () => this.reload();
|
|
27
22
|
private _busSubscriptions: Array<() => void> = [];
|
|
23
|
+
// Bumped by every navigation: one whose guards resolve after a newer navigation started
|
|
24
|
+
// is dropped instead of rendering over it.
|
|
25
|
+
private _navId = 0;
|
|
26
|
+
// False until the first view is rendered: the first load leaves focus to the browser.
|
|
27
|
+
private _hasRendered = false;
|
|
28
28
|
|
|
29
|
-
constructor(private routes: Routes) {
|
|
29
|
+
constructor(private routes: Nutin.Routes) {
|
|
30
30
|
super();
|
|
31
31
|
this.initializeEventListeners();
|
|
32
|
-
this.navigate(
|
|
32
|
+
this.navigate(this.currentLocation());
|
|
33
33
|
this.registerCleanup(this.removeEventListeners);
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -41,50 +41,101 @@ class Router extends Service<Router> {
|
|
|
41
41
|
window.removeEventListener('popstate', this.onPopState);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// Re-renders the current page, so focus isn't moved (see focusView()).
|
|
44
45
|
public async reload(): Promise<void> {
|
|
45
|
-
|
|
46
|
-
await this.navigate(currentRoute, false);
|
|
46
|
+
await this.navigateWithMode(this.currentLocation(), 'none', false);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
public async navigate(path: string | '', pushState: boolean = true): Promise<void> {
|
|
50
|
-
|
|
50
|
+
await this.navigateWithMode(path, pushState ? 'push' : 'none', this._hasRendered);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public getCurrentParams(): Record<string, string> {
|
|
54
|
+
return { ...this._currentParams };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public getParam(key: string): string | undefined {
|
|
58
|
+
return this._currentParams[key];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Path, query and hash of the current URL, as navigate() takes them.
|
|
62
|
+
private currentLocation(): string {
|
|
63
|
+
return NavigationManager.getCurrentPath() + window.location.search + window.location.hash;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private async navigateWithMode(path: string, mode: HistoryMode, moveFocus: boolean): Promise<void> {
|
|
67
|
+
const navId = ++this._navId;
|
|
68
|
+
const isStale = () => navId !== this._navId;
|
|
69
|
+
const [pathAndQuery = '', hash] = path.split('#');
|
|
70
|
+
const [rawPath = '', query] = pathAndQuery.split('?');
|
|
71
|
+
const search = query ? `?${query}` : '';
|
|
72
|
+
const normalizedPath = NavigationManager.normalizePath(rawPath);
|
|
51
73
|
const currentPath = NavigationManager.getCurrentPath();
|
|
52
|
-
|
|
74
|
+
|
|
53
75
|
// Try to match the route with parameters
|
|
54
76
|
const routeMatch = this.matchRoute(normalizedPath);
|
|
55
77
|
|
|
56
78
|
if (!routeMatch) {
|
|
57
|
-
await this.handleNotFound(normalizedPath, currentPath,
|
|
79
|
+
await this.handleNotFound(normalizedPath, currentPath, mode, isStale, moveFocus);
|
|
58
80
|
return;
|
|
59
81
|
}
|
|
60
82
|
|
|
61
83
|
const guardResult = await this.handleGuards(
|
|
62
|
-
normalizedPath,
|
|
63
|
-
routeMatch.route,
|
|
64
|
-
routeMatch.params,
|
|
65
|
-
|
|
84
|
+
normalizedPath,
|
|
85
|
+
routeMatch.route,
|
|
86
|
+
routeMatch.params,
|
|
87
|
+
currentPath,
|
|
88
|
+
mode,
|
|
89
|
+
moveFocus
|
|
66
90
|
);
|
|
67
|
-
|
|
68
|
-
if (!guardResult) return;
|
|
91
|
+
|
|
92
|
+
if (!guardResult || isStale()) return;
|
|
93
|
+
|
|
94
|
+
// Before the current view is torn down: it stays up while a lazy route's chunk loads.
|
|
95
|
+
const view = await this.resolveView(guardResult.viewConstructor!, NavigationManager.localizedUrl(normalizedPath, search, hash), isStale);
|
|
96
|
+
if (!view) return;
|
|
69
97
|
|
|
70
98
|
this._currentView = await ViewRenderManager.transitionOutCurrentView(this._currentView);
|
|
71
99
|
this._currentParams = routeMatch.params;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
);
|
|
100
|
+
|
|
101
|
+
// Must run before renderNewView(): a view's onEnter() may rewrite the URL via NavigationManager.replaceState(),
|
|
102
|
+
// which acts on whatever history entry is current. This ensures current before onEnter() fires.
|
|
103
|
+
NavigationManager.updateHistory(normalizedPath, currentPath, mode, hash, search);
|
|
104
|
+
|
|
105
|
+
this._currentView = ViewRenderManager.renderNewView(view, routeMatch.params);
|
|
76
106
|
|
|
77
107
|
NavigationManager.updateDocumentTitle(this._currentView, routeMatch.pattern);
|
|
78
|
-
NavigationManager.
|
|
79
|
-
|
|
108
|
+
NavigationManager.scrollToHash(hash);
|
|
109
|
+
this.afterRender(moveFocus);
|
|
80
110
|
}
|
|
81
111
|
|
|
82
|
-
|
|
83
|
-
|
|
112
|
+
// Runs a route's view factory. A sync one behaves as it always did (its errors propagate).
|
|
113
|
+
// A lazy one is awaited: if it fails to load, recoverFromFailedViewLoad() takes over; if a
|
|
114
|
+
// newer navigation started meanwhile, the view (already mounted, empty, by its
|
|
115
|
+
// constructor) is destroyed and dropped.
|
|
116
|
+
private async resolveView(factory: Nutin.ViewFactory, url: string, isStale: () => boolean): Promise<View | null> {
|
|
117
|
+
if (isStale()) return null;
|
|
118
|
+
const result = factory();
|
|
119
|
+
if (!isPromise(result)) return result;
|
|
120
|
+
|
|
121
|
+
let view: View;
|
|
122
|
+
try {
|
|
123
|
+
view = await result;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (!isStale()) NavigationManager.recoverFromFailedViewLoad(url, error);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (isStale()) {
|
|
129
|
+
view.destroy();
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return view;
|
|
84
133
|
}
|
|
85
134
|
|
|
86
|
-
|
|
87
|
-
|
|
135
|
+
// After updateDocumentTitle(): focusView() announces document.title when the view has no h1.
|
|
136
|
+
private afterRender(moveFocus: boolean): void {
|
|
137
|
+
if (moveFocus && this._currentView) NavigationManager.focusView(this._currentView);
|
|
138
|
+
this._hasRendered = true;
|
|
88
139
|
}
|
|
89
140
|
|
|
90
141
|
private initializeEventListeners(): void {
|
|
@@ -107,11 +158,13 @@ class Router extends Service<Router> {
|
|
|
107
158
|
}
|
|
108
159
|
|
|
109
160
|
private async handlePopState(): Promise<void> {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
161
|
+
if (globalThis.__NUTIN_I18N__ ?? CONFIG.i18n) {
|
|
162
|
+
const newLocale = NavigationManager.getCurrentLocale();
|
|
163
|
+
if (newLocale && newLocale !== I18nService.currentLanguage) {
|
|
164
|
+
await I18nService.setCurrentLanguage(newLocale as Nutin.Language);
|
|
165
|
+
}
|
|
113
166
|
}
|
|
114
|
-
this.
|
|
167
|
+
this.navigateWithMode(this.currentLocation(), 'none', this._hasRendered);
|
|
115
168
|
}
|
|
116
169
|
|
|
117
170
|
/**
|
|
@@ -120,7 +173,7 @@ class Router extends Service<Router> {
|
|
|
120
173
|
* - '/users/:id?' matches '/users' and '/users/123'
|
|
121
174
|
* - '/posts/:id' matches '/posts/123' but not '/posts'
|
|
122
175
|
*/
|
|
123
|
-
private matchRoute(path: string): RouteMatch | null {
|
|
176
|
+
private matchRoute(path: string): Nutin.RouteMatch | null {
|
|
124
177
|
for (const [pattern, routeConfig] of Object.entries(this.routes)) {
|
|
125
178
|
const match = NavigationManager.matchPattern(pattern, path);
|
|
126
179
|
if (match) {
|
|
@@ -133,7 +186,9 @@ class Router extends Service<Router> {
|
|
|
133
186
|
private async handleNotFound(
|
|
134
187
|
normalizedPath: string,
|
|
135
188
|
currentPath: string,
|
|
136
|
-
|
|
189
|
+
mode: HistoryMode,
|
|
190
|
+
isStale: () => boolean,
|
|
191
|
+
moveFocus: boolean
|
|
137
192
|
): Promise<void> {
|
|
138
193
|
const notFoundRoute = this.routes['/404'];
|
|
139
194
|
|
|
@@ -142,21 +197,26 @@ class Router extends Service<Router> {
|
|
|
142
197
|
return;
|
|
143
198
|
}
|
|
144
199
|
|
|
145
|
-
const
|
|
146
|
-
|
|
200
|
+
const notFoundFactory = RouteGuardsManager.getViewConstructor(notFoundRoute);
|
|
201
|
+
const view = await this.resolveView(notFoundFactory, NavigationManager.localizedUrl(normalizedPath), isStale);
|
|
202
|
+
if (!view) return;
|
|
203
|
+
|
|
147
204
|
this._currentView = await ViewRenderManager.transitionOutCurrentView(this._currentView);
|
|
148
205
|
this._currentParams = {};
|
|
149
|
-
this._currentView = ViewRenderManager.renderNewView(
|
|
206
|
+
this._currentView = ViewRenderManager.renderNewView(view, {});
|
|
150
207
|
|
|
151
208
|
NavigationManager.updateDocumentTitle(this._currentView, '/404');
|
|
152
|
-
NavigationManager.updateHistory(normalizedPath, currentPath,
|
|
209
|
+
NavigationManager.updateHistory(normalizedPath, currentPath, mode);
|
|
210
|
+
this.afterRender(moveFocus);
|
|
153
211
|
}
|
|
154
212
|
|
|
155
213
|
private async handleGuards(
|
|
156
214
|
normalizedPath: string,
|
|
157
|
-
routeConfig: RouteConfig,
|
|
215
|
+
routeConfig: Nutin.RouteConfig,
|
|
158
216
|
params: Record<string, string>,
|
|
159
|
-
|
|
217
|
+
currentPath: string,
|
|
218
|
+
mode: HistoryMode,
|
|
219
|
+
moveFocus: boolean
|
|
160
220
|
): Promise<GuardResult | false> {
|
|
161
221
|
const guardResult = await RouteGuardsManager.processRouteGuards(
|
|
162
222
|
routeConfig,
|
|
@@ -166,7 +226,11 @@ class Router extends Service<Router> {
|
|
|
166
226
|
|
|
167
227
|
if (!guardResult.allowed) {
|
|
168
228
|
if (guardResult.redirectTo) {
|
|
169
|
-
|
|
229
|
+
// A guarded URL that's already the current history entry (back/forward,
|
|
230
|
+
// reload, first load) is replaced by the redirect target, so the address
|
|
231
|
+
// bar matches what's rendered and Back doesn't land on the guarded URL again.
|
|
232
|
+
const redirectMode = mode === 'push' && normalizedPath !== currentPath ? 'push' : 'replace';
|
|
233
|
+
await this.navigateWithMode(guardResult.redirectTo, redirectMode, moveFocus);
|
|
170
234
|
}
|
|
171
235
|
// If no redirect, stay on current route (guard blocked navigation)
|
|
172
236
|
return false;
|
|
@@ -176,4 +240,8 @@ class Router extends Service<Router> {
|
|
|
176
240
|
}
|
|
177
241
|
}
|
|
178
242
|
|
|
179
|
-
|
|
243
|
+
function isPromise<T>(value: T | Promise<T>): value is Promise<T> {
|
|
244
|
+
return typeof (value as Promise<T> | null)?.then === 'function';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const AppRouter = (routes: Nutin.Routes) => Router.getInstance(routes);
|
|
@@ -1,113 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/* ===========
|
|
4
|
-
CSS Reset
|
|
5
|
-
=========== */
|
|
6
|
-
|
|
7
|
-
/* Box sizing */
|
|
8
|
-
|
|
9
|
-
*,
|
|
10
|
-
*::before,
|
|
11
|
-
*::after {
|
|
12
|
-
box-sizing: border-box;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/* Remove default spacing */
|
|
16
|
-
|
|
17
|
-
* {
|
|
18
|
-
margin: 0;
|
|
19
|
-
padding: 0;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/* Document */
|
|
23
|
-
|
|
24
|
-
html {
|
|
25
|
-
font-size: 100%;
|
|
26
|
-
-webkit-text-size-adjust: 100%;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
body {
|
|
30
|
-
min-height: 100vh;
|
|
31
|
-
font: inherit;
|
|
32
|
-
line-height: 1.5;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/* Media */
|
|
36
|
-
|
|
37
|
-
img,
|
|
38
|
-
picture,
|
|
39
|
-
svg,
|
|
40
|
-
canvas,
|
|
41
|
-
video {
|
|
42
|
-
display: block;
|
|
43
|
-
max-width: 100%;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/* Forms */
|
|
47
|
-
|
|
48
|
-
button,
|
|
49
|
-
input,
|
|
50
|
-
select,
|
|
51
|
-
textarea {
|
|
52
|
-
font: inherit;
|
|
53
|
-
color: inherit;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
button {
|
|
57
|
-
background: none;
|
|
58
|
-
border: none;
|
|
59
|
-
cursor: pointer;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
textarea {
|
|
63
|
-
resize: vertical;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/* Tables */
|
|
67
|
-
|
|
68
|
-
table {
|
|
69
|
-
border-collapse: collapse;
|
|
70
|
-
border-spacing: 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/* Lists */
|
|
74
|
-
|
|
75
|
-
ul,
|
|
76
|
-
ol {
|
|
77
|
-
list-style: none;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/* Links */
|
|
81
|
-
|
|
82
|
-
a {
|
|
83
|
-
color: inherit;
|
|
84
|
-
text-decoration: none;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/* Quotes */
|
|
88
|
-
|
|
89
|
-
blockquote,
|
|
90
|
-
q {
|
|
91
|
-
quotes: none;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
blockquote::before,
|
|
95
|
-
blockquote::after,
|
|
96
|
-
q::before,
|
|
97
|
-
q::after {
|
|
98
|
-
content: "";
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/* Miscellaneous */
|
|
102
|
-
|
|
103
|
-
hr {
|
|
104
|
-
border: none;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
fieldset {
|
|
108
|
-
border: none;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
:where([hidden]) {
|
|
112
|
-
display: none !important;
|
|
113
|
-
}
|
|
1
|
+
// Global styles
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Global styles
|
|
2
2
|
@use "styles";
|
|
@@ -19,6 +19,11 @@ function validateConfig(config) {
|
|
|
19
19
|
|
|
20
20
|
validateConfig(nutinConfig);
|
|
21
21
|
|
|
22
|
+
// `build:prod` passes --prod rather than a NODE_ENV=production prefix, which Windows shells
|
|
23
|
+
// don't understand. Every build step is a separate process that only inherits the env (see
|
|
24
|
+
// runScript), so the flag is turned into NODE_ENV here, for them and for this process.
|
|
25
|
+
if (process.argv.includes('--prod')) process.env.NODE_ENV = 'production';
|
|
26
|
+
|
|
22
27
|
const wellKnownNonSEORoutes = [
|
|
23
28
|
'/400',
|
|
24
29
|
'/401',
|
|
@@ -34,6 +39,7 @@ export const builderConfig = {
|
|
|
34
39
|
...nutinConfig.builder,
|
|
35
40
|
isProd: process.env.NODE_ENV === 'production',
|
|
36
41
|
i18n: nutinConfig.i18n,
|
|
42
|
+
markdownSources: nutinConfig.markdownSources,
|
|
37
43
|
tailwind: nutinConfig.tailwind,
|
|
38
44
|
generateSEO: nutinConfig.generateSEOFiles,
|
|
39
45
|
WELL_KNOWN_NON_SEO_ROUTES: wellKnownNonSEORoutes,
|
|
@@ -1,35 +1,53 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { print, runScript } from '../utils/index.js';
|
|
4
|
+
import { print, runScript, acquireBuildLock } from '../utils/index.js';
|
|
5
5
|
import { builderConfig } from './builder.config.js';
|
|
6
6
|
|
|
7
|
+
// Must happen before copy-static.js wipes/recreates dist-build — a second build
|
|
8
|
+
// starting mid-run (a manual build, another dev watcher instance, ...) would
|
|
9
|
+
// otherwise race it. See tools/utils/build-lock.js.
|
|
10
|
+
await acquireBuildLock();
|
|
11
|
+
|
|
7
12
|
const scriptsDir = path.join(process.cwd(), 'tools', 'builder', 'core');
|
|
8
13
|
|
|
9
14
|
print.boldHead(`\nStarting build...`);
|
|
10
15
|
|
|
16
|
+
// runScript forwards env vars but not argv, so -y/--yes (auto-install an opt-in
|
|
17
|
+
// feature's missing dependencies, see utils/ensure-deps.js) is passed through the env.
|
|
18
|
+
if (process.argv.includes('-y') || process.argv.includes('--yes')) process.env.NUTIN_ASSUME_YES = '1';
|
|
19
|
+
|
|
11
20
|
runScript(path.join(scriptsDir, 'app', 'copy-static.js'), 'Copying files...');
|
|
21
|
+
{{#if markdown}}
|
|
22
|
+
|
|
23
|
+
runScript(path.join(scriptsDir, 'markdown', 'generate-markdown-pages.js'), 'Compiling Markdown pages...');
|
|
24
|
+
{{/if}}
|
|
12
25
|
|
|
13
26
|
runScript(path.join(scriptsDir, 'html-index', 'validate-html.js'), 'Processing index.html...');
|
|
14
27
|
runScript(path.join(scriptsDir, 'app', 'validate-routes.js'), 'Validating app routes...');
|
|
15
28
|
|
|
16
|
-
runScript(path.join(scriptsDir, 'app', 'compile-ts.js'), 'Compiling TypeScript...');
|
|
17
|
-
|
|
18
29
|
runScript(path.join(scriptsDir, 'html-templates', 'process-html-templates.js'), 'Processing HTML templates...');
|
|
30
|
+
{{#unless (eq lang "js")}}
|
|
31
|
+
// After the templates are merged, so they're type-checked too.
|
|
32
|
+
runScript(path.join(scriptsDir, 'app', 'compile-ts.js'), 'Compiling TypeScript...');
|
|
33
|
+
{{/unless}}
|
|
34
|
+
runScript(path.join(scriptsDir, 'html-templates', 'minify-templates.js'), 'Minifying HTML templates...');
|
|
19
35
|
|
|
20
36
|
runScript(path.join(scriptsDir, 'styles', 'sass.js'), 'Compiling styles...');
|
|
21
37
|
if (builderConfig.tailwind) runScript(path.join(scriptsDir, 'styles', 'tailwind.js'), 'Compiling Tailwind CSS...');
|
|
22
38
|
|
|
23
39
|
if (builderConfig.i18n) runScript(path.join(scriptsDir, 'i18n', 'build-i18n.js'), 'Combining locales...');
|
|
24
40
|
|
|
41
|
+
runScript(path.join(scriptsDir, 'prod-bundle', 'esbuild.js'), 'Running esbuild...');
|
|
42
|
+
|
|
25
43
|
if (builderConfig.isProd) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
44
|
+
runScript(path.join(scriptsDir, 'prod-bundle', 'hash-files.js'), 'Hashing files...');
|
|
45
|
+
if (builderConfig.generateSEO) runScript(path.join(scriptsDir, 'seo', 'generate-seo-files.js'), 'Generating SEO Files...');
|
|
46
|
+
// Last, so the prerendered SEO pages, sitemap.xml and robots.txt get compressed too.
|
|
47
|
+
runScript(path.join(scriptsDir, 'prod-bundle', 'compress-files.js'), 'Compressing files...');
|
|
30
48
|
}
|
|
31
49
|
|
|
32
|
-
runScript(path.join(scriptsDir, 'finalize-build.js'), 'Finalizing build...')
|
|
50
|
+
runScript(path.join(scriptsDir, 'finalize-build.js'), 'Finalizing build...');
|
|
33
51
|
|
|
34
52
|
print.boldSuccess(`\nBuild successful!\n`);
|
|
35
53
|
|
|
@@ -1,14 +1,89 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { captureCommand, errorExit } from "../../../utils/index.js";
|
|
2
4
|
import { builderConfig } from '../../builder.config.js';
|
|
5
|
+
import { PATHS } from './paths.js';
|
|
6
|
+
import { readTemplateMap } from '../html-templates/merge-templates.js';
|
|
7
|
+
import { remapTscOutput } from './ts-errors.js';
|
|
8
|
+
import { injectHandlerRefs, stripHandlerRefs } from './handler-refs.js';
|
|
9
|
+
|
|
10
|
+
// Runs after the .html templates are merged (process-html-templates), on dist-build's copies,
|
|
11
|
+
// so templates are type-checked like the rest of the code. A tsconfig generated there extends
|
|
12
|
+
// the project's: dist-build/src mirrors src, so its include/exclude apply as written.
|
|
13
|
+
const BUILD_TSCONFIG = path.join(PATHS.temp, 'tsconfig.json');
|
|
3
14
|
|
|
4
15
|
async function compileTS() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
|
|
16
|
+
writeBuildTsconfig();
|
|
17
|
+
|
|
18
|
+
// Prod bundles straight from the merged .ts (see prod-bundle/esbuild.js), so tsc only
|
|
19
|
+
// type-checks there. Dev emits each .js next to its .ts, for the dev bundle and testin-nutin.
|
|
20
|
+
const args = ['--project', BUILD_TSCONFIG, '--pretty', 'false'];
|
|
9
21
|
if (builderConfig.isProd) args.push('--noEmit');
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
// data-event handlers are type-checked through references injected for tsc only, then
|
|
24
|
+
// removed from the .ts copies the prod bundle is built from (see handler-refs.js).
|
|
25
|
+
const handlerRefs = await injectHandlerRefs();
|
|
26
|
+
const { code, output } = await captureCommand('tsc', args);
|
|
27
|
+
await stripHandlerRefs(handlerRefs);
|
|
28
|
+
const report = remapTscOutput(output, {
|
|
29
|
+
cwd: process.cwd(),
|
|
30
|
+
tempDir: PATHS.temp,
|
|
31
|
+
templateMap: await readTemplateMap(),
|
|
32
|
+
handlerRefs,
|
|
33
|
+
}).trim();
|
|
34
|
+
|
|
35
|
+
// On stdout, where tsc itself writes its diagnostics.
|
|
36
|
+
if (report) console.log(report);
|
|
37
|
+
if (code !== 0) throw new Error(`tsc exited with code ${code}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeBuildTsconfig() {
|
|
41
|
+
const project = readTsconfig(path.resolve('tsconfig.json'));
|
|
42
|
+
const config = {
|
|
43
|
+
extends: '../tsconfig.json',
|
|
44
|
+
compilerOptions: { rootDir: 'src', outDir: 'src' },
|
|
45
|
+
include: (project.include ?? ['src']).map(toBuildPath),
|
|
46
|
+
exclude: (project.exclude ?? []).map(toBuildPath),
|
|
47
|
+
};
|
|
48
|
+
if (project.files) config.files = project.files.map(toBuildPath);
|
|
49
|
+
fs.writeFileSync(BUILD_TSCONFIG, JSON.stringify(config, null, 2));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Paths in src/ are mirrored in dist-build/src; anything else stays where it is, one level up.
|
|
53
|
+
function toBuildPath(entry) {
|
|
54
|
+
const normalized = entry.replace(/^\.\//, '');
|
|
55
|
+
return normalized === 'src' || normalized.startsWith('src/') ? normalized : `../${normalized}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// tsconfig.json is JSONC: comments and trailing commas are allowed.
|
|
59
|
+
function readTsconfig(file) {
|
|
60
|
+
const source = fs.readFileSync(file, 'utf-8');
|
|
61
|
+
let json = '';
|
|
62
|
+
for (let i = 0; i < source.length; i++) {
|
|
63
|
+
const ch = source[i];
|
|
64
|
+
if (ch === '"') {
|
|
65
|
+
const end = endOfString(source, i);
|
|
66
|
+
json += source.slice(i, end);
|
|
67
|
+
i = end - 1;
|
|
68
|
+
} else if (ch === '/' && source[i + 1] === '/') {
|
|
69
|
+
while (i < source.length && source[i] !== '\n') i++;
|
|
70
|
+
json += '\n';
|
|
71
|
+
} else if (ch === '/' && source[i + 1] === '*') {
|
|
72
|
+
i = source.indexOf('*/', i + 2) + 1;
|
|
73
|
+
if (i === 0) break;
|
|
74
|
+
} else {
|
|
75
|
+
json += ch;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return JSON.parse(json.replace(/,(\s*[}\]])/g, '$1'));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function endOfString(source, start) {
|
|
82
|
+
for (let i = start + 1; i < source.length; i++) {
|
|
83
|
+
if (source[i] === '\\') i++;
|
|
84
|
+
else if (source[i] === '"') return i + 1;
|
|
85
|
+
}
|
|
86
|
+
return source.length;
|
|
12
87
|
}
|
|
13
88
|
|
|
14
89
|
compileTS().catch((err) => {
|
|
@@ -7,7 +7,13 @@ import { builderConfig } from '../../builder.config.js';
|
|
|
7
7
|
|
|
8
8
|
async function copyStatic() {
|
|
9
9
|
const extensions = [...BINARY_EXTENSIONS, '.html'];
|
|
10
|
-
|
|
10
|
+
{{#if (eq lang "js")}}
|
|
11
|
+
// No compile step emits .js here, so sources are copied in dev and prod alike.
|
|
12
|
+
extensions.push('.js');
|
|
13
|
+
{{else}}
|
|
14
|
+
// Templates are merged into these copies, then tsc checks them (and emits .js in dev).
|
|
15
|
+
extensions.push('.ts');
|
|
16
|
+
{{/if}}
|
|
11
17
|
|
|
12
18
|
// Every build starts from a clean dist-build: a failed/interrupted prior
|
|
13
19
|
// build never gets renamed away by finalize-build.js, so a stale sibling
|
|
@@ -23,6 +29,11 @@ async function copyStatic() {
|
|
|
23
29
|
const files = await getFilesRecursive(PATHS.source, extension);
|
|
24
30
|
|
|
25
31
|
for (const file of files) {
|
|
32
|
+
{{#if (eq lang "js")}}
|
|
33
|
+
// Tests import the build output; they must not end up in it.
|
|
34
|
+
if (file.endsWith('.test.js')) continue;
|
|
35
|
+
|
|
36
|
+
{{/if}}
|
|
26
37
|
const relative = path.relative(PATHS.source, file);
|
|
27
38
|
const dest = path.join(PATHS.tempSource, relative);
|
|
28
39
|
|