@nutin/cli 1.3.1 → 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 +89 -9
- package/README.md +20 -35
- 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 +76 -25
- 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/{prompts.mjs → create-app/new-app-prompt.mjs} +6 -6
- package/lib/src/{project-generator.mjs → create-app/project-generator.mjs} +10 -8
- 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 -19
- 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 +68 -35
- 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 +9 -8
- 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 +7 -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 +2 -2
- 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 +24 -33
- 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/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 +22 -4
- 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 +40 -29
- 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 +14 -38
- package/templates/base/src/core/services/router/router.test.js.hbs +226 -2
- package/templates/base/src/core/services/router/router.ts.hbs +40 -8
- 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 -86
- 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 -40
- 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 -32
- package/lib/src/utils.mjs +0 -18
- package/lib/src/version.mjs +0 -1
- package/templates/base/builder.config.js.hbs +0 -36
- 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 -113
- 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 -131
- 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/helpers/focus-trap.helper.ts.hbs +0 -197
- package/templates/base/src/libs/popover/helpers/popover-dom.helper.ts.hbs +0 -75
- package/templates/base/src/libs/popover/popover.ts.hbs +0 -138
- package/templates/base/src/libs/snackbar/snackbar.ts.hbs +0 -75
- package/templates/base/src/styles/base/_functions.scss.hbs +0 -9
- package/templates/base/src/styles/core/_anchors.scss.hbs +0 -10
- package/templates/base/src/styles/core/_index.scss.hbs +0 -6
- package/templates/base/src/styles/core/_popover.scss.hbs +0 -96
- package/templates/base/src/styles/core/_snackbar.scss.hbs +0 -50
- 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 -254
- 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
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import {
|
|
3
|
+
registerTestGlobals, getTestFiles, runQueuedTests, setCurrentTestFile, print,
|
|
4
|
+
startCoverage, stopCoverage, buildCoverageReport, printCoverageReport, maybeWriteUncoveredReport,
|
|
5
|
+
writeSummaryReport, setupJsdom, teardownJsdom,
|
|
6
|
+
} from './core/index.js';
|
|
7
|
+
import config from '#root/nutin.config.js';
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const coverageEnabled = args.includes('--coverage') || Boolean(config.testinNutin.coverage?.enabled);
|
|
11
|
+
const verboseEnabled = args.includes('--verbose');
|
|
12
|
+
const fileFilters = args.filter(arg => arg !== '--coverage' && arg !== '--verbose');
|
|
13
|
+
|
|
14
|
+
// Tests import and run the compiled build output (dist/src/)
|
|
15
|
+
// and has no sourcemap back to the .ts sources
|
|
16
|
+
const origins = [];
|
|
17
|
+
const coverageDirs = [];
|
|
18
|
+
|
|
19
|
+
if (config.testinNutin.includeFramework) {
|
|
20
|
+
const frameworkDir = path.join('src', 'core');
|
|
21
|
+
origins.push(frameworkDir);
|
|
22
|
+
if (coverageEnabled) coverageDirs.push(path.join('dist', frameworkDir));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (config.testinNutin.includeApp) {
|
|
26
|
+
const appDir = path.join('src', 'app');
|
|
27
|
+
origins.push(appDir);
|
|
28
|
+
if (coverageEnabled) coverageDirs.push(path.join('dist', appDir));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (config.testinNutin.includeTools) origins.push('tools');
|
|
32
|
+
|
|
33
|
+
registerTestGlobals();
|
|
34
|
+
|
|
35
|
+
let testFiles = origins.flatMap(
|
|
36
|
+
origin => getTestFiles(path.join('..', origin))
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (fileFilters.length) {
|
|
40
|
+
testFiles = testFiles.filter(file => fileFilters.some(f => file.includes(f)));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function loadTestFile(file) {
|
|
44
|
+
setupJsdom();
|
|
45
|
+
setCurrentTestFile(file);
|
|
46
|
+
await import(file);
|
|
47
|
+
teardownJsdom();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function runCoverage(testResults) {
|
|
51
|
+
let scopedResults = await stopCoverage(coverageDirs);
|
|
52
|
+
if (fileFilters.length) {
|
|
53
|
+
scopedResults = scopedResults.filter(({ filePath }) => fileFilters.some(f => filePath.includes(f)));
|
|
54
|
+
}
|
|
55
|
+
const report = buildCoverageReport(scopedResults);
|
|
56
|
+
const globals = printCoverageReport(report);
|
|
57
|
+
writeSummaryReport(report, globals, testResults);
|
|
58
|
+
await maybeWriteUncoveredReport(report);
|
|
59
|
+
|
|
60
|
+
const threshold = config.testinNutin.coverage?.threshold;
|
|
61
|
+
if (typeof threshold === 'number') {
|
|
62
|
+
const misses = Object.entries({
|
|
63
|
+
lines: globals.globalLinesPct,
|
|
64
|
+
functions: globals.globalFunctionsPct,
|
|
65
|
+
branches: globals.globalBranchesPct,
|
|
66
|
+
}).filter(([, pct]) => pct < threshold);
|
|
67
|
+
|
|
68
|
+
if (misses.length) {
|
|
69
|
+
print.boldError(`\n✗ Coverage below threshold (${threshold}%): ${misses.map(([k, pct]) => `${k} ${pct.toFixed(1)}%`).join(', ')}\n`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function runTests() {
|
|
76
|
+
if (verboseEnabled) print.head('Test suites :');
|
|
77
|
+
|
|
78
|
+
if (coverageEnabled) await startCoverage();
|
|
79
|
+
|
|
80
|
+
for (const file of testFiles) {
|
|
81
|
+
try {
|
|
82
|
+
await loadTestFile(file);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
print.boldError(`✗ ${file} failed to load:`);
|
|
85
|
+
print.grayError(error.stack);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const testResults = await runQueuedTests();
|
|
90
|
+
|
|
91
|
+
if (testResults.failed > 0) process.exitCode = 1;
|
|
92
|
+
|
|
93
|
+
if (coverageEnabled) await runCoverage(testResults);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
runTests().catch((err) => {
|
|
97
|
+
print.boldError(`Unexpected error: ${err.message}`);
|
|
98
|
+
print.grayError(err.stack);
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import { exec } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { print } from './core/index.js'
|
|
5
|
+
|
|
6
|
+
const watcher = chokidar.watch(['src', 'test', 'unit', 'e2e'], {
|
|
7
|
+
ignored: /(^|[/\\])\../,
|
|
8
|
+
persistent: true,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
print.boldHead('Watching for test changes...')
|
|
12
|
+
|
|
13
|
+
watcher.on('error', (err) => {
|
|
14
|
+
print.boldError(`\nWatcher error: ${err.message}`);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
let isRunning = false;
|
|
18
|
+
let runTimeout = null;
|
|
19
|
+
|
|
20
|
+
watcher.on('change', (filePath) => {
|
|
21
|
+
if (runTimeout) clearTimeout(runTimeout);
|
|
22
|
+
|
|
23
|
+
runTimeout = setTimeout(() => {
|
|
24
|
+
if (isRunning) return;
|
|
25
|
+
isRunning = true;
|
|
26
|
+
|
|
27
|
+
console.clear();
|
|
28
|
+
print.boldInfo(`\n🔄 File changed: ${path.relative(process.cwd(), filePath)}\n`);
|
|
29
|
+
|
|
30
|
+
exec('node --experimental-modules testin-nutin/runner.js', { maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
31
|
+
if (stdout) process.stdout.write(stdout);
|
|
32
|
+
if (stderr) process.stderr.write(stderr);
|
|
33
|
+
if (err) print.boldError(`\nTest run failed: ${err.message}`);
|
|
34
|
+
isRunning = false;
|
|
35
|
+
});
|
|
36
|
+
}, 100);
|
|
37
|
+
});
|
|
@@ -5,20 +5,20 @@ export function getLastWord(input) {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function capitalized(input) {
|
|
8
|
-
|
|
8
|
+
const formatted = input.replace(
|
|
9
9
|
/\w*/g,
|
|
10
10
|
function(txt) {
|
|
11
11
|
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
|
12
12
|
})
|
|
13
|
-
|
|
13
|
+
return formatted.replace(/,/g, '');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function kebabCased(input) {
|
|
17
|
-
|
|
17
|
+
return input.replace(/[A-Z][a-z]*/g, str => str.toLowerCase() + ' ').trim().replace(/ /g, '-').replace(/,/g, '');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function camelCased(input) {
|
|
21
|
-
|
|
21
|
+
return input
|
|
22
22
|
.split('-')
|
|
23
23
|
.map((word, index) =>
|
|
24
24
|
index === 0
|
|
@@ -36,10 +36,10 @@ export function pascalCased(input) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export function allFormats(input) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
return {
|
|
40
|
+
kebab: kebabCased(input),
|
|
41
|
+
pascal: pascalCased(input),
|
|
42
|
+
camel: camelCased(input),
|
|
43
|
+
capitalized: capitalized(input)
|
|
44
|
+
}
|
|
45
45
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getLastWord, capitalized, kebabCased, camelCased, pascalCased, allFormats } from './format.js';
|
|
2
|
+
|
|
3
|
+
describe('format', () => {
|
|
4
|
+
it('getLastWord returns the last non-empty path segment', () => {
|
|
5
|
+
expect(getLastWord('a/b/c')).toBe('c');
|
|
6
|
+
expect(getLastWord('a/b/c/')).toBe('c');
|
|
7
|
+
expect(getLastWord('single')).toBe('single');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('getLastWord returns an empty string for non-string input', () => {
|
|
11
|
+
expect(getLastWord(123)).toBe('');
|
|
12
|
+
expect(getLastWord(undefined)).toBe('');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('capitalized title-cases each word and strips commas', () => {
|
|
16
|
+
expect(capitalized('hello world')).toBe('Hello World');
|
|
17
|
+
expect(capitalized('HELLO, WORLd')).toBe('Hello World');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('kebabCased splits on capital letters', () => {
|
|
21
|
+
expect(kebabCased('HelloWorld')).toBe('hello-world');
|
|
22
|
+
// leading lowercase run isn't matched by the [A-Z][a-z]* regex, so it
|
|
23
|
+
// sticks to the next captured word instead of being split off on its own
|
|
24
|
+
expect(kebabCased('myComponentName')).toBe('mycomponent-name');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('camelCased joins kebab-case segments with an initial lowercase', () => {
|
|
28
|
+
expect(camelCased('my-component-name')).toBe('myComponentName');
|
|
29
|
+
expect(camelCased('single')).toBe('single');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('pascalCased joins kebab-case segments with every segment capitalized', () => {
|
|
33
|
+
expect(pascalCased('my-component-name')).toBe('MyComponentName');
|
|
34
|
+
expect(pascalCased('single')).toBe('Single');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('allFormats returns all four casings for the same input', () => {
|
|
38
|
+
expect(allFormats('my-component')).toEqual({
|
|
39
|
+
kebab: 'my-component',
|
|
40
|
+
pascal: 'MyComponent',
|
|
41
|
+
camel: 'myComponent',
|
|
42
|
+
capitalized: 'My-Component',
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -16,7 +16,12 @@ export function getFilesRecursive(dir, extension) {
|
|
|
16
16
|
|
|
17
17
|
function findFilesRecursively(currentDir) {
|
|
18
18
|
const files = [];
|
|
19
|
-
|
|
19
|
+
let items;
|
|
20
|
+
try {
|
|
21
|
+
items = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
22
|
+
} catch (err) {
|
|
23
|
+
throw new Error(`Failed to read directory "${currentDir}": ${err.message}`, { cause: err });
|
|
24
|
+
}
|
|
20
25
|
|
|
21
26
|
for (const item of items) {
|
|
22
27
|
const fullPath = path.join(currentDir, item.name);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { getFilesRecursive } from './get-files-recursive.js';
|
|
5
|
+
|
|
6
|
+
describe('getFilesRecursive', () => {
|
|
7
|
+
let tmpDir;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nutin-get-files-recursive-'));
|
|
11
|
+
fs.mkdirSync(path.join(tmpDir, 'nested'), { recursive: true });
|
|
12
|
+
fs.writeFileSync(path.join(tmpDir, 'a.js'), '');
|
|
13
|
+
fs.writeFileSync(path.join(tmpDir, 'b.css'), '');
|
|
14
|
+
fs.writeFileSync(path.join(tmpDir, 'nested', 'c.js'), '');
|
|
15
|
+
fs.writeFileSync(path.join(tmpDir, 'nested', 'd.txt'), '');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('recursively collects files matching a single extension', () => {
|
|
23
|
+
const files = getFilesRecursive(tmpDir, '.js').sort();
|
|
24
|
+
expect(files).toEqual([
|
|
25
|
+
path.join(tmpDir, 'a.js'),
|
|
26
|
+
path.join(tmpDir, 'nested', 'c.js'),
|
|
27
|
+
].sort());
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('accepts an extension without a leading dot', () => {
|
|
31
|
+
const files = getFilesRecursive(tmpDir, 'js').sort();
|
|
32
|
+
expect(files).toEqual([
|
|
33
|
+
path.join(tmpDir, 'a.js'),
|
|
34
|
+
path.join(tmpDir, 'nested', 'c.js'),
|
|
35
|
+
].sort());
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('accepts an array of extensions', () => {
|
|
39
|
+
const files = getFilesRecursive(tmpDir, ['.css', '.txt']).sort();
|
|
40
|
+
expect(files).toEqual([
|
|
41
|
+
path.join(tmpDir, 'b.css'),
|
|
42
|
+
path.join(tmpDir, 'nested', 'd.txt'),
|
|
43
|
+
].sort());
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('returns an empty array when the directory does not exist', () => {
|
|
47
|
+
expect(getFilesRecursive(path.join(tmpDir, 'does-not-exist'), '.js')).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { getRelToCore } from './get-rel-to-core.js';
|
|
3
|
+
|
|
4
|
+
describe('getRelToCore', () => {
|
|
5
|
+
it('adds one "../" per path segment before core/index.js', () => {
|
|
6
|
+
const targetPath = ['src', 'app', 'components', 'widget'].join(path.sep);
|
|
7
|
+
expect(getRelToCore(targetPath)).toBe('../../../core/index.js');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('adds no "../" for a single-segment path', () => {
|
|
11
|
+
expect(getRelToCore('widget')).toBe('core/index.js');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('scales with the number of segments', () => {
|
|
15
|
+
const targetPath = ['a', 'b'].join(path.sep);
|
|
16
|
+
expect(getRelToCore(targetPath)).toBe('../core/index.js');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -3,8 +3,6 @@ export * from './format.js';
|
|
|
3
3
|
export * from './get-files-recursive.js';
|
|
4
4
|
export * from './run-command.js';
|
|
5
5
|
export * from './run-script.js';
|
|
6
|
-
export * from './export-flags.js';
|
|
7
6
|
export * from './prompt-boolean.js';
|
|
8
|
-
{{#if i18n}}
|
|
9
7
|
export * from './languages.js';
|
|
10
|
-
|
|
8
|
+
export * from './error-exit.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { errorExit } from './error-exit.js';
|
|
4
|
+
|
|
5
|
+
const langsPath = path.join(process.cwd(), 'config', 'languages.json');
|
|
6
|
+
|
|
7
|
+
let langs;
|
|
8
|
+
try {
|
|
9
|
+
langs = JSON.parse(fs.readFileSync(langsPath, 'utf-8'));
|
|
10
|
+
} catch (err) {
|
|
11
|
+
errorExit(new Error(`config/languages.json is missing or malformed: ${err.message}`), 'languages');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const LANGUAGES = langs.languages;
|
|
15
|
+
export const DEFAULT_LANGUAGE = langs.defaultLanguage;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { LANGUAGES, DEFAULT_LANGUAGE } from './languages.js';
|
|
2
|
+
|
|
3
|
+
describe('languages', () => {
|
|
4
|
+
it('reads LANGUAGES from config/languages.json', () => {
|
|
5
|
+
expect(Array.isArray(LANGUAGES)).toBeTruthy();
|
|
6
|
+
expect(LANGUAGES.length).toBeGreaterThan(0);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('reads DEFAULT_LANGUAGE from config/languages.json', () => {
|
|
10
|
+
expect(typeof DEFAULT_LANGUAGE).toBe('string');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('includes the default language among the supported languages', () => {
|
|
14
|
+
expect(LANGUAGES).toContain(DEFAULT_LANGUAGE);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -12,20 +12,26 @@ export const chalk = {
|
|
|
12
12
|
boldYellow: (s) => `\x1b[1;33m${s}\x1b[0m`,
|
|
13
13
|
boldCyan: (s) => `\x1b[1;36m${s}\x1b[0m`,
|
|
14
14
|
boldPurple: (s) => `\x1b[1;35m${s}\x1b[0m`,
|
|
15
|
+
boldGray: (s) => `\x1b[1;90m${s}\x1b[0m`,
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
export const print = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
boldInfo: (s) => console.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
head: (s) => console.info(`${chalk.purple(s)}`),
|
|
20
|
+
boldHead : (s) => console.info(`${chalk.boldPurple(s)}`),
|
|
21
|
+
section: (s) => console.info(`${chalk.yellow(s)}`),
|
|
22
|
+
boldSection: (s) => console.info(`${chalk.boldYellow(s)}`),
|
|
23
|
+
info: (s) => console.info(`${chalk.green(s)}`),
|
|
24
|
+
boldInfo: (s) => console.info(`${chalk.boldGreen(s)}`),
|
|
25
|
+
success: (s) => console.log(`${chalk.cyan(s)}`),
|
|
26
|
+
boldSuccess: (s) => console.log(`${chalk.boldCyan(s)}`),
|
|
27
|
+
gray: (s) => console.info(`${chalk.gray(s)}`),
|
|
28
|
+
boldGray: (s) => console.info(`${chalk.boldGray(s)}`),
|
|
29
|
+
blue: (s) => console.info(`${chalk.blue(s)}`),
|
|
30
|
+
boldBlue: (s) => console.info(`${chalk.boldBlue(s)}`),
|
|
31
|
+
error: (s) => console.error(`${chalk.red(s)}`),
|
|
32
|
+
boldError: (s) => console.error(`${chalk.boldRed(s)}`),
|
|
33
|
+
grayError: (s) => console.error(`${chalk.boldGray(s)}`),
|
|
34
|
+
warn: (s) => console.warn(`${chalk.yellow(s)}`),
|
|
35
|
+
boldWarn: (s) => console.warn(`${chalk.yellow(s)}`),
|
|
36
|
+
debug: (s) => console.debug(`${chalk.gray(s)}`),
|
|
31
37
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { print, chalk } from './print.js';
|
|
2
|
+
|
|
3
|
+
describe('print', () => {
|
|
4
|
+
let infoSpy;
|
|
5
|
+
let logSpy;
|
|
6
|
+
let errorSpy;
|
|
7
|
+
let warnSpy;
|
|
8
|
+
let debugSpy;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
infoSpy = spyOn(console, 'info').andCallFake(() => {});
|
|
12
|
+
logSpy = spyOn(console, 'log').andCallFake(() => {});
|
|
13
|
+
errorSpy = spyOn(console, 'error').andCallFake(() => {});
|
|
14
|
+
warnSpy = spyOn(console, 'warn').andCallFake(() => {});
|
|
15
|
+
debugSpy = spyOn(console, 'debug').andCallFake(() => {});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
infoSpy.restore();
|
|
20
|
+
logSpy.restore();
|
|
21
|
+
errorSpy.restore();
|
|
22
|
+
warnSpy.restore();
|
|
23
|
+
debugSpy.restore();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('info() writes to console.info, colorized green', () => {
|
|
27
|
+
print.info('hello');
|
|
28
|
+
expect(infoSpy.callCount).toBe(1);
|
|
29
|
+
expect(infoSpy.lastCall).toEqual([chalk.green('hello')]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('success() writes to console.log, colorized cyan', () => {
|
|
33
|
+
print.success('done');
|
|
34
|
+
expect(logSpy.callCount).toBe(1);
|
|
35
|
+
expect(logSpy.lastCall).toEqual([chalk.cyan('done')]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('error() writes to console.error, colorized red', () => {
|
|
39
|
+
print.error('oops');
|
|
40
|
+
expect(errorSpy.callCount).toBe(1);
|
|
41
|
+
expect(errorSpy.lastCall).toEqual([chalk.red('oops')]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('warn() writes to console.warn, colorized yellow', () => {
|
|
45
|
+
print.warn('careful');
|
|
46
|
+
expect(warnSpy.callCount).toBe(1);
|
|
47
|
+
expect(warnSpy.lastCall).toEqual([chalk.yellow('careful')]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('debug() writes to console.debug, colorized gray', () => {
|
|
51
|
+
print.debug('trace');
|
|
52
|
+
expect(debugSpy.callCount).toBe(1);
|
|
53
|
+
expect(debugSpy.lastCall).toEqual([chalk.gray('trace')]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('chalk wraps text in the matching ANSI escape codes', () => {
|
|
57
|
+
expect(chalk.red('x')).toBe('\x1b[31mx\x1b[0m');
|
|
58
|
+
expect(chalk.boldGreen('x')).toBe('\x1b[1;32mx\x1b[0m');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -7,9 +7,16 @@ export const promptBoolean = (question) => {
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
return new Promise((resolve) => {
|
|
10
|
+
let answered = false;
|
|
11
|
+
|
|
10
12
|
rl.question(`${question} (y/n): `, (answer) => {
|
|
13
|
+
answered = true;
|
|
11
14
|
rl.close();
|
|
12
15
|
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
13
16
|
});
|
|
17
|
+
|
|
18
|
+
rl.on('close', () => {
|
|
19
|
+
if (!answered) resolve(false);
|
|
20
|
+
});
|
|
14
21
|
});
|
|
15
22
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import { promptBoolean } from './prompt-boolean.js';
|
|
3
|
+
|
|
4
|
+
describe('promptBoolean', () => {
|
|
5
|
+
let createInterfaceSpy;
|
|
6
|
+
let answer;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
createInterfaceSpy = spyOn(readline, 'createInterface').andCallFake(() => ({
|
|
10
|
+
question: (_question, callback) => callback(answer),
|
|
11
|
+
close: () => {},
|
|
12
|
+
on: () => {},
|
|
13
|
+
}));
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
createInterfaceSpy.restore();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('resolves true for "y"', async () => {
|
|
21
|
+
answer = 'y';
|
|
22
|
+
expect(await promptBoolean('continue?')).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('resolves true for "yes", case-insensitively', async () => {
|
|
26
|
+
answer = 'YES';
|
|
27
|
+
expect(await promptBoolean('continue?')).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('resolves false for "n"', async () => {
|
|
31
|
+
answer = 'n';
|
|
32
|
+
expect(await promptBoolean('continue?')).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('resolves false for anything else', async () => {
|
|
36
|
+
answer = 'maybe';
|
|
37
|
+
expect(await promptBoolean('continue?')).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('resolves false instead of hanging if stdin closes before an answer', async () => {
|
|
41
|
+
createInterfaceSpy.restore();
|
|
42
|
+
let closeHandler;
|
|
43
|
+
createInterfaceSpy = spyOn(readline, 'createInterface').andCallFake(() => ({
|
|
44
|
+
question: () => {},
|
|
45
|
+
close: () => {},
|
|
46
|
+
on: (event, handler) => { if (event === 'close') closeHandler = handler; },
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
const resultPromise = promptBoolean('continue?');
|
|
50
|
+
closeHandler();
|
|
51
|
+
|
|
52
|
+
expect(await resultPromise).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
|
|
3
|
-
export function runCommand(command, args = []) {
|
|
3
|
+
export function runCommand(command, args = [], options = {}) {
|
|
4
4
|
return new Promise((resolve, reject) => {
|
|
5
|
-
const child = spawn(command, args, { stdio: 'inherit', shell: true });
|
|
5
|
+
const child = spawn([command, ...args].join(' '), { stdio: 'inherit', shell: true, ...options });
|
|
6
|
+
|
|
7
|
+
child.on('error', (err) => reject(err));
|
|
6
8
|
|
|
7
9
|
child.on('close', (code) => {
|
|
8
10
|
if (code === 0) resolve();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { runCommand } from './run-command.js';
|
|
2
|
+
|
|
3
|
+
describe('runCommand', () => {
|
|
4
|
+
it('resolves when the child process exits with code 0', async () => {
|
|
5
|
+
const result = await runCommand('node', ['-e', 'process.exitCode=0']);
|
|
6
|
+
expect(result).toBe(undefined);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('rejects with the exit code when the child process exits non-zero', async () => {
|
|
10
|
+
let error;
|
|
11
|
+
try {
|
|
12
|
+
await runCommand('node', ['-e', 'process.exitCode=7']);
|
|
13
|
+
} catch (err) {
|
|
14
|
+
error = err;
|
|
15
|
+
}
|
|
16
|
+
expect(error).toBeDefined();
|
|
17
|
+
expect(error.message).toContain('7');
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -4,17 +4,28 @@ import process from 'process';
|
|
|
4
4
|
import { print } from './index.js';
|
|
5
5
|
|
|
6
6
|
export function runScript(path, message) {
|
|
7
|
-
print.boldSection(
|
|
7
|
+
if (!message) print.boldSection(`Running ${path}...`);
|
|
8
|
+
else print.boldSection(`${message}`);
|
|
8
9
|
|
|
9
|
-
const args = [resolve(path)];
|
|
10
|
+
const args = [resolve(path), '--silent'];
|
|
10
11
|
|
|
11
|
-
const result = spawnSync('node', args,
|
|
12
|
+
const result = spawnSync('node', args,
|
|
12
13
|
{
|
|
13
14
|
stdio: 'inherit',
|
|
14
15
|
env: { ...process.env },
|
|
15
16
|
}
|
|
16
17
|
);
|
|
17
18
|
|
|
19
|
+
if (result.error) {
|
|
20
|
+
print.boldError(`\nScript ${path} could not be started: ${result.error.message}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (result.signal) {
|
|
25
|
+
print.boldError(`\nScript ${path} was terminated by signal ${result.signal}.`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
if (result.status !== 0) {
|
|
19
30
|
print.boldError(`\nScript ${path} failed.`);
|
|
20
31
|
process.exit(result.status ?? 1);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { runScript } from './run-script.js';
|
|
5
|
+
|
|
6
|
+
describe('runScript', () => {
|
|
7
|
+
let tmpDir;
|
|
8
|
+
let exitSpy;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nutin-run-script-'));
|
|
12
|
+
exitSpy = spyOn(process, 'exit').andCallFake(() => {});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
exitSpy.restore();
|
|
17
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('does not call process.exit when the script exits 0', () => {
|
|
21
|
+
const scriptPath = path.join(tmpDir, 'ok.js');
|
|
22
|
+
fs.writeFileSync(scriptPath, '// no-op, exits 0 by default\n');
|
|
23
|
+
|
|
24
|
+
// runScript prints its status message via console.info as part of its real behavior
|
|
25
|
+
silenceConsole('info', () => runScript(scriptPath, 'running ok script'));
|
|
26
|
+
|
|
27
|
+
expect(exitSpy.callCount).toBe(0);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('calls process.exit with the script status when it exits non-zero', () => {
|
|
31
|
+
const scriptPath = path.join(tmpDir, 'fail.js');
|
|
32
|
+
fs.writeFileSync(scriptPath, 'process.exitCode = 3;\n');
|
|
33
|
+
|
|
34
|
+
// runScript prints its status message and a failure message via console.info/console.error
|
|
35
|
+
silenceConsole(['info', 'error'], () => runScript(scriptPath, 'running failing script'));
|
|
36
|
+
|
|
37
|
+
expect(exitSpy.callCount).toBe(1);
|
|
38
|
+
expect(exitSpy.lastCall).toEqual([3]);
|
|
39
|
+
});
|
|
40
|
+
});
|