@nutin/cli 1.3.0 → 2.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 +122 -8
- package/README.md +21 -37
- package/bin/add-feature.mjs +9 -0
- package/bin/cli.mjs +8 -0
- package/bin/create-app.mjs +5 -4
- package/bin/update-app.mjs +9 -0
- package/lib/index.mjs +81 -30
- package/lib/src/add-feature/feature-adder.mjs +83 -0
- package/lib/src/add-feature/feature-context-builder.mjs +25 -0
- package/lib/src/add-feature/nutin-config-updater.mjs +29 -0
- package/lib/src/add-feature/package-json-updater.mjs +49 -0
- package/lib/src/common/feature-registry.mjs +11 -0
- package/lib/src/common/file-generator.mjs +159 -0
- package/lib/src/common/meta-bootstrap-prompt.mjs +33 -0
- package/lib/src/common/package-data.mjs +10 -0
- package/lib/src/common/package-json-helper.mjs +103 -0
- package/lib/src/common/project-meta.mjs +54 -0
- package/lib/src/create-app/context-builder.mjs +21 -0
- package/lib/src/{git-manager.mjs → create-app/git-manager.mjs} +2 -2
- package/lib/src/create-app/json-manager.mjs +73 -0
- package/lib/src/create-app/new-app-prompt.mjs +51 -0
- package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +15 -13
- package/lib/src/create-app/success-message.mjs +14 -0
- package/lib/src/update-app/conflict-report-writer.mjs +31 -0
- package/lib/src/update-app/fetch-old-templates.mjs +61 -0
- package/lib/src/update-app/project-updater.mjs +136 -0
- package/lib/src/update-app/template-differ.mjs +124 -0
- package/lib/src/update-app/update-context-builder.mjs +25 -0
- package/lib/src/update-app/update-summary-printer.mjs +22 -0
- package/lib/src/update-app/version-compare.mjs +12 -0
- package/lib/src/utils/print.mjs +36 -0
- package/lib/src/utils/promise-exec-alias.mjs +4 -0
- package/package.json +17 -12
- package/templates/base/.gitignore.hbs +6 -1
- package/templates/base/AGENTS.md.hbs +169 -0
- package/templates/base/GETTING_STARTED.md.hbs +227 -0
- package/templates/base/README.md.hbs +8 -17
- package/templates/base/config/languages.json.hbs +6 -0
- package/templates/base/config/seo.json.hbs +12 -0
- package/templates/base/nutin.config.js.hbs +51 -0
- package/templates/base/src/app/components/index.ts.hbs +1 -2
- package/templates/base/src/app/globals.d.ts.hbs +3 -15
- package/templates/base/src/{libs/guards → app}/guards.ts.hbs +1 -5
- package/templates/base/src/app/main.ts.hbs +7 -26
- package/templates/base/src/app/services/index.ts.hbs +1 -1
- package/templates/base/src/app/views/home/home.view.html.hbs +7 -0
- package/templates/base/src/app/views/home/home.view.ts.hbs +1 -56
- package/templates/base/src/app/views/index.ts.hbs +1 -0
- package/templates/base/src/app/views/not-found/not-found.view.html.hbs +8 -0
- package/templates/base/src/app/views/not-found/not-found.view.ts.hbs +4 -37
- package/templates/base/src/core/base-classes/base-component/base-component.test.js.hbs +158 -0
- package/templates/base/src/core/base-classes/base-component/base-component.ts.hbs +81 -31
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.test.js.hbs +124 -0
- package/templates/base/src/core/base-classes/base-component/helpers/catalog.helper.ts.hbs +38 -23
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.test.js.hbs +74 -0
- package/templates/base/src/core/base-classes/base-component/helpers/children.helper.ts.hbs +1 -1
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.test.js.hbs +145 -0
- package/templates/base/src/core/base-classes/base-component/helpers/dom.helper.ts.hbs +41 -1
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.test.js.hbs +88 -0
- package/templates/base/src/core/base-classes/base-component/helpers/event.helper.ts.hbs +13 -14
- package/templates/base/src/core/base-classes/base-component/helpers/i18n.helper.test.js.hbs +62 -0
- package/templates/base/src/core/base-classes/base-component/helpers/pipe.helper.test.js.hbs +82 -0
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.test.js.hbs +142 -0
- package/templates/base/src/core/base-classes/base-component/helpers/security.helper.ts.hbs +67 -22
- package/templates/base/src/core/base-classes/base-component/helpers/token.helper.test.js.hbs +183 -0
- package/templates/base/src/core/base-classes/component/component.test.js.hbs +85 -0
- package/templates/base/src/core/base-classes/component/component.ts.hbs +27 -40
- package/templates/base/src/core/base-classes/component/helpers/config.helper.test.js.hbs +58 -0
- package/templates/base/src/core/base-classes/component/helpers/config.helper.ts.hbs +17 -0
- package/templates/base/src/core/base-classes/component/helpers/data-binding.helper.test.js.hbs +75 -0
- package/templates/base/src/core/base-classes/service/service.test.js.hbs +150 -0
- package/templates/base/src/core/base-classes/service/service.ts.hbs +26 -20
- package/templates/base/src/core/base-classes/view/view.test.js.hbs +74 -0
- package/templates/base/src/core/base-classes/view/view.ts.hbs +17 -41
- package/templates/base/src/core/config.ts.hbs +7 -0
- package/templates/base/src/core/index.ts.hbs +0 -3
- package/templates/base/src/core/internals.d.ts.hbs +27 -0
- package/templates/base/src/core/nutin-config.d.ts.hbs +4 -0
- package/templates/base/src/core/services/event-bus/event-bus.test.js.hbs +67 -3
- package/templates/base/src/core/services/event-bus/event-bus.ts.hbs +9 -4
- package/templates/base/src/core/services/event-bus/index.ts.hbs +3 -0
- package/templates/base/src/core/services/event-bus/lifecycle.facade.test.js.hbs +123 -0
- package/templates/base/src/core/services/event-bus/lifecycle.facade.ts.hbs +61 -0
- package/templates/base/src/core/services/event-bus/navigation.facade.test.js.hbs +46 -0
- package/templates/base/src/core/services/event-bus/navigation.facade.ts.hbs +25 -0
- package/templates/base/src/core/services/globals/globals.test.js.hbs +149 -0
- package/templates/base/src/core/services/globals/globals.ts.hbs +95 -0
- package/templates/base/src/core/services/http-client/helpers/http-builder.helper.test.js.hbs +25 -0
- package/templates/base/src/core/services/http-client/helpers/http-manager.helper.test.js.hbs +20 -0
- package/templates/base/src/core/services/http-client/http-client.test.js.hbs +43 -0
- package/templates/base/src/core/services/i18n/i18n.test.js.hbs +270 -0
- package/templates/{features/i18n → base}/src/core/services/i18n/i18n.ts.hbs +60 -9
- package/templates/base/src/core/services/index.ts.hbs +5 -6
- package/templates/base/src/core/services/pipe-registry/pipe-registry.test.js.hbs +60 -1
- package/templates/base/src/core/services/pipe-registry/pipe-registry.ts.hbs +1 -1
- package/templates/base/src/core/services/pipe-registry/pipes.test.js.hbs +128 -0
- package/templates/base/src/{libs/pipes → core/services/pipe-registry}/pipes.ts.hbs +8 -5
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.test.js.hbs +149 -6
- package/templates/base/src/core/services/router/helpers/navigation-manager.helper.ts.hbs +42 -30
- package/templates/base/src/core/services/router/helpers/route-guard-manager.helper.test.js.hbs +27 -0
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.test.js.hbs +83 -17
- package/templates/base/src/core/services/router/helpers/view-render-manager.helper.ts.hbs +13 -70
- package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
- package/templates/base/src/core/services/router/router.ts.hbs +40 -9
- package/templates/base/src/index.html.hbs +2 -6
- package/templates/base/src/styles/_styles.scss.hbs +102 -43
- package/templates/base/src/styles/base/_variables.scss.hbs +1 -85
- package/templates/base/src/styles/main.scss.hbs +1 -18
- package/templates/base/src/styles/tailwind.css.hbs +2 -0
- package/templates/base/tools/builder/builder.config.js.hbs +40 -0
- package/templates/base/tools/builder/builder.js.hbs +24 -26
- package/templates/base/tools/builder/core/app/binary-extensions.test.js.hbs +16 -0
- package/templates/base/tools/builder/core/app/compile-ts.js.hbs +16 -0
- package/templates/base/tools/builder/core/{copy-static.js.hbs → app/copy-static.js.hbs} +27 -34
- package/templates/base/tools/builder/core/app/validate-routes.js.hbs +77 -0
- package/templates/base/tools/builder/core/finalize-build.js.hbs +16 -7
- package/templates/base/tools/builder/core/{add-tags.js.hbs → html-index/add-tags.js.hbs} +2 -2
- package/templates/base/tools/builder/core/html-index/add-tags.test.js.hbs +56 -0
- package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs} +5 -11
- package/templates/base/tools/builder/core/html-templates/merge-templates.js.hbs +152 -0
- package/templates/base/tools/builder/core/html-templates/minify-html.js.hbs +15 -0
- package/templates/base/tools/builder/core/html-templates/process-html-templates.js.hbs +8 -0
- package/templates/{features/i18n/tools/builder/core → base/tools/builder/core/i18n}/build-i18n.js.hbs +6 -17
- package/templates/base/tools/builder/core/prod-bundle/compress-files.js.hbs +37 -0
- package/templates/base/tools/builder/core/prod-bundle/esbuild.js.hbs +30 -0
- package/templates/base/tools/builder/core/{hash-files.js.hbs → prod-bundle/hash-files.js.hbs} +14 -11
- package/templates/base/tools/builder/core/seo/generate-robots-txt.js.hbs +51 -0
- package/templates/base/tools/builder/core/seo/generate-seo-files.js.hbs +16 -0
- package/templates/base/tools/builder/core/seo/generate-seo-html.js.hbs +128 -0
- package/templates/base/tools/builder/core/seo/generate-sitemap-xml.js.hbs +43 -0
- package/templates/base/tools/builder/core/seo/html-substitutions.js.hbs +99 -0
- package/templates/base/tools/builder/core/seo/i18n-resolution.js.hbs +66 -0
- package/templates/base/tools/builder/core/seo/route-output.js.hbs +64 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-bundle.js.hbs +54 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-polyfills.js.hbs +132 -0
- package/templates/base/tools/builder/core/seo/ssr/ssr-render.js.hbs +95 -0
- package/templates/base/tools/builder/core/styles/sass.js.hbs +40 -0
- package/templates/base/tools/builder/core/styles/tailwind.js.hbs +84 -0
- package/templates/base/tools/dev/dev-serve.js.hbs +26 -18
- package/templates/base/tools/dev/serve.js.hbs +11 -1
- package/templates/base/tools/dev/watcher.js.hbs +14 -6
- package/templates/base/tools/generator/generator.js.hbs +64 -47
- package/templates/base/tools/generator/handle-file.js.hbs +17 -44
- package/templates/base/tools/generator/handle-file.test.js.hbs +72 -0
- package/templates/base/tools/generator/templates/{component.template.js.hbs → component/component.template.js.hbs} +3 -2
- package/templates/base/tools/generator/templates/component/component.template.test.js.hbs +23 -0
- package/templates/base/tools/generator/templates/html/html.template.js.hbs +2 -0
- package/templates/base/tools/generator/templates/html/html.template.test.js.hbs +7 -0
- package/templates/base/tools/generator/templates/index.js.hbs +7 -12
- package/templates/base/tools/generator/templates/locale/locale.template.js.hbs +4 -0
- package/templates/base/tools/generator/templates/locale/locale.template.test.js.hbs +7 -0
- package/templates/base/tools/generator/templates/scss/scss.template.js.hbs +1 -0
- package/templates/base/tools/generator/templates/scss/scss.template.test.js.hbs +7 -0
- package/templates/base/tools/generator/templates/service/service.template.js.hbs +17 -0
- package/templates/base/tools/generator/templates/service/service.template.test.js.hbs +22 -0
- package/templates/base/tools/generator/templates/test/test.template.js.hbs +10 -0
- package/templates/base/tools/generator/templates/test/test.template.test.js.hbs +27 -0
- package/templates/base/tools/generator/templates/{view.template.js.hbs → view/view.template.js.hbs} +3 -2
- package/templates/base/tools/generator/templates/view/view.template.test.js.hbs +24 -0
- package/templates/base/tools/testin-nutin/core/coverage/collect-coverage.js.hbs +39 -0
- package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.js.hbs +115 -0
- package/templates/base/tools/testin-nutin/core/coverage/compute-coverage.test.js.hbs +74 -0
- package/templates/base/tools/testin-nutin/core/coverage/write-summary-report.js.hbs +54 -0
- package/templates/base/tools/testin-nutin/core/coverage/write-uncovered-report.js.hbs +44 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/assertion-lib.js.hbs +62 -24
- package/templates/base/tools/testin-nutin/core/globals/clock.js.hbs +206 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/jsdom-setup.js.hbs +19 -6
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/register-test-globals.js.hbs +13 -4
- package/templates/base/tools/testin-nutin/core/globals/silence-console.js.hbs +23 -0
- package/templates/{features/testinNutin/testin-nutin/core/src → base/tools/testin-nutin/core}/globals/spyon.js.hbs +2 -0
- package/templates/base/tools/testin-nutin/core/index.js.hbs +9 -0
- package/templates/base/tools/testin-nutin/core/printer.js.hbs +85 -0
- package/templates/base/tools/testin-nutin/core/queue/queue.test.js.hbs +46 -0
- package/templates/base/tools/testin-nutin/core/queue/test-discovery.test.js.hbs +46 -0
- package/templates/base/tools/testin-nutin/core/queue/test-queue.js.hbs +154 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/core/tests/assertions.test.js.hbs +27 -0
- package/templates/base/tools/testin-nutin/core/tests/clock.test.js.hbs +118 -0
- package/templates/base/tools/testin-nutin/core/tests/globals-each-all.test.js.hbs +28 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/create-mock-method.js.hbs +2 -1
- package/templates/base/tools/testin-nutin/mocks/create-mock-method.test.js.hbs +64 -0
- package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-event-bus.js.hbs +3 -2
- package/templates/base/tools/testin-nutin/mocks/mock-event-bus.test.js.hbs +66 -0
- package/templates/base/tools/testin-nutin/mocks/mock-http-client.test.js.hbs +26 -0
- package/templates/base/tools/testin-nutin/mocks/mock-i18n.test.js.hbs +61 -0
- package/templates/base/tools/testin-nutin/mocks/mock-router.test.js.hbs +29 -0
- package/templates/base/tools/testin-nutin/runner.js.hbs +100 -0
- package/templates/base/tools/testin-nutin/watch-tests.js.hbs +37 -0
- package/templates/base/tools/utils/error-exit.js.hbs +8 -0
- package/templates/base/tools/utils/format.js.hbs +10 -10
- package/templates/base/tools/utils/format.test.js.hbs +45 -0
- package/templates/base/tools/utils/get-files-recursive.js.hbs +6 -1
- package/templates/base/tools/utils/get-files-recursive.test.js.hbs +49 -0
- package/templates/base/tools/utils/get-rel-to-core.test.js.hbs +18 -0
- package/templates/base/tools/utils/index.js.hbs +1 -3
- package/templates/base/tools/utils/languages.js.hbs +15 -0
- package/templates/base/tools/utils/languages.test.js.hbs +16 -0
- package/templates/base/tools/utils/print.js.hbs +19 -13
- package/templates/base/tools/utils/print.test.js.hbs +60 -0
- package/templates/base/tools/utils/prompt-boolean.js.hbs +7 -0
- package/templates/base/tools/utils/prompt-boolean.test.js.hbs +54 -0
- package/templates/base/tools/utils/run-command.js.hbs +4 -2
- package/templates/base/tools/utils/run-command.test.js.hbs +19 -0
- package/templates/base/tools/utils/run-script.js.hbs +14 -3
- package/templates/base/tools/utils/run-script.test.js.hbs +40 -0
- package/templates/features/{deployHelper → docker}/.dockerignore.hbs +4 -4
- package/templates/features/docker/tools/docker/Dockerfile.template.hbs +51 -0
- package/templates/features/{deployHelper/tools/deployment/nginx.conf.hbs → docker/tools/docker/nginx.conf.template} +4 -2
- package/templates/features/docker/tools/docker/scripts/docker-build.js.hbs +20 -0
- package/templates/features/docker/tools/docker/scripts/docker-ports.js.hbs +48 -0
- package/templates/features/docker/tools/docker/scripts/docker-ports.test.js.hbs +52 -0
- package/templates/features/docker/tools/docker/scripts/docker-run.js.hbs +28 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.js.hbs +23 -0
- package/templates/features/docker/tools/docker/scripts/render-docker-template.test.js.hbs +60 -0
- package/templates/features/docker/tools/docker/scripts/validate-docker.js.hbs +30 -0
- package/lib/src/context-builder.mjs +0 -35
- package/lib/src/file-generator.mjs +0 -109
- package/lib/src/json-generator.mjs +0 -31
- package/lib/src/package-manager.mjs +0 -139
- package/lib/src/print.mjs +0 -31
- package/lib/src/prompts.mjs +0 -123
- package/lib/src/utils.mjs +0 -18
- package/lib/src/version.mjs +0 -1
- package/templates/base/builder.config.js.hbs +0 -35
- package/templates/base/src/app/components/common/anchor/anchor.component.ts.hbs +0 -32
- package/templates/base/src/app/components/common/button/button.component.ts.hbs +0 -40
- package/templates/base/src/app/dom.test.js.hbs +0 -13
- package/templates/base/src/app/services/example/example.service.ts.hbs +0 -18
- package/templates/base/src/app/views/home/home.view.test.js.hbs +0 -26
- package/templates/base/src/core/services/store/store.test.js.hbs +0 -8
- package/templates/base/src/core/services/store/store.ts.hbs +0 -39
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.test.js.hbs +0 -0
- package/templates/base/src/core/utils/anchor-manager/anchor-manager.utils.ts.hbs +0 -85
- package/templates/base/src/core/utils/button-manager/button-manager.utils.test.js.hbs +0 -249
- package/templates/base/src/core/utils/button-manager/button-manager.utils.ts.hbs +0 -108
- package/templates/base/src/core/utils/helpers/attributes.helper.ts.hbs +0 -50
- package/templates/base/src/core/utils/index.ts.hbs +0 -2
- package/templates/base/src/libs/index.ts.hbs +0 -4
- package/templates/base/src/libs/popover/popover.ts.hbs +0 -145
- package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -72
- package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
- package/templates/base/src/styles/core/_libs.scss.hbs +0 -132
- package/templates/base/tools/builder/core/esbuild.js.hbs +0 -25
- package/templates/base/tools/builder/core/minify-html.js.hbs +0 -60
- package/templates/base/tools/builder/core/sass.js.hbs +0 -22
- package/templates/base/tools/builder/core/tsc.js.hbs +0 -12
- package/templates/base/tools/generator/templates/service.template.js.hbs +0 -17
- package/templates/base/tools/utils/export-flags.js.hbs +0 -6
- package/templates/features/deployHelper/tools/builder/core/compress-files.js.hbs +0 -56
- package/templates/features/deployHelper/tools/deployment/Dockerfile.hbs +0 -30
- package/templates/features/i18n/src/app/views/home/locales/en.json.hbs +0 -9
- package/templates/features/i18n/src/app/views/not-found/locales/en.json +0 -8
- package/templates/features/i18n/src/app/views/not-found/locales/fr.json +0 -8
- package/templates/features/i18n/src/core/config.ts.hbs +0 -3
- package/templates/features/i18n/src/core/services/i18n/i18n.test.js.hbs +0 -125
- package/templates/features/i18n/tools/generator/templates/json.template.js.hbs +0 -8
- package/templates/features/i18n/tools/utils/languages.js.hbs +0 -7
- package/templates/features/stylinNutin/src/styles/_custom-classes.scss.hbs +0 -9
- package/templates/features/stylinNutin/src/styles/base/_mixins.scss.hbs +0 -372
- package/templates/features/stylinNutin/src/styles/components/_index.scss.hbs +0 -3
- package/templates/features/stylinNutin/src/styles/core/_utilities.scss.hbs +0 -240
- package/templates/features/stylinNutin/src/styles/views/_index.scss.hbs +0 -0
- package/templates/features/stylinNutin/tools/generator/templates/scss.template.js.hbs +0 -4
- package/templates/features/template/src/app/views/home/home.view.html.hbs +0 -56
- package/templates/features/template/src/app/views/not-found/not-found.view.html.hbs +0 -19
- package/templates/features/template/tools/builder/core/merge-templates.js.hbs +0 -49
- package/templates/features/template/tools/generator/templates/html.template.js.hbs +0 -2
- package/templates/features/testinNutin/testin-nutin/core/index.js.hbs +0 -5
- package/templates/features/testinNutin/testin-nutin/core/src/utils/print.js.hbs +0 -64
- package/templates/features/testinNutin/testin-nutin/core/src/utils/test-queue.js.hbs +0 -64
- package/templates/features/testinNutin/testin-nutin/core/tests/globals-each-all.test.js.hbs +0 -28
- package/templates/features/testinNutin/testin-nutin/mocks/mock-store.js.hbs +0 -38
- package/templates/features/testinNutin/testin-nutin/runner.js.hbs +0 -36
- package/templates/features/testinNutin/testin-nutin/watch-tests.js.hbs +0 -21
- package/templates/features/testinNutin/testin-nutin.config.js.hbs +0 -14
- /package/lib/src/{template-compiler.mjs → common/template-compiler.mjs} +0 -0
- /package/templates/{features/i18n → base}/src/core/base-classes/base-component/helpers/i18n.helper.ts.hbs +0 -0
- /package/templates/{features/i18n → base}/src/core/services/i18n/languages.ts.hbs +0 -0
- /package/templates/base/tools/builder/{variables → core/app}/binary-extensions.js.hbs +0 -0
- /package/templates/base/tools/builder/core/{paths.js.hbs → app/paths.js.hbs} +0 -0
- /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/queue.js.hbs +0 -0
- /package/templates/{features/testinNutin/testin-nutin/core/src/utils → base/tools/testin-nutin/core/queue}/test-discovery.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-http-client.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-i18n.js.hbs +0 -0
- /package/templates/{features/testinNutin → base/tools}/testin-nutin/mocks/mock-router.js.hbs +0 -0
|
@@ -1,38 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { print,
|
|
4
|
+
import { print, runScript } from '../utils/index.js';
|
|
5
|
+
import { builderConfig } from './builder.config.js';
|
|
5
6
|
|
|
6
7
|
const scriptsDir = path.join(process.cwd(), 'tools', 'builder', 'core');
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
runScript(path.join(scriptsDir, '
|
|
28
|
-
runScript(path.join(scriptsDir, '
|
|
29
|
-
{{#if deployHelper}}
|
|
30
|
-
runScript(path.join(scriptsDir, 'compress-files.js'), 'Compressing files...');
|
|
31
|
-
{{/if}}
|
|
9
|
+
print.boldHead(`\nStarting build...`);
|
|
10
|
+
|
|
11
|
+
runScript(path.join(scriptsDir, 'app', 'copy-static.js'), 'Copying files...');
|
|
12
|
+
|
|
13
|
+
runScript(path.join(scriptsDir, 'html-index', 'validate-html.js'), 'Processing index.html...');
|
|
14
|
+
runScript(path.join(scriptsDir, 'app', 'validate-routes.js'), 'Validating app routes...');
|
|
15
|
+
|
|
16
|
+
runScript(path.join(scriptsDir, 'app', 'compile-ts.js'), 'Compiling TypeScript...');
|
|
17
|
+
|
|
18
|
+
runScript(path.join(scriptsDir, 'html-templates', 'process-html-templates.js'), 'Processing HTML templates...');
|
|
19
|
+
|
|
20
|
+
runScript(path.join(scriptsDir, 'styles', 'sass.js'), 'Compiling styles...');
|
|
21
|
+
if (builderConfig.tailwind) runScript(path.join(scriptsDir, 'styles', 'tailwind.js'), 'Compiling Tailwind CSS...');
|
|
22
|
+
|
|
23
|
+
if (builderConfig.i18n) runScript(path.join(scriptsDir, 'i18n', 'build-i18n.js'), 'Combining locales...');
|
|
24
|
+
|
|
25
|
+
if (builderConfig.isProd) {
|
|
26
|
+
runScript(path.join(scriptsDir, 'prod-bundle', 'esbuild.js'), 'Running esbuild...');
|
|
27
|
+
runScript(path.join(scriptsDir, 'prod-bundle', 'hash-files.js'), 'Hashing files...');
|
|
28
|
+
runScript(path.join(scriptsDir, 'prod-bundle', 'compress-files.js'), 'Compressing files...');
|
|
29
|
+
if (builderConfig.generateSEO) runScript(path.join(scriptsDir, 'seo', 'generate-seo-files.js'), 'Generating SEO Files...');
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
runScript(path.join(scriptsDir, 'finalize-build.js'), 'Finalizing build...')
|
|
35
33
|
|
|
36
34
|
print.boldSuccess(`\nBuild successful!\n`);
|
|
37
35
|
|
|
38
|
-
if (!isProd) print.
|
|
36
|
+
if (!builderConfig.isProd) print.info('For production, use "{{packageManager}} run build:prod"');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BINARY_EXTENSIONS } from './binary-extensions.js';
|
|
2
|
+
|
|
3
|
+
describe('BINARY_EXTENSIONS', () => {
|
|
4
|
+
it('is a Set containing common binary asset extensions', () => {
|
|
5
|
+
expect(BINARY_EXTENSIONS).toBeInstanceOf(Set);
|
|
6
|
+
expect(BINARY_EXTENSIONS.has('.png')).toBeTruthy();
|
|
7
|
+
expect(BINARY_EXTENSIONS.has('.woff2')).toBeTruthy();
|
|
8
|
+
expect(BINARY_EXTENSIONS.has('.gz')).toBeTruthy();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('does not treat text/source extensions as binary', () => {
|
|
12
|
+
expect(BINARY_EXTENSIONS.has('.js')).toBeFalsy();
|
|
13
|
+
expect(BINARY_EXTENSIONS.has('.css')).toBeFalsy();
|
|
14
|
+
expect(BINARY_EXTENSIONS.has('.html')).toBeFalsy();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { runCommand, print, errorExit } from "../../../utils/index.js";
|
|
2
|
+
import { builderConfig } from '../../builder.config.js';
|
|
3
|
+
|
|
4
|
+
async function compileTS() {
|
|
5
|
+
// Prod bundles straight from .ts source (see prod-bundle/esbuild.js), so tsc only
|
|
6
|
+
// needs to type-check there, not emit — emitting would leave a stale, unmerged .js
|
|
7
|
+
// sibling next to each merged .ts view/component's source.
|
|
8
|
+
const args = ['--project', 'tsconfig.json'];
|
|
9
|
+
if (builderConfig.isProd) args.push('--noEmit');
|
|
10
|
+
|
|
11
|
+
await runCommand('tsc', args);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
compileTS().catch((err) => {
|
|
15
|
+
errorExit(err, 'compile-ts');
|
|
16
|
+
});
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { BINARY_EXTENSIONS } from '../variables/binary-extensions.js';
|
|
3
|
+
import { getFilesRecursive, print, errorExit } from '../../../utils/index.js';
|
|
4
|
+
import { BINARY_EXTENSIONS } from './binary-extensions.js';
|
|
6
5
|
import { PATHS } from './paths.js';
|
|
6
|
+
import { builderConfig } from '../../builder.config.js';
|
|
7
7
|
|
|
8
8
|
async function copyStatic() {
|
|
9
|
-
const extensions = [...BINARY_EXTENSIONS, 'html'];
|
|
10
|
-
if (isProd) extensions.push('.ts');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const extensions = [...BINARY_EXTENSIONS, '.html'];
|
|
10
|
+
if (builderConfig.isProd) extensions.push('.ts');
|
|
11
|
+
|
|
12
|
+
// Every build starts from a clean dist-build: a failed/interrupted prior
|
|
13
|
+
// build never gets renamed away by finalize-build.js, so a stale sibling
|
|
14
|
+
// (already merged, placeholder gone) can otherwise sit next to the next
|
|
15
|
+
// run's fresh output and trip merge-templates.js's per-sibling check.
|
|
16
|
+
await fs.rm(PATHS.temp, { recursive: true, force: true });
|
|
14
17
|
await ensureDir(PATHS.tempSource);
|
|
15
18
|
|
|
16
19
|
await copyFavicon();
|
|
17
|
-
{{#if i18n}}
|
|
18
20
|
await copyConfig();
|
|
19
|
-
{{/if}}
|
|
20
21
|
|
|
21
22
|
for (const extension of extensions) {
|
|
22
23
|
const files = await getFilesRecursive(PATHS.source, extension);
|
|
@@ -27,17 +28,13 @@ async function copyStatic() {
|
|
|
27
28
|
|
|
28
29
|
try {
|
|
29
30
|
await copyFile(file, dest);
|
|
30
|
-
if (isVerbose) print.info(`✅ Copied: ${file} → ${dest}`);
|
|
31
31
|
} catch (err) {
|
|
32
|
-
|
|
33
|
-
exit(1);
|
|
32
|
+
throw new Error(`Failed to copy: ${file}. ${err.message}`, { cause: err });
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
if (isProd) await copyJsonFiles(PATHS.source, PATHS.tempSource);
|
|
39
|
-
|
|
40
|
-
if (isVerbose) print.boldInfo(`Copy complete.\n`);
|
|
37
|
+
if (builderConfig.isProd) await copyJsonFiles(PATHS.source, PATHS.tempSource);
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
async function ensureDir(dir) {
|
|
@@ -55,33 +52,31 @@ async function copyFavicon() {
|
|
|
55
52
|
|
|
56
53
|
try {
|
|
57
54
|
await copyFile(srcPath, destPath);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (err.code === 'ENOENT') print.warn('No favicon found at public/favicon.ico');
|
|
57
|
+
else errorExit(err, 'copy-static');
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
{{#if i18n}}
|
|
65
61
|
async function copyConfig() {
|
|
66
62
|
const srcPath = path.resolve(path.join('config'));
|
|
67
|
-
const temp = isProd ? PATHS.temp : PATHS.tempSource;
|
|
63
|
+
const temp = builderConfig.isProd ? PATHS.temp : PATHS.tempSource;
|
|
68
64
|
const destPath = path.join(temp, 'config');
|
|
69
65
|
try {
|
|
70
66
|
await copyJsonFiles(srcPath, destPath);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
await copyFile(path.resolve('nutin.config.js'), path.join(temp, 'nutin.config.js'));
|
|
68
|
+
|
|
69
|
+
// for testin-nutin tests to run
|
|
70
|
+
if (!builderConfig.isProd) {
|
|
71
|
+
await copyJsonFiles(srcPath, path.join(PATHS.temp, 'config'));
|
|
72
|
+
await copyFile(path.resolve('nutin.config.js'), path.join(PATHS.temp, 'nutin.config.js'));
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
errorExit(err, 'copy-static');
|
|
79
76
|
}
|
|
80
77
|
}
|
|
81
78
|
|
|
82
|
-
{{/if}}
|
|
83
79
|
async function copyJsonFiles(sourceDir, destDir) {
|
|
84
|
-
|
|
85
80
|
const items = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
86
81
|
|
|
87
82
|
for (const item of items) {
|
|
@@ -93,12 +88,10 @@ async function copyJsonFiles(sourceDir, destDir) {
|
|
|
93
88
|
} else if (item.isFile() && path.extname(item.name) === '.json') {
|
|
94
89
|
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
95
90
|
await fs.copyFile(sourcePath, destPath);
|
|
96
|
-
if (isVerbose) print.info(`Copied JSON: ${sourcePath} -> ${destPath}`);
|
|
97
91
|
}
|
|
98
92
|
}
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
copyStatic().catch((err) => {
|
|
102
|
-
|
|
103
|
-
exit(1);
|
|
96
|
+
errorExit(err, 'copy-static');
|
|
104
97
|
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import { print, errorExit } from '../../../utils/index.js';
|
|
5
|
+
import { PATHS } from './paths.js';
|
|
6
|
+
|
|
7
|
+
const ROUTES_FILE = path.join(PATHS.sourceApp, 'routes.ts');
|
|
8
|
+
|
|
9
|
+
function findAppRoutesObjectLiteral(sourceFile) {
|
|
10
|
+
let objectLiteral;
|
|
11
|
+
|
|
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
|
+
let source;
|
|
49
|
+
try {
|
|
50
|
+
source = readFileSync(ROUTES_FILE, 'utf-8');
|
|
51
|
+
} catch {
|
|
52
|
+
errorExit(`Failed to read ${ROUTES_FILE}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const sourceFile = ts.createSourceFile(ROUTES_FILE, source, ts.ScriptTarget.Latest, true);
|
|
56
|
+
const objectLiteral = findAppRoutesObjectLiteral(sourceFile);
|
|
57
|
+
|
|
58
|
+
if (!objectLiteral) {
|
|
59
|
+
errorExit(`Could not find an "appRoutes" object literal in ${ROUTES_FILE}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const duplicates = findDuplicates(getRouteKeys(objectLiteral));
|
|
63
|
+
|
|
64
|
+
if (duplicates.length > 0) {
|
|
65
|
+
const list = duplicates.map((key) => `"${key}"`).join(', ');
|
|
66
|
+
errorExit(
|
|
67
|
+
`Duplicate route key(s) in ${ROUTES_FILE}: ${list} — an earlier route with this path is silently unreachable.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
validateRoutes();
|
|
75
|
+
} catch(err) {
|
|
76
|
+
errorExit(err, 'validate-routes');
|
|
77
|
+
}
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
import { print, isProd, isVerbose } from "../../utils/index.js";
|
|
2
1
|
import fs from 'fs';
|
|
3
2
|
import path from 'path';
|
|
4
|
-
import { PATHS } from "./paths.js";
|
|
3
|
+
import { PATHS } from "./app/paths.js";
|
|
4
|
+
import { builderConfig } from '../builder.config.js';
|
|
5
|
+
import { print, errorExit } from "../../utils/index.js";
|
|
5
6
|
|
|
6
7
|
function finalizeBuild() {
|
|
7
|
-
if (isProd) removeFoldersAfterBundle();
|
|
8
|
+
if (builderConfig.isProd) removeFoldersAfterBundle();
|
|
9
|
+
if (builderConfig.isProd) removeNutinConfig();
|
|
8
10
|
replaceDir(PATHS.temp, PATHS.build);
|
|
9
|
-
if (isVerbose) print.boldInfo('Build finalized.\n');
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
function removeFoldersAfterBundle() {
|
|
13
|
-
const foldersToRemove = ['core', '
|
|
14
|
+
const foldersToRemove = [ 'core', 'app', path.join('..', 'config') ];
|
|
14
15
|
const pathToFolder = (folder) => path.join(PATHS.tempSource, folder);
|
|
15
16
|
|
|
16
|
-
foldersToRemove.forEach(folder => fs.rmSync(pathToFolder(folder), { recursive : true }));
|
|
17
|
+
foldersToRemove.forEach(folder => fs.rmSync(pathToFolder(folder), { recursive: true, force: true }));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function removeNutinConfig() {
|
|
21
|
+
fs.rmSync(path.join(PATHS.temp, 'nutin.config.js'));
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
function replaceDir(src, dest) {
|
|
@@ -21,4 +26,8 @@ function replaceDir(src, dest) {
|
|
|
21
26
|
fs.renameSync(src, dest);
|
|
22
27
|
}
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
try {
|
|
30
|
+
finalizeBuild();
|
|
31
|
+
} catch(err) {
|
|
32
|
+
errorExit(err, 'finalize-build')
|
|
33
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { print, isProd } from '../../utils/index.js';
|
|
2
1
|
import { writeFile } from 'fs/promises';
|
|
2
|
+
import { builderConfig } from '../../builder.config.js';
|
|
3
3
|
|
|
4
4
|
export async function addTags(htmlContent, filePath) {
|
|
5
|
-
const scriptSrc = isProd ? "bundle.js" : "app/main.js";
|
|
5
|
+
const scriptSrc = builderConfig.isProd ? "bundle.js" : "app/main.js";
|
|
6
6
|
|
|
7
7
|
const tags = [
|
|
8
8
|
{ type: 'stylesheet', href: 'main.css', position: 'head' },
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { addTags } from './add-tags.js';
|
|
5
|
+
|
|
6
|
+
describe('addTags', () => {
|
|
7
|
+
let tmpDir;
|
|
8
|
+
let filePath;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nutin-add-tags-'));
|
|
12
|
+
filePath = path.join(tmpDir, 'index.html');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('inserts the stylesheet before </head> and the script before </body>', async () => {
|
|
20
|
+
const html = '<html><head><title>T</title></head><body></body></html>';
|
|
21
|
+
|
|
22
|
+
const result = await addTags(html, filePath);
|
|
23
|
+
|
|
24
|
+
expect(result).toBe(
|
|
25
|
+
'<html><head><title>T</title><link rel="stylesheet" href="/main.css"></head><body><script type="module" src="/app/main.js"></script></body></html>'
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('falls back to inserting the stylesheet before <title> when there is no </head>', async () => {
|
|
30
|
+
const html = '<html><title>T</title><body></body></html>';
|
|
31
|
+
|
|
32
|
+
const result = await addTags(html, filePath);
|
|
33
|
+
|
|
34
|
+
expect(result).toBe(
|
|
35
|
+
'<html><link rel="stylesheet" href="/main.css"><title>T</title><body><script type="module" src="/app/main.js"></script></body></html>'
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('appends both tags to the end when neither anchor is present', async () => {
|
|
40
|
+
const html = '<html></html>';
|
|
41
|
+
|
|
42
|
+
const result = await addTags(html, filePath);
|
|
43
|
+
|
|
44
|
+
expect(result).toBe(
|
|
45
|
+
'<html></html><link rel="stylesheet" href="/main.css"><script type="module" src="/app/main.js"></script>'
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('writes the modified html to filePath', async () => {
|
|
50
|
+
const html = '<html><head></head><body></body></html>';
|
|
51
|
+
|
|
52
|
+
const result = await addTags(html, filePath);
|
|
53
|
+
|
|
54
|
+
expect(fs.readFileSync(filePath, 'utf8')).toBe(result);
|
|
55
|
+
});
|
|
56
|
+
});
|
package/templates/base/tools/builder/core/{validate-html.js.hbs → html-index/validate-html.js.hbs}
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'fs/promises';
|
|
2
|
-
import { exit } from 'process';
|
|
3
|
-
import { print, isVerbose } from '../../utils/index.js';
|
|
4
|
-
import { PATHS } from './paths.js';
|
|
5
2
|
import path from 'path';
|
|
3
|
+
import { print, errorExit } from '../../../utils/index.js';
|
|
4
|
+
import { PATHS } from '../app/paths.js';
|
|
6
5
|
import { addTags } from './add-tags.js';
|
|
7
6
|
|
|
8
7
|
async function validateHtml() {
|
|
@@ -27,13 +26,8 @@ async function validateHtml() {
|
|
|
27
26
|
if (!/<link[^>]*rel=["']stylesheet["']/.test(htmlContent)) {
|
|
28
27
|
errorExit('No stylesheet found in HTML');
|
|
29
28
|
}
|
|
30
|
-
|
|
31
|
-
if (isVerbose) print.boldInfo('HTML validation passed\n');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function errorExit(message) {
|
|
35
|
-
print.boldError(`[ERROR] ${message}`);
|
|
36
|
-
exit(1);
|
|
37
29
|
}
|
|
38
30
|
|
|
39
|
-
validateHtml()
|
|
31
|
+
validateHtml().catch((err) => {
|
|
32
|
+
errorExit(err, 'validate-html');
|
|
33
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getFilesRecursive, print, pascalCased, capitalized } from '../../../utils/index.js';
|
|
4
|
+
import { PATHS } from '../app/paths.js';
|
|
5
|
+
import { minifyHTML } from './minify-html.js';
|
|
6
|
+
|
|
7
|
+
const PLACEHOLDER = '__TEMPLATE_PLACEHOLDER__';
|
|
8
|
+
const TARGET_EXTENSIONS = ['.ts', '.js'];
|
|
9
|
+
const TEMPLATE_REGEX = /const\s+(?:template|templateFn)\s*=?\s*(?:\(.*?\)\s*=>\s*)?`([\s\S]*?)`/;
|
|
10
|
+
|
|
11
|
+
// A component/view's template can live inline in its .ts/.js file or in a
|
|
12
|
+
// colocated .html sibling merged in via `__TEMPLATE_PLACEHOLDER__` — resolved
|
|
13
|
+
// automatically per file rather than through a global config flag:
|
|
14
|
+
// external + placeholder -> merge external in
|
|
15
|
+
// external + real inline markup -> conflict, fail
|
|
16
|
+
// no external + real inline markup -> minify inline
|
|
17
|
+
// no external + still the placeholder -> nothing to use, fail
|
|
18
|
+
export async function resolveTemplates() {
|
|
19
|
+
const htmlFiles = await getFilesRecursive(PATHS.tempApp, 'html');
|
|
20
|
+
const resolvedPaths = new Set();
|
|
21
|
+
const failures = [];
|
|
22
|
+
|
|
23
|
+
for (const htmlPath of htmlFiles) {
|
|
24
|
+
await resolveExternalTemplate(htmlPath, resolvedPaths, failures);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
await resolveRemainingTemplates(resolvedPaths, failures);
|
|
28
|
+
|
|
29
|
+
if (failures.length) {
|
|
30
|
+
throw new Error(`Failed to resolve ${failures.length} template(s).`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Normally a view/component has exactly one sibling here — `.js` in dev (tsc
|
|
35
|
+
// output), `.ts` in prod (esbuild bundles straight from source, tsc runs
|
|
36
|
+
// --noEmit there). Checking whichever exist(s) rather than assuming one, so
|
|
37
|
+
// this can't silently regress again if that dev/prod split ever changes.
|
|
38
|
+
async function resolveExternalTemplate(htmlPath, resolvedPaths, failures) {
|
|
39
|
+
const htmlFilename = path.basename(htmlPath);
|
|
40
|
+
|
|
41
|
+
let htmlContent;
|
|
42
|
+
try {
|
|
43
|
+
htmlContent = await fs.readFile(htmlPath, 'utf-8');
|
|
44
|
+
} catch (err) {
|
|
45
|
+
print.warn(`Failed to read "${htmlFilename}". ${err.message}`);
|
|
46
|
+
failures.push(htmlFilename);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const minifiedHtml = await minifyHTML(htmlContent);
|
|
51
|
+
let mergedCount = 0;
|
|
52
|
+
let conflictCount = 0;
|
|
53
|
+
|
|
54
|
+
for (const extension of TARGET_EXTENSIONS) {
|
|
55
|
+
const codePath = htmlPath.replace(/\.html$/, extension);
|
|
56
|
+
|
|
57
|
+
let content;
|
|
58
|
+
try {
|
|
59
|
+
content = await fs.readFile(codePath, 'utf-8');
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (err.code === 'ENOENT') continue;
|
|
62
|
+
print.warn(`Cannot update "${path.basename(codePath)}". ${err.message}`);
|
|
63
|
+
failures.push(path.basename(codePath));
|
|
64
|
+
resolvedPaths.add(codePath);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
resolvedPaths.add(codePath);
|
|
69
|
+
|
|
70
|
+
if (content.includes(PLACEHOLDER)) {
|
|
71
|
+
await fs.writeFile(codePath, content.replace(PLACEHOLDER, minifiedHtml));
|
|
72
|
+
mergedCount++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
print.warn(describeConflict(codePath));
|
|
77
|
+
failures.push(path.basename(codePath));
|
|
78
|
+
conflictCount++;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (mergedCount > 0) {
|
|
82
|
+
await fs.unlink(htmlPath);
|
|
83
|
+
} else if (mergedCount + conflictCount === 0) {
|
|
84
|
+
print.warn(describeOrphanHtml(htmlPath));
|
|
85
|
+
failures.push(htmlFilename);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function resolveRemainingTemplates(resolvedPaths, failures) {
|
|
90
|
+
const codeFiles = await getFilesRecursive(PATHS.tempApp, TARGET_EXTENSIONS);
|
|
91
|
+
|
|
92
|
+
for (const codePath of codeFiles) {
|
|
93
|
+
if (resolvedPaths.has(codePath)) continue;
|
|
94
|
+
|
|
95
|
+
const content = await fs.readFile(codePath, 'utf-8');
|
|
96
|
+
const match = content.match(TEMPLATE_REGEX);
|
|
97
|
+
if (!match) continue;
|
|
98
|
+
|
|
99
|
+
const [full, body] = match;
|
|
100
|
+
|
|
101
|
+
if (body === PLACEHOLDER) {
|
|
102
|
+
print.warn(describeMissingTemplate(codePath));
|
|
103
|
+
failures.push(path.basename(codePath));
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const minified = await minifyHTML(body);
|
|
108
|
+
await fs.writeFile(codePath, content.replace(full, full.replace(body, minified)));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function describeCodeFile(codePath) {
|
|
113
|
+
const filename = path.basename(codePath);
|
|
114
|
+
const basename = filename.replace(/\.(ts|js)$/, '');
|
|
115
|
+
const lastDot = basename.lastIndexOf('.');
|
|
116
|
+
const namePart = lastDot === -1 ? basename : basename.slice(0, lastDot);
|
|
117
|
+
const suffix = lastDot === -1 ? '' : basename.slice(lastDot + 1);
|
|
118
|
+
const kind = suffix ? capitalized(suffix) : 'Component';
|
|
119
|
+
const className = `${pascalCased(namePart)}${kind}`;
|
|
120
|
+
|
|
121
|
+
return { filename, basename, kind, className };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function describeConflict(codePath) {
|
|
125
|
+
const { filename, basename, kind, className } = describeCodeFile(codePath);
|
|
126
|
+
|
|
127
|
+
return `${kind} "${className}" has both an inline template and an external template.
|
|
128
|
+
|
|
129
|
+
Use either:
|
|
130
|
+
${filename}
|
|
131
|
+
or:
|
|
132
|
+
${filename} (template placeholder) + ${basename}.html
|
|
133
|
+
Remove one of the template definitions.`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function describeMissingTemplate(codePath) {
|
|
137
|
+
const { filename, basename, kind, className } = describeCodeFile(codePath);
|
|
138
|
+
|
|
139
|
+
return `${kind} "${className}" has a template placeholder but no external template.
|
|
140
|
+
|
|
141
|
+
Expected:
|
|
142
|
+
${filename} (template placeholder) + ${basename}.html
|
|
143
|
+
Add ${basename}.html, or replace the placeholder in ${filename} with inline markup.`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function describeOrphanHtml(htmlPath) {
|
|
147
|
+
const htmlFilename = path.basename(htmlPath);
|
|
148
|
+
const basename = htmlFilename.replace(/\.html$/, '');
|
|
149
|
+
|
|
150
|
+
return `"${htmlFilename}" has no matching component or view (expected ${basename}.ts or ${basename}.js).
|
|
151
|
+
Remove ${htmlFilename}, or add ${basename}.ts with a template placeholder to merge it into.`;
|
|
152
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { minify } from 'html-minifier-terser';
|
|
2
|
+
|
|
3
|
+
export async function minifyHTML(html) {
|
|
4
|
+
return minify(html, {
|
|
5
|
+
collapseWhitespace: true,
|
|
6
|
+
removeComments: true,
|
|
7
|
+
removeRedundantAttributes: true,
|
|
8
|
+
removeScriptTypeAttributes: true,
|
|
9
|
+
removeStyleLinkTypeAttributes: true,
|
|
10
|
+
useShortDoctype: true,
|
|
11
|
+
keepClosingSlash: true,
|
|
12
|
+
minifyCSS: true,
|
|
13
|
+
minifyJS: true,
|
|
14
|
+
});
|
|
15
|
+
}
|