@warlock.js/core 5.2.2 → 5.2.4
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 +13 -0
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs.map +1 -1
- package/esm/benchmark/benchmark-snapshots.d.mts.map +1 -1
- package/esm/benchmark/benchmark.d.mts.map +1 -1
- package/esm/benchmark/channels/console.channel.mjs.map +1 -1
- package/esm/benchmark/channels/noop.channel.d.mts.map +1 -1
- package/esm/benchmark/profiler.d.mts.map +1 -1
- package/esm/benchmark/profiler.mjs.map +1 -1
- package/esm/bootstrap/setup.d.mts.map +1 -1
- package/esm/bootstrap/setup.mjs.map +1 -1
- package/esm/bootstrap.d.mts.map +1 -1
- package/esm/cache/database-cache-driver.d.mts.map +1 -1
- package/esm/cache/database-cache-driver.mjs.map +1 -1
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/cli-commands.utils.mjs.map +1 -1
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -1
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/name-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/prompt.mjs.map +1 -1
- package/esm/cli/commands/routes/diff-page-routes.mjs.map +1 -1
- package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -1
- package/esm/cli/commands/routes/route-row.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-diff.action.mjs.map +1 -1
- package/esm/cli/commands/storage-put.action.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/commands-loader.mjs.map +1 -1
- package/esm/cli/start.mjs.map +1 -1
- package/esm/cli/string-similarity.mjs.map +1 -1
- package/esm/commands/cli-command.mjs.map +1 -1
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/config/config-setter.d.mts.map +1 -1
- package/esm/config/load-config-files.mjs.map +1 -1
- package/esm/connectors/access-connector.d.mts.map +1 -1
- package/esm/connectors/ai-connector.d.mts.map +1 -1
- package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -1
- package/esm/connectors/assert-unique-connector-names.mjs.map +1 -1
- package/esm/connectors/base-connector.d.mts.map +1 -1
- package/esm/connectors/base-connector.mjs.map +1 -1
- package/esm/connectors/cache-connector.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/database-connector.d.mts.map +1 -1
- package/esm/connectors/describe-server-address.mjs.map +1 -1
- package/esm/connectors/herald-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.d.mts.map +1 -1
- package/esm/connectors/mail-connector.d.mts.map +1 -1
- package/esm/connectors/notifications-connector.d.mts.map +1 -1
- package/esm/connectors/register-configured-connectors.mjs.map +1 -1
- package/esm/connectors/socket-connector.d.mts.map +1 -1
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/connectors/storage.connector.d.mts.map +1 -1
- package/esm/container/index.d.mts.map +1 -1
- package/esm/database/create-database-action.mjs.map +1 -1
- package/esm/database/drop-tables-action.mjs.map +1 -1
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/database/seeds/seed-records-table-migration.mjs.map +1 -1
- package/esm/database/seeds/seeders.manager.mjs.map +1 -1
- package/esm/database/seeds/seeds-table-migration.mjs.map +1 -1
- package/esm/dev-server/check-for-updates.mjs.map +1 -1
- package/esm/dev-server/dependency-graph.d.mts.map +1 -1
- package/esm/dev-server/dependency-graph.mjs.map +1 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/file-manager.d.mts.map +1 -1
- package/esm/dev-server/file-manager.mjs.map +1 -1
- package/esm/dev-server/file-operations.d.mts.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/typescript-health-checker.mjs.map +1 -1
- package/esm/dev-server/health-checker/file-health-result.d.mts.map +1 -1
- package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
- package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
- package/esm/dev-server/layer-executor.mjs.map +1 -1
- package/esm/dev-server/loader/hook-thread.d.mts.map +1 -1
- package/esm/dev-server/loader/own-resolver.mjs.map +1 -1
- package/esm/dev-server/loader/register-loader.mjs.map +1 -1
- package/esm/dev-server/loader/resolve-hook.mjs.map +1 -1
- package/esm/dev-server/loader/source-slug.mjs.map +1 -1
- package/esm/dev-server/loader/transpile-cache.mjs.map +1 -1
- package/esm/dev-server/loader/version-registry.mjs.map +1 -1
- package/esm/dev-server/manifest-manager.d.mts.map +1 -1
- package/esm/dev-server/manifest-manager.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/package-json-manager.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/ready-block.mjs.map +1 -1
- package/esm/dev-server/special-files-collector.d.mts.map +1 -1
- package/esm/dev-server/special-files-collector.mjs.map +1 -1
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/dev-server/utils.mjs.map +1 -1
- package/esm/encryption/encrypt.mjs.map +1 -1
- package/esm/encryption/hash.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs +28 -11
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/features/shadcn.feature.mjs.map +1 -1
- package/esm/generations/features/shared/migration-timestamp.mjs.map +1 -1
- package/esm/generations/features/tailwind.feature.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs +8 -7
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/generations/stubs.mjs +49 -33
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/boot-port-preflight.d.mts.map +1 -1
- package/esm/http/context/request-context.d.mts.map +1 -1
- package/esm/http/context/request-context.mjs.map +1 -1
- package/esm/http/context/request-memo.d.mts.map +1 -1
- package/esm/http/context/request-memo.mjs.map +1 -1
- package/esm/http/createHttpApplication.d.mts.map +1 -1
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/health.d.mts.map +1 -1
- package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
- package/esm/http/ready-report.d.mts.map +1 -1
- package/esm/http/request-controller.d.mts.map +1 -1
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs.map +1 -1
- package/esm/http/uploaded-file.d.mts.map +1 -1
- package/esm/http/uploaded-file.mjs.map +1 -1
- package/esm/http/uploads-config.d.mts.map +1 -1
- package/esm/image/image.d.mts.map +1 -1
- package/esm/image/image.mjs.map +1 -1
- package/esm/logger/logger.mjs.map +1 -1
- package/esm/mail/config.d.mts.map +1 -1
- package/esm/mail/events.d.mts.map +1 -1
- package/esm/mail/mail.d.mts.map +1 -1
- package/esm/mail/mail.mjs.map +1 -1
- package/esm/mail/mailer-pool.d.mts.map +1 -1
- package/esm/mail/mailer-pool.mjs.map +1 -1
- package/esm/mail/send-mail.mjs.map +1 -1
- package/esm/mail/test-mailbox.d.mts.map +1 -1
- package/esm/mail/test-mailbox.mjs.map +1 -1
- package/esm/production/assert-generated-imports.mjs.map +1 -1
- package/esm/production/build-app-production.mjs.map +1 -1
- package/esm/production/dist-build-manifest.mjs.map +1 -1
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs.map +1 -1
- package/esm/production/promote-dist.mjs.map +1 -1
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-adapter.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-adapter.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
- package/esm/repositories/repository.manager.d.mts.map +1 -1
- package/esm/repositories/repository.manager.mjs.map +1 -1
- package/esm/resource/register-resource.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.mjs.map +1 -1
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/restful/restful.d.mts.map +1 -1
- package/esm/restful/restful.mjs.map +1 -1
- package/esm/router/log-request-lifecycle.mjs.map +1 -1
- package/esm/router/normalize-route-path.d.mts.map +1 -1
- package/esm/router/positional-handler-diagnostics.d.mts.map +1 -1
- package/esm/router/positional-handler-diagnostics.mjs.map +1 -1
- package/esm/router/route-registry.d.mts.map +1 -1
- package/esm/router/route-registry.mjs.map +1 -1
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs.map +1 -1
- package/esm/socket/utils.d.mts.map +1 -1
- package/esm/storage/config.d.mts.map +1 -1
- package/esm/storage/context/storage-driver-context.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
- package/esm/storage/drivers/do-spaces-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.mjs.map +1 -1
- package/esm/storage/drivers/r2-driver.d.mts.map +1 -1
- package/esm/storage/scoped-storage.d.mts.map +1 -1
- package/esm/storage/scoped-storage.mjs.map +1 -1
- package/esm/storage/storage-file.d.mts.map +1 -1
- package/esm/storage/storage-file.mjs.map +1 -1
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/utils/safe-fetch.mjs.map +1 -1
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/test-connectors-selection.mjs.map +1 -1
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-setup-timeout.mjs.map +1 -1
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/updater/package-manager.mjs +15 -1
- package/esm/updater/package-manager.mjs.map +1 -1
- package/esm/updater/update-warlock-packages.mjs.map +1 -1
- package/esm/use-cases/use-case-broadcast.d.mts.map +1 -1
- package/esm/use-cases/use-case-events.d.mts.map +1 -1
- package/esm/use-cases/use-case-pipeline.d.mts.map +1 -1
- package/esm/use-cases/use-case.d.mts.map +1 -1
- package/esm/use-cases/use-cases-registry.d.mts.map +1 -1
- package/esm/use-cases/use-cases-registry.mjs.map +1 -1
- package/esm/utils/database-log.d.mts.map +1 -1
- package/esm/utils/database-log.mjs.map +1 -1
- package/esm/utils/environment.d.mts.map +1 -1
- package/esm/utils/framework-vesion.mjs.map +1 -1
- package/esm/utils/get-localized.mjs.map +1 -1
- package/esm/utils/load-environment.mjs.map +1 -1
- package/esm/utils/normalized-path.d.mts.map +1 -1
- package/esm/utils/paths.d.mts.map +1 -1
- package/esm/utils/promise-all-object.d.mts.map +1 -1
- package/esm/utils/promise-all-object.mjs.map +1 -1
- package/esm/utils/sluggable.mjs.map +1 -1
- package/esm/utils/version-compare.mjs.map +1 -1
- package/esm/validation/database/exists-except-current-id.mjs.map +1 -1
- package/esm/validation/database/exists-except-current-user.mjs.map +1 -1
- package/esm/validation/database/unique-except-current-id.mjs.map +1 -1
- package/esm/validation/plugins/localized-plugin.mjs.map +1 -1
- package/esm/validation/validators/file-validator.mjs.map +1 -1
- package/esm/vite/lower-stage3-decorators.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/llms-full.txt +4 -1
- package/llms.txt +1 -1
- package/package.json +12 -12
- package/skills/README.md +1 -1
- package/skills/write-cli-command/SKILL.md +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
> ⚠ **Versioning: `@warlock.js/*` does not follow SemVer strictly — breaking changes may ship in a minor.** This is a deliberate decision, not an oversight: the framework is pre-adoption and the cost of a major per behaviour fix currently outweighs the benefit. **Pin an exact version or a tilde range (`~4.13.0`) if you need to opt into changes rather than receive them.** Every breaking change is marked **BREAKING** in its entry and summarised in an *Upgrading* section at the top of the release. **This policy will change once the framework has consumers beyond its author.**
|
|
8
8
|
|
|
9
|
+
## 5.2.4 - 2026-09-02
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `warlock add web` now writes universal page setup to `src/web/index.register.ts` and re-exports `register()` from the page, preserving component state across React Fast Refresh edits without changing SSR or hydration registration.
|
|
14
|
+
|
|
15
|
+
## 5.2.3 - 2026-09-02
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- `warlock add` now preserves exact lockstep versions for added Warlock family packages while retaining declared ranges for third-party dependencies.
|
|
20
|
+
- `warlock add web` now emits a projection-safe `register()` hook and one `index` route identity for SSR and hydration, with deterministic form and favicon markup for a clean browser console.
|
|
21
|
+
|
|
9
22
|
## 5.2.2
|
|
10
23
|
|
|
11
24
|
### Fixed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application.d.mts","names":[],"sources":["../../../../../../../core/src/application/application.ts"],"mappings":";;;;;AAiBA;;;;KAAY,WAAA;EACV,WAAA,EAAa,WAAA;EACb,eAAA,EAAiB,eAAe;EAAf;;;AAKH;EAAd,cAAA;AAAA;;;;;;KAQU,YAAA,IAAgB,OAAA,EAAS,WAAA,YAAuB,OAAO;AAAA;AAYnE;;;;AAAgD;AAkBhD;;;;AA9BmE,KAYvD,aAAA,gBAA6B,OAAO;AAoBhD;;;;;AAAA,KAFY,gBAAA,gBAAgC,OAAO;AAAA,cAEtC,WAAA;EAwHyB;;;EAAA,gBApHb,SAAA,EAAS,IAAA;EA2OY;;;EAAA,OAtO9B,eAAA,EAAiB,eAAA;EAoVG;;;EAAA,eA/UnB,MAAA;EAViB;;;EAAA,eAejB,WAAA;EAAA;;;;EAAA,eAMA,aAAA;EA2BA;;;;;EAAA,eApBA,SAAA;EA8CG;;;;EAAA,eAxCH,cAAA;EAmEU;;;;;;EAAA,eA3DV,UAAA;EAiIM;;;;EAAA,eA3HN,YAAA;EA2L6B;;;EAAA,eAtL7B,iBAAA;EAqOU;;;EAAA,WAhOP,OAAA
|
|
1
|
+
{"version":3,"file":"application.d.mts","names":[],"sources":["../../../../../../../core/src/application/application.ts"],"mappings":";;;;;AAiBA;;;;KAAY,WAAA;EACV,WAAA,EAAa,WAAA;EACb,eAAA,EAAiB,eAAe;EAAf;;;AAKH;EAAd,cAAA;AAAA;;;;;;KAQU,YAAA,IAAgB,OAAA,EAAS,WAAA,YAAuB,OAAO;AAAA;AAYnE;;;;AAAgD;AAkBhD;;;;AA9BmE,KAYvD,aAAA,gBAA6B,OAAO;AAoBhD;;;;;AAAA,KAFY,gBAAA,gBAAgC,OAAO;AAAA,cAEtC,WAAA;EAwHyB;;;EAAA,gBApHb,SAAA,EAAS,IAAA;EA2OY;;;EAAA,OAtO9B,eAAA,EAAiB,eAAA;EAoVG;;;EAAA,eA/UnB,MAAA;EAViB;;;EAAA,eAejB,WAAA;EAAA;;;;EAAA,eAMA,aAAA;EA2BA;;;;;EAAA,eApBA,SAAA;EA8CG;;;;EAAA,eAxCH,cAAA;EAmEU;;;;;;EAAA,eA3DV,UAAA;EAiIM;;;;EAAA,eA3HN,YAAA;EA2L6B;;;EAAA,eAtL7B,iBAAA;EAqOU;;;EAAA,WAhOP,OAAA;EAiRA;;;EAAA,OA1QJ,kBAAA,CAAmB,QAAA,EAAU,eAAA;EAwRT;;;;;EAAA,WA/QhB,QAAA;EAkTA;;;;;EAAA,OAzSJ,aAAA,CAAc,IAAA;EAqUA;;;;;;;;;;;;;;EAAA,OAnTd,UAAA,CAAW,QAAA,EAAU,YAAA;;;;;SAerB,UAAA,IAAc,OAAA,CAAQ,WAAA;;;;;;;;;;;;;;;SAwBtB,UAAA,CAAW,OAAA,EAAS,WAAA;;;;;iBA+Bb,eAAA;;;;;;;;;;;;;;;;;;;;;;;;SAkCP,cAAA,CAAe,SAAA,EAAW,aAAA;;;;;;;;;;;;;;;;;;SA8BpB,oBAAA,IAAwB,OAAA;;;;;;aAgC1B,cAAA;;;;;;;;;;;;SAeJ,UAAA,CAAW,QAAA,EAAU,gBAAA;;;;;;;;;;SAmBf,gBAAA,IAAoB,OAAA;;;;;iBAmBnB,mBAAA;;;;aAWH,MAAA;;;;aAOA,WAAA,IAAe,WAAA;;;;SAOnB,cAAA,CAAe,GAAA,EAAK,WAAA;;;;aAOhB,YAAA;;;;aAOA,aAAA;;;;aAOA,MAAA;;;;aAOA,QAAA;;;;aAOA,OAAA;;;;aAOA,OAAA;;;;aAOA,WAAA;;;;aAOA,WAAA;;;;aAOA,UAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application.mjs","names":[],"sources":["../../../../../../../core/src/application/application.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport { BOOT_SIGNAL_VERSION, sendBootSignal } from \"./boot-signal\";\nimport {\n environment,\n setEnvironment,\n type Environment,\n type RuntimeStrategy,\n} from \"../utils/environment\";\nimport { getFrameworkVersion } from \"../utils/framework-vesion\";\nimport { appPath, publicPath, rootPath, srcPath, storagePath, uploadsPath } from \"../utils/paths\";\n\n/**\n * Snapshot of the global facts known the moment the application finished\n * booting: every connector (early + late) is active and all app files\n * (locales, events, main, routes) are loaded. Passed to every\n * `Application.onceBooted` listener and resolved from `Application.whenBooted`.\n */\nexport type BootContext = {\n environment: Environment;\n runtimeStrategy: RuntimeStrategy;\n /**\n * Wall-clock milliseconds the boot took. The dev server measures it; the\n * production entry omits it.\n */\n bootDurationMs?: number;\n};\n\n/**\n * A callback registered through `Application.onceBooted`. May be async — its\n * rejection is caught and logged so a single listener can never break boot or\n * the other listeners.\n */\nexport type BootListener = (context: BootContext) => void | Promise<void>;\n\n/**\n * A callback registered through `Application.onValidateBoot`. May be async —\n * unlike {@link BootListener}, its rejection is NOT caught: it aborts boot.\n *\n * Use this for configuration that makes the application WRONG rather than\n * degraded when missing (a signing secret, a required integration key) — the\n * kind of check that must fail closed. Use `onceBooted` for everything else;\n * that contract stays fire-and-forget on purpose (it is the right behaviour\n * for plugins reacting to boot, not for gating it).\n */\nexport type BootValidator = () => void | Promise<void>;\n\n/**\n * `new Error(message, { cause })` is ES2022, and this package still compiles\n * against the ES2020 lib, where `Error` is typed as taking a message only.\n * Node has supported the option since v16, so this is the type layer\n * catching up with the runtime, not a change in what the code does. Same\n * workaround as `image.ts` (see #16).\n */\ntype ErrorWithCauseConstructor = new (message: string, options: { cause: unknown }) => Error;\n\nconst ErrorWithCause = Error as ErrorWithCauseConstructor;\n\n/**\n * A callback registered through `Application.onShutdown`. Runs once, while the\n * connectors (db, cache, http) are still up, so it can release app-owned\n * resources cleanly. May be async — its rejection is caught and logged.\n */\nexport type ShutdownListener = () => void | Promise<void>;\n\nexport class Application {\n /**\n * Project start time regarding the process start time\n */\n public static readonly startedAt = new Date(Date.now() - process.uptime() * 1000);\n\n /**\n * Runtime strategy\n */\n public static runtimeStrategy: RuntimeStrategy;\n\n /**\n * Whether the application has finished booting. Flipped once by `markBooted`.\n */\n private static booted = false;\n\n /**\n * The boot context, set once booted so it can be replayed to late subscribers.\n */\n private static bootContext: BootContext | undefined;\n\n /**\n * Listeners queued before boot completed, drained in registration order by\n * `markBooted`.\n */\n private static bootListeners: BootListener[] = [];\n\n /**\n * Whether `runStartupValidators` has already run. Flipped once, so a\n * validator registered afterwards is refused instead of silently skipped\n * or run too late to matter.\n */\n private static validated = false;\n\n /**\n * Startup validators queued before `runStartupValidators` ran, drained in\n * registration order.\n */\n private static bootValidators: BootValidator[] = [];\n\n /**\n * The http port this process actually bound, once it has. Reported in the\n * readiness signal so a supervisor knows where to send its health check\n * instead of re-deriving it from config it may not be able to read.\n * Undefined for an app with no http connector — a queue worker still boots.\n */\n private static servedPort: number | undefined;\n\n /**\n * Whether the application has begun (or finished) shutting down. Flipped once\n * by `runShutdownHooks`.\n */\n private static shuttingDown = false;\n\n /**\n * Teardown callbacks, run once when shutdown begins.\n */\n private static shutdownListeners: ShutdownListener[] = [];\n\n /**\n * Get framework version\n */\n public static get version() {\n return getFrameworkVersion();\n }\n\n /**\n * Set the runtime strategy\n */\n public static setRuntimeStrategy(strategy: RuntimeStrategy) {\n this.runtimeStrategy = strategy;\n }\n\n /**\n * Whether the application has finished booting — every connector in both\n * the early and late phases is active and all app files (locales, events,\n * main, routes) have been loaded.\n */\n public static get isBooted(): boolean {\n return this.booted;\n }\n\n /**\n * Record the http port this process bound.\n *\n * @internal The http connector calls this after a successful `listen`.\n */\n public static setServedPort(port: number): void {\n this.servedPort = port;\n }\n\n /**\n * Run a callback once the application is fully booted.\n *\n * App files (`main` / `events` / `routes` / locales) are imported BEFORE the\n * late-phase connectors (http, socket) start, so a listener registered at\n * import time waits for the whole sequence to finish before it runs. If the\n * application is already booted, the callback runs on the next microtask —\n * so a late subscriber never silently misses the event.\n *\n * @example\n * Application.onceBooted(({ environment }) => {\n * log.info(\"app\", \"booted\", `ready in ${environment}`);\n * });\n */\n public static onceBooted(listener: BootListener): void {\n if (this.booted && this.bootContext) {\n const context = this.bootContext;\n void this.runBootListener(listener, context);\n\n return;\n }\n\n this.bootListeners.push(listener);\n }\n\n /**\n * Promise form of {@link onceBooted} — resolves with the boot context once\n * the application is booted, or immediately if it already is.\n */\n public static whenBooted(): Promise<BootContext> {\n if (this.booted && this.bootContext) {\n return Promise.resolve(this.bootContext);\n }\n\n return new Promise<BootContext>((resolve) => {\n this.onceBooted((context) => resolve(context));\n });\n }\n\n /**\n * Flip the boot latch and drain every queued listener. The dev server and\n * the production entry call this once, right after the late phase starts. It\n * is idempotent — a second call is a no-op, so a double-invoke can never\n * double-fire listeners.\n *\n * Also reports readiness to a supervising parent process — this is the one\n * moment in the lifecycle where \"the application is serving\" is true, so it\n * is the only honest source for `warlock start`'s success banner. Sent before\n * the listeners run: a slow or hanging `onceBooted` listener must not delay\n * the parent's view of a server that is already accepting requests.\n *\n * @internal Framework entry points call this; application code must not.\n */\n public static markBooted(context: BootContext): void {\n if (this.booted) {\n return;\n }\n\n this.booted = true;\n this.bootContext = context;\n\n sendBootSignal({\n type: \"warlock:ready\",\n version: BOOT_SIGNAL_VERSION,\n pid: process.pid,\n at: new Date().toISOString(),\n environment: context.environment,\n runtimeStrategy: context.runtimeStrategy,\n bootDurationMs: context.bootDurationMs,\n port: this.servedPort,\n });\n\n const listeners = this.bootListeners;\n this.bootListeners = [];\n\n for (const listener of listeners) {\n void this.runBootListener(listener, context);\n }\n }\n\n /**\n * Invoke a single boot listener, isolating its failure so neither boot nor\n * the remaining listeners are affected.\n */\n private static async runBootListener(\n listener: BootListener,\n context: BootContext,\n ): Promise<void> {\n try {\n await listener(context);\n } catch (error) {\n log.error(\"application\", \"booted-listener\", error as Error);\n }\n }\n\n /**\n * Register a startup validator — distinct from {@link onceBooted}. A\n * rejecting validator aborts boot instead of being caught and logged, so\n * this is where configuration that must fail closed belongs (a signing\n * secret, a required integration key), not `onceBooted`.\n *\n * Must be registered before the framework calls `runStartupValidators` —\n * typically at module scope in `src/app/main.ts`, the same place a\n * `BootListener` would be registered. Registering one after validation has\n * already run throws: by then a rejection could no longer stop the app\n * from serving, so running it late would silently defeat the guarantee\n * this hook exists to give.\n *\n * @example\n * Application.onValidateBoot(() => {\n * if (!env(\"JWT_SECRET\")) {\n * throw new Error(\n * \"JWT_SECRET is not set. Sessions cannot be signed without it. \" +\n * \"Generate one with `yarn jwt` and put it in .env.\",\n * );\n * }\n * });\n */\n public static onValidateBoot(validator: BootValidator): void {\n if (this.validated) {\n throw new Error(\n \"Application.onValidateBoot(): startup validators already ran — \" +\n \"this validator was registered too late to block boot. Register it \" +\n \"before the framework calls Application.runStartupValidators(), \" +\n \"typically at module scope in `src/app/main.ts`.\",\n );\n }\n\n this.bootValidators.push(validator);\n }\n\n /**\n * Run every registered startup validator, in registration order, and abort\n * on the first rejection. Idempotent — a second call is a no-op.\n *\n * Distinct from `runBootListener`: a listener's failure is isolated so it\n * can never break boot; a validator's failure IS the boot failing — that is\n * the entire point of registering one instead of the other. The thrown\n * error names the failing validator and the original cause, so whatever\n * catches it (the dev server's boot try/catch, the production entry's\n * top-level await) reports which check failed and why, not a bare\n * rejection.\n *\n * @internal Framework entry points call this once app code (main.ts et al)\n * has loaded and BEFORE late-phase connectors (http, socket) start, so a\n * failing validator runs before anything can bind a port or accept a\n * request. Application code must not call this directly.\n */\n public static async runStartupValidators(): Promise<void> {\n if (this.validated) {\n return;\n }\n\n this.validated = true;\n\n const validators = this.bootValidators;\n this.bootValidators = [];\n\n for (const validator of validators) {\n try {\n await validator();\n } catch (error) {\n const name = validator.name || \"<anonymous>\";\n const cause = error instanceof Error ? error.message : String(error);\n\n throw new ErrorWithCause(\n `Startup validator \"${name}\" rejected boot: ${cause}. ` +\n `Fix the condition it checks, or remove the validator if it no ` +\n `longer applies — boot cannot proceed while it fails.`,\n { cause: error },\n );\n }\n }\n }\n\n /**\n * Whether the application has begun shutting down. Once true, readiness\n * checks report not-ready so a load balancer drains this instance before the\n * HTTP server stops accepting requests.\n */\n public static get isShuttingDown(): boolean {\n return this.shuttingDown;\n }\n\n /**\n * Register a teardown callback, run once when the application shuts down —\n * before the connectors (db, cache, http) are torn down, so it can still use\n * them. The natural place to close an app-owned resource opened in\n * `onceBooted`. If shutdown has already begun, the callback runs immediately.\n *\n * @example\n * Application.onShutdown(async () => {\n * await myQueueConsumer.stop();\n * });\n */\n public static onShutdown(listener: ShutdownListener): void {\n if (this.shuttingDown) {\n void this.runShutdownListener(listener);\n\n return;\n }\n\n this.shutdownListeners.push(listener);\n }\n\n /**\n * Flip the shutdown latch and run every teardown callback. The connectors\n * manager calls this at the start of shutdown — before any connector is torn\n * down. Idempotent and error-isolated: each callback is awaited, a thrown one\n * is logged and does not stop the rest. Listeners run LIFO (reverse of\n * registration), mirroring resource-unwind order.\n *\n * @internal Framework entry points call this; application code must not.\n */\n public static async runShutdownHooks(): Promise<void> {\n if (this.shuttingDown) {\n return;\n }\n\n this.shuttingDown = true;\n\n const listeners = this.shutdownListeners.reverse();\n this.shutdownListeners = [];\n\n for (const listener of listeners) {\n await this.runShutdownListener(listener);\n }\n }\n\n /**\n * Invoke a single teardown callback, isolating its failure so the rest still\n * run and shutdown is never blocked by one bad hook.\n */\n private static async runShutdownListener(listener: ShutdownListener): Promise<void> {\n try {\n await listener();\n } catch (error) {\n log.error(\"application\", \"shutdown-listener\", error as Error);\n }\n }\n\n /**\n * Get project uptime in milliseconds\n */\n public static get uptime(): number {\n return process.uptime() * 1000;\n }\n\n /**\n * Get the current environment\n */\n public static get environment(): Environment {\n return environment();\n }\n\n /**\n * Set the current environment\n */\n public static setEnvironment(env: Environment) {\n setEnvironment(env);\n }\n\n /**\n * Check if the application is running in production environment\n */\n public static get isProduction(): boolean {\n return this.environment === \"production\";\n }\n\n /**\n * Check if the application is running in development environment\n */\n public static get isDevelopment(): boolean {\n return this.environment === \"development\";\n }\n\n /**\n * Check if the application is running in test environment\n */\n public static get isTest(): boolean {\n return this.environment === \"test\";\n }\n\n /**\n * Get the root path\n */\n public static get rootPath(): string {\n return rootPath();\n }\n\n /**\n * Get the src path\n */\n public static get srcPath(): string {\n return srcPath();\n }\n\n /**\n * Get the app path\n */\n public static get appPath(): string {\n return appPath();\n }\n\n /**\n * Get the storage path\n */\n public static get storagePath(): string {\n return storagePath();\n }\n\n /**\n * Get the uploads path\n */\n public static get uploadsPath(): string {\n return uploadsPath();\n }\n\n /**\n * Get the public path\n */\n public static get publicPath(): string {\n return publicPath();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"application.mjs","names":[],"sources":["../../../../../../../core/src/application/application.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport { BOOT_SIGNAL_VERSION, sendBootSignal } from \"./boot-signal\";\nimport {\n environment,\n setEnvironment,\n type Environment,\n type RuntimeStrategy,\n} from \"../utils/environment\";\nimport { getFrameworkVersion } from \"../utils/framework-vesion\";\nimport { appPath, publicPath, rootPath, srcPath, storagePath, uploadsPath } from \"../utils/paths\";\n\n/**\n * Snapshot of the global facts known the moment the application finished\n * booting: every connector (early + late) is active and all app files\n * (locales, events, main, routes) are loaded. Passed to every\n * `Application.onceBooted` listener and resolved from `Application.whenBooted`.\n */\nexport type BootContext = {\n environment: Environment;\n runtimeStrategy: RuntimeStrategy;\n /**\n * Wall-clock milliseconds the boot took. The dev server measures it; the\n * production entry omits it.\n */\n bootDurationMs?: number;\n};\n\n/**\n * A callback registered through `Application.onceBooted`. May be async — its\n * rejection is caught and logged so a single listener can never break boot or\n * the other listeners.\n */\nexport type BootListener = (context: BootContext) => void | Promise<void>;\n\n/**\n * A callback registered through `Application.onValidateBoot`. May be async —\n * unlike {@link BootListener}, its rejection is NOT caught: it aborts boot.\n *\n * Use this for configuration that makes the application WRONG rather than\n * degraded when missing (a signing secret, a required integration key) — the\n * kind of check that must fail closed. Use `onceBooted` for everything else;\n * that contract stays fire-and-forget on purpose (it is the right behaviour\n * for plugins reacting to boot, not for gating it).\n */\nexport type BootValidator = () => void | Promise<void>;\n\n/**\n * `new Error(message, { cause })` is ES2022, and this package still compiles\n * against the ES2020 lib, where `Error` is typed as taking a message only.\n * Node has supported the option since v16, so this is the type layer\n * catching up with the runtime, not a change in what the code does. Same\n * workaround as `image.ts` (see #16).\n */\ntype ErrorWithCauseConstructor = new (message: string, options: { cause: unknown }) => Error;\n\nconst ErrorWithCause = Error as ErrorWithCauseConstructor;\n\n/**\n * A callback registered through `Application.onShutdown`. Runs once, while the\n * connectors (db, cache, http) are still up, so it can release app-owned\n * resources cleanly. May be async — its rejection is caught and logged.\n */\nexport type ShutdownListener = () => void | Promise<void>;\n\nexport class Application {\n /**\n * Project start time regarding the process start time\n */\n public static readonly startedAt = new Date(Date.now() - process.uptime() * 1000);\n\n /**\n * Runtime strategy\n */\n public static runtimeStrategy: RuntimeStrategy;\n\n /**\n * Whether the application has finished booting. Flipped once by `markBooted`.\n */\n private static booted = false;\n\n /**\n * The boot context, set once booted so it can be replayed to late subscribers.\n */\n private static bootContext: BootContext | undefined;\n\n /**\n * Listeners queued before boot completed, drained in registration order by\n * `markBooted`.\n */\n private static bootListeners: BootListener[] = [];\n\n /**\n * Whether `runStartupValidators` has already run. Flipped once, so a\n * validator registered afterwards is refused instead of silently skipped\n * or run too late to matter.\n */\n private static validated = false;\n\n /**\n * Startup validators queued before `runStartupValidators` ran, drained in\n * registration order.\n */\n private static bootValidators: BootValidator[] = [];\n\n /**\n * The http port this process actually bound, once it has. Reported in the\n * readiness signal so a supervisor knows where to send its health check\n * instead of re-deriving it from config it may not be able to read.\n * Undefined for an app with no http connector — a queue worker still boots.\n */\n private static servedPort: number | undefined;\n\n /**\n * Whether the application has begun (or finished) shutting down. Flipped once\n * by `runShutdownHooks`.\n */\n private static shuttingDown = false;\n\n /**\n * Teardown callbacks, run once when shutdown begins.\n */\n private static shutdownListeners: ShutdownListener[] = [];\n\n /**\n * Get framework version\n */\n public static get version() {\n return getFrameworkVersion();\n }\n\n /**\n * Set the runtime strategy\n */\n public static setRuntimeStrategy(strategy: RuntimeStrategy) {\n this.runtimeStrategy = strategy;\n }\n\n /**\n * Whether the application has finished booting — every connector in both\n * the early and late phases is active and all app files (locales, events,\n * main, routes) have been loaded.\n */\n public static get isBooted(): boolean {\n return this.booted;\n }\n\n /**\n * Record the http port this process bound.\n *\n * @internal The http connector calls this after a successful `listen`.\n */\n public static setServedPort(port: number): void {\n this.servedPort = port;\n }\n\n /**\n * Run a callback once the application is fully booted.\n *\n * App files (`main` / `events` / `routes` / locales) are imported BEFORE the\n * late-phase connectors (http, socket) start, so a listener registered at\n * import time waits for the whole sequence to finish before it runs. If the\n * application is already booted, the callback runs on the next microtask —\n * so a late subscriber never silently misses the event.\n *\n * @example\n * Application.onceBooted(({ environment }) => {\n * log.info(\"app\", \"booted\", `ready in ${environment}`);\n * });\n */\n public static onceBooted(listener: BootListener): void {\n if (this.booted && this.bootContext) {\n const context = this.bootContext;\n void this.runBootListener(listener, context);\n\n return;\n }\n\n this.bootListeners.push(listener);\n }\n\n /**\n * Promise form of {@link onceBooted} — resolves with the boot context once\n * the application is booted, or immediately if it already is.\n */\n public static whenBooted(): Promise<BootContext> {\n if (this.booted && this.bootContext) {\n return Promise.resolve(this.bootContext);\n }\n\n return new Promise<BootContext>((resolve) => {\n this.onceBooted((context) => resolve(context));\n });\n }\n\n /**\n * Flip the boot latch and drain every queued listener. The dev server and\n * the production entry call this once, right after the late phase starts. It\n * is idempotent — a second call is a no-op, so a double-invoke can never\n * double-fire listeners.\n *\n * Also reports readiness to a supervising parent process — this is the one\n * moment in the lifecycle where \"the application is serving\" is true, so it\n * is the only honest source for `warlock start`'s success banner. Sent before\n * the listeners run: a slow or hanging `onceBooted` listener must not delay\n * the parent's view of a server that is already accepting requests.\n *\n * @internal Framework entry points call this; application code must not.\n */\n public static markBooted(context: BootContext): void {\n if (this.booted) {\n return;\n }\n\n this.booted = true;\n this.bootContext = context;\n\n sendBootSignal({\n type: \"warlock:ready\",\n version: BOOT_SIGNAL_VERSION,\n pid: process.pid,\n at: new Date().toISOString(),\n environment: context.environment,\n runtimeStrategy: context.runtimeStrategy,\n bootDurationMs: context.bootDurationMs,\n port: this.servedPort,\n });\n\n const listeners = this.bootListeners;\n this.bootListeners = [];\n\n for (const listener of listeners) {\n void this.runBootListener(listener, context);\n }\n }\n\n /**\n * Invoke a single boot listener, isolating its failure so neither boot nor\n * the remaining listeners are affected.\n */\n private static async runBootListener(\n listener: BootListener,\n context: BootContext,\n ): Promise<void> {\n try {\n await listener(context);\n } catch (error) {\n log.error(\"application\", \"booted-listener\", error as Error);\n }\n }\n\n /**\n * Register a startup validator — distinct from {@link onceBooted}. A\n * rejecting validator aborts boot instead of being caught and logged, so\n * this is where configuration that must fail closed belongs (a signing\n * secret, a required integration key), not `onceBooted`.\n *\n * Must be registered before the framework calls `runStartupValidators` —\n * typically at module scope in `src/app/main.ts`, the same place a\n * `BootListener` would be registered. Registering one after validation has\n * already run throws: by then a rejection could no longer stop the app\n * from serving, so running it late would silently defeat the guarantee\n * this hook exists to give.\n *\n * @example\n * Application.onValidateBoot(() => {\n * if (!env(\"JWT_SECRET\")) {\n * throw new Error(\n * \"JWT_SECRET is not set. Sessions cannot be signed without it. \" +\n * \"Generate one with `yarn jwt` and put it in .env.\",\n * );\n * }\n * });\n */\n public static onValidateBoot(validator: BootValidator): void {\n if (this.validated) {\n throw new Error(\n \"Application.onValidateBoot(): startup validators already ran — \" +\n \"this validator was registered too late to block boot. Register it \" +\n \"before the framework calls Application.runStartupValidators(), \" +\n \"typically at module scope in `src/app/main.ts`.\",\n );\n }\n\n this.bootValidators.push(validator);\n }\n\n /**\n * Run every registered startup validator, in registration order, and abort\n * on the first rejection. Idempotent — a second call is a no-op.\n *\n * Distinct from `runBootListener`: a listener's failure is isolated so it\n * can never break boot; a validator's failure IS the boot failing — that is\n * the entire point of registering one instead of the other. The thrown\n * error names the failing validator and the original cause, so whatever\n * catches it (the dev server's boot try/catch, the production entry's\n * top-level await) reports which check failed and why, not a bare\n * rejection.\n *\n * @internal Framework entry points call this once app code (main.ts et al)\n * has loaded and BEFORE late-phase connectors (http, socket) start, so a\n * failing validator runs before anything can bind a port or accept a\n * request. Application code must not call this directly.\n */\n public static async runStartupValidators(): Promise<void> {\n if (this.validated) {\n return;\n }\n\n this.validated = true;\n\n const validators = this.bootValidators;\n this.bootValidators = [];\n\n for (const validator of validators) {\n try {\n await validator();\n } catch (error) {\n const name = validator.name || \"<anonymous>\";\n const cause = error instanceof Error ? error.message : String(error);\n\n throw new ErrorWithCause(\n `Startup validator \"${name}\" rejected boot: ${cause}. ` +\n `Fix the condition it checks, or remove the validator if it no ` +\n `longer applies — boot cannot proceed while it fails.`,\n { cause: error },\n );\n }\n }\n }\n\n /**\n * Whether the application has begun shutting down. Once true, readiness\n * checks report not-ready so a load balancer drains this instance before the\n * HTTP server stops accepting requests.\n */\n public static get isShuttingDown(): boolean {\n return this.shuttingDown;\n }\n\n /**\n * Register a teardown callback, run once when the application shuts down —\n * before the connectors (db, cache, http) are torn down, so it can still use\n * them. The natural place to close an app-owned resource opened in\n * `onceBooted`. If shutdown has already begun, the callback runs immediately.\n *\n * @example\n * Application.onShutdown(async () => {\n * await myQueueConsumer.stop();\n * });\n */\n public static onShutdown(listener: ShutdownListener): void {\n if (this.shuttingDown) {\n void this.runShutdownListener(listener);\n\n return;\n }\n\n this.shutdownListeners.push(listener);\n }\n\n /**\n * Flip the shutdown latch and run every teardown callback. The connectors\n * manager calls this at the start of shutdown — before any connector is torn\n * down. Idempotent and error-isolated: each callback is awaited, a thrown one\n * is logged and does not stop the rest. Listeners run LIFO (reverse of\n * registration), mirroring resource-unwind order.\n *\n * @internal Framework entry points call this; application code must not.\n */\n public static async runShutdownHooks(): Promise<void> {\n if (this.shuttingDown) {\n return;\n }\n\n this.shuttingDown = true;\n\n const listeners = this.shutdownListeners.reverse();\n this.shutdownListeners = [];\n\n for (const listener of listeners) {\n await this.runShutdownListener(listener);\n }\n }\n\n /**\n * Invoke a single teardown callback, isolating its failure so the rest still\n * run and shutdown is never blocked by one bad hook.\n */\n private static async runShutdownListener(listener: ShutdownListener): Promise<void> {\n try {\n await listener();\n } catch (error) {\n log.error(\"application\", \"shutdown-listener\", error as Error);\n }\n }\n\n /**\n * Get project uptime in milliseconds\n */\n public static get uptime(): number {\n return process.uptime() * 1000;\n }\n\n /**\n * Get the current environment\n */\n public static get environment(): Environment {\n return environment();\n }\n\n /**\n * Set the current environment\n */\n public static setEnvironment(env: Environment) {\n setEnvironment(env);\n }\n\n /**\n * Check if the application is running in production environment\n */\n public static get isProduction(): boolean {\n return this.environment === \"production\";\n }\n\n /**\n * Check if the application is running in development environment\n */\n public static get isDevelopment(): boolean {\n return this.environment === \"development\";\n }\n\n /**\n * Check if the application is running in test environment\n */\n public static get isTest(): boolean {\n return this.environment === \"test\";\n }\n\n /**\n * Get the root path\n */\n public static get rootPath(): string {\n return rootPath();\n }\n\n /**\n * Get the src path\n */\n public static get srcPath(): string {\n return srcPath();\n }\n\n /**\n * Get the app path\n */\n public static get appPath(): string {\n return appPath();\n }\n\n /**\n * Get the storage path\n */\n public static get storagePath(): string {\n return storagePath();\n }\n\n /**\n * Get the uploads path\n */\n public static get uploadsPath(): string {\n return uploadsPath();\n }\n\n /**\n * Get the public path\n */\n public static get publicPath(): string {\n return publicPath();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"benchmark-snapshots.d.mts","names":[],"sources":["../../../../../../../core/src/benchmark/benchmark-snapshots.ts"],"mappings":";;;cAMa,kBAAA;EAAA,iBACM,YAAA;EAAA,iBACA,OAAA;EAAA,iBACA,SAAA;cAKE,OAAA,GAAU,yBAAA;EAAA;;;;;;;;;EActB,MAAA,CAAO,MAAA,EAAQ,sBAAA,YAAkC,oBAAA;EArBvC;;;;;;;;;EAgDV,YAAA,CAAa,IAAA,YAAgB,sBAAA,YAAkC,oBAAA;EAA/D;;;;;;;;EAYA,YAAA,
|
|
1
|
+
{"version":3,"file":"benchmark-snapshots.d.mts","names":[],"sources":["../../../../../../../core/src/benchmark/benchmark-snapshots.ts"],"mappings":";;;cAMa,kBAAA;EAAA,iBACM,YAAA;EAAA,iBACA,OAAA;EAAA,iBACA,SAAA;cAKE,OAAA,GAAU,yBAAA;EAAA;;;;;;;;;EActB,MAAA,CAAO,MAAA,EAAQ,sBAAA,YAAkC,oBAAA;EArBvC;;;;;;;;;EAgDV,YAAA,CAAa,IAAA,YAAgB,sBAAA,YAAkC,oBAAA;EAA/D;;;;;;;;EAYA,YAAA,IAAgB,MAAA,UAEpB,sBAAA,YAAkC,oBAAA;EAoBxB;;AAAa;;;;;;;EAAnB,KAAA,CAAM,IAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"benchmark.d.mts","names":[],"sources":["../../../../../../../core/src/benchmark/benchmark.ts"],"mappings":";;;;;AA+CA;;;;;;;;;;;;;;;;;;;;iBAAsB,OAAA,
|
|
1
|
+
{"version":3,"file":"benchmark.d.mts","names":[],"sources":["../../../../../../../core/src/benchmark/benchmark.ts"],"mappings":";;;;;AA+CA;;;;;;;;;;;;;;;;;;;;iBAAsB,OAAA,IACpB,IAAA,UACA,EAAA,QAAU,CAAA,GAAI,OAAA,CAAQ,CAAA,GACtB,OAAA,GAAU,gBAAA,CAAiB,CAAA,IAC1B,OAAA,CAAQ,sBAAA,CAAuB,CAAA,IAAK,oBAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console.channel.mjs","names":[],"sources":["../../../../../../../../core/src/benchmark/channels/console.channel.ts"],"sourcesContent":["import type { BenchmarkChannel, BenchmarkStats } from \"../types\";\n\nexport class ConsoleChannel implements BenchmarkChannel {\n /**\n * Pretty-prints a stats table per operation on onFlush().\n *\n * @param stats - Aggregated stats for all tracked operations.\n *\n * @example\n * const channel = new ConsoleChannel();\n * channel.onFlush({ \"db-query\": { p50: 10, count: 100, ... } });\n */\n public onFlush(stats: Record<string, BenchmarkStats>): void {\n const tableData: Record<string, any> = {};\n\n for (const [name, operationStats] of Object.entries(stats)) {\n tableData[name] = {\n \"p50 (ms)\": operationStats.p50,\n \"p90 (ms)\": operationStats.p90,\n \"p95 (ms)\": operationStats.p95,\n \"p99 (ms)\": operationStats.p99,\n \"Avg (ms)\": operationStats.avg,\n \"Min (ms)\": operationStats.min,\n \"Max (ms)\": operationStats.max,\n \"Error Rate\": `${(operationStats.errorRate * 100).toFixed(2)}%`,\n Errors: operationStats.errors,\n Count: operationStats.count,\n };\n }\n\n console.table(tableData);\n }\n}\n"],"mappings":";AAEA,IAAa,iBAAb,MAAwD;;;;;;;;;;CAUtD,AAAO,QAAQ,OAA6C;EAC1D,MAAM,YAAiC,CAAC;EAExC,KAAK,MAAM,CAAC,MAAM,mBAAmB,OAAO,QAAQ,KAAK,GACvD,UAAU,QAAQ;GAChB,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,cAAc,IAAI,eAAe,YAAY,
|
|
1
|
+
{"version":3,"file":"console.channel.mjs","names":[],"sources":["../../../../../../../../core/src/benchmark/channels/console.channel.ts"],"sourcesContent":["import type { BenchmarkChannel, BenchmarkStats } from \"../types\";\n\nexport class ConsoleChannel implements BenchmarkChannel {\n /**\n * Pretty-prints a stats table per operation on onFlush().\n *\n * @param stats - Aggregated stats for all tracked operations.\n *\n * @example\n * const channel = new ConsoleChannel();\n * channel.onFlush({ \"db-query\": { p50: 10, count: 100, ... } });\n */\n public onFlush(stats: Record<string, BenchmarkStats>): void {\n const tableData: Record<string, any> = {};\n\n for (const [name, operationStats] of Object.entries(stats)) {\n tableData[name] = {\n \"p50 (ms)\": operationStats.p50,\n \"p90 (ms)\": operationStats.p90,\n \"p95 (ms)\": operationStats.p95,\n \"p99 (ms)\": operationStats.p99,\n \"Avg (ms)\": operationStats.avg,\n \"Min (ms)\": operationStats.min,\n \"Max (ms)\": operationStats.max,\n \"Error Rate\": `${(operationStats.errorRate * 100).toFixed(2)}%`,\n Errors: operationStats.errors,\n Count: operationStats.count,\n };\n }\n\n console.table(tableData);\n }\n}\n"],"mappings":";AAEA,IAAa,iBAAb,MAAwD;;;;;;;;;;CAUtD,AAAO,QAAQ,OAA6C;EAC1D,MAAM,YAAiC,CAAC;EAExC,KAAK,MAAM,CAAC,MAAM,mBAAmB,OAAO,QAAQ,KAAK,GACvD,UAAU,QAAQ;GAChB,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,YAAY,eAAe;GAC3B,cAAc,IAAI,eAAe,YAAY,IAAG,CAAE,QAAQ,CAAC,EAAE;GAC7D,QAAQ,eAAe;GACvB,OAAO,eAAe;EACxB;EAGF,QAAQ,MAAM,SAAS;CACzB;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noop.channel.d.mts","names":[],"sources":["../../../../../../../../core/src/benchmark/channels/noop.channel.ts"],"mappings":";;;cAEa,WAAA,YAAuB,gBAAgB;EAC3C,OAAO
|
|
1
|
+
{"version":3,"file":"noop.channel.d.mts","names":[],"sources":["../../../../../../../../core/src/benchmark/channels/noop.channel.ts"],"mappings":";;;cAEa,WAAA,YAAuB,gBAAgB;EAC3C,OAAO;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiler.d.mts","names":[],"sources":["../../../../../../../core/src/benchmark/profiler.ts"],"mappings":";;;cAkBa,iBAAA;EAAA,iBACM,UAAA;EAAA,iBACA,QAAA;EAAA,iBACA,OAAA;EAAA,QACT,QAAA;cAEW,OAAA,GAAU,wBAAA;EAoBP;;;;;;;;EAAf,MAAA,CAAO,MAAA,EAAQ,sBAAA,YAAkC,oBAAA;EAxBvC;;;;;;;;;;EAqEV,KAAA,CAAM,IAAA,WAAe,cAAA;EAAf;;;;;;;;EAoCN,QAAA,
|
|
1
|
+
{"version":3,"file":"profiler.d.mts","names":[],"sources":["../../../../../../../core/src/benchmark/profiler.ts"],"mappings":";;;cAkBa,iBAAA;EAAA,iBACM,UAAA;EAAA,iBACA,QAAA;EAAA,iBACA,OAAA;EAAA,QACT,QAAA;cAEW,OAAA,GAAU,wBAAA;EAoBP;;;;;;;;EAAf,MAAA,CAAO,MAAA,EAAQ,sBAAA,YAAkC,oBAAA;EAxBvC;;;;;;;;;;EAqEV,KAAA,CAAM,IAAA,WAAe,cAAA;EAAf;;;;;;;;EAoCN,QAAA,IAAY,MAAA,SAAe,cAAA;EAyD3B;;AAAO;;;;EAxCD,KAAA,IAAS,OAAA;;;;;;;;;;;EAmBf,KAAA,CAAM,IAAA;;;;;;;EAqBN,OAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiler.mjs","names":[],"sources":["../../../../../../../core/src/benchmark/profiler.ts"],"sourcesContent":["import { NoopChannel } from \"./channels/noop.channel\";\nimport type {\n BenchmarkChannel,\n BenchmarkErrorResult,\n BenchmarkProfilerOptions,\n BenchmarkStats,\n BenchmarkSuccessResult,\n} from \"./types\";\n\ntype BenchmarkEntry = {\n latencies: number[];\n sum: number;\n total: number;\n errors: number;\n firstSeenAt: Date;\n lastSeenAt: Date;\n};\n\nexport class BenchmarkProfiler {\n private readonly maxSamples: number;\n private readonly channels: BenchmarkChannel[];\n private readonly entries = new Map<string, BenchmarkEntry>();\n private interval?: ReturnType<typeof setInterval>;\n\n public constructor(options?: BenchmarkProfilerOptions) {\n this.maxSamples = options?.maxSamples ?? 1000;\n this.channels = options?.channels ?? [new NoopChannel()];\n\n if (options?.flushEvery) {\n this.interval = setInterval(() => {\n // We use void to explicitly ignore the promise, as this is a background interval\n void this.flush();\n }, options.flushEvery);\n }\n }\n\n /**\n * Record one measurement result. Called automatically by measure() when a profiler is set.\n *\n * @param result - The success or error result from measure()\n *\n * @example\n * profiler.record(result);\n */\n public record(result: BenchmarkSuccessResult<unknown> | BenchmarkErrorResult): void {\n let entry = this.entries.get(result.name);\n\n if (!entry) {\n entry = {\n latencies: [],\n sum: 0,\n total: 0,\n errors: 0,\n firstSeenAt: result.startedAt,\n lastSeenAt: result.endedAt,\n };\n\n this.entries.set(result.name, entry);\n }\n\n entry.latencies.push(result.latency);\n entry.sum += result.latency;\n\n if (entry.latencies.length > this.maxSamples) {\n const removed = entry.latencies.shift();\n if (removed !== undefined) {\n entry.sum -= removed;\n }\n }\n\n entry.total += 1;\n\n if (!result.success) {\n entry.errors += 1;\n }\n\n entry.lastSeenAt = result.endedAt;\n }\n\n /**\n * Get aggregated stats for one operation name.\n * Computes p50/p95/p99 by sorting the ring buffer on demand.\n *\n * @param name - The operation name to get stats for.\n * @returns Stats object or undefined if no data yet.\n *\n * @example\n * const stats = profiler.stats(\"db-query\");\n */\n public stats(name: string): BenchmarkStats | undefined {\n const entry = this.entries.get(name);\n if (!entry || entry.latencies.length === 0) return undefined;\n\n const latencies = [...entry.latencies].sort((a, b) => a - b);\n const count = latencies.length;\n\n const getP = (percentile: number) => {\n const index = Math.min(count - 1, Math.floor(count * percentile));\n return latencies[index];\n };\n\n return {\n p50: getP(0.5),\n p90: getP(0.9),\n p95: getP(0.95),\n p99: getP(0.99),\n avg: Math.round((entry.sum / count) * 100) / 100,\n min: latencies[0],\n max: latencies[count - 1],\n count: entry.total,\n errors: entry.errors,\n errorRate: Math.round((entry.errors / entry.total) * 100) / 100,\n firstSeenAt: entry.firstSeenAt,\n lastSeenAt: entry.lastSeenAt,\n };\n }\n\n /**\n * Get stats for all tracked operations.\n *\n * @returns A record mapping operation names to their stats.\n *\n * @example\n * const all = profiler.allStats();\n */\n public allStats(): Record<string, BenchmarkStats> {\n const all: Record<string, BenchmarkStats> = {};\n for (const name of this.entries.keys()) {\n const operationStats = this.stats(name);\n if (operationStats) {\n all[name] = operationStats;\n }\n }\n return all;\n }\n\n /**\n * Send allStats() to all registered channels.\n *\n * @example\n * await profiler.flush();\n */\n public async flush(): Promise<void> {\n const stats = this.allStats();\n if (Object.keys(stats).length === 0) return;\n\n for (const channel of this.channels) {\n await channel.onFlush(stats);\n }\n }\n\n /**\n * Clear ring buffer for one or all operations.\n * Does NOT reset unbounded total/error counters.\n *\n * @param name - Optional operation name. If omitted, clears all ring buffers.\n *\n * @example\n * profiler.reset(\"db-query\");\n * profiler.reset();\n */\n public reset(name?: string): void {\n if (name) {\n const entry = this.entries.get(name);\n if (entry) {\n entry.latencies = [];\n entry.sum = 0;\n }\n } else {\n for (const entry of this.entries.values()) {\n entry.latencies = [];\n entry.sum = 0;\n }\n }\n }\n\n /**\n * Dispose the profiler, clearing its auto-flush interval.\n *\n * @example\n * profiler.dispose();\n */\n public dispose(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = undefined;\n }\n }\n}\n"],"mappings":";;;AAkBA,IAAa,oBAAb,MAA+B;CAM7B,AAAO,YAAY,SAAoC;iCAH5B,IAAI,IAA4B;EAIzD,KAAK,aAAa,SAAS,cAAc;EACzC,KAAK,WAAW,SAAS,YAAY,CAAC,IAAI,YAAY,CAAC;EAEvD,IAAI,SAAS,YACX,KAAK,WAAW,kBAAkB;GAEhC,AAAK,KAAK,MAAM;EAClB,GAAG,QAAQ,UAAU;CAEzB;;;;;;;;;CAUA,AAAO,OAAO,QAAsE;EAClF,IAAI,QAAQ,KAAK,QAAQ,IAAI,OAAO,IAAI;EAExC,IAAI,CAAC,OAAO;GACV,QAAQ;IACN,WAAW,CAAC;IACZ,KAAK;IACL,OAAO;IACP,QAAQ;IACR,aAAa,OAAO;IACpB,YAAY,OAAO;GACrB;GAEA,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK;EACrC;EAEA,MAAM,UAAU,KAAK,OAAO,OAAO;EACnC,MAAM,OAAO,OAAO;EAEpB,IAAI,MAAM,UAAU,SAAS,KAAK,YAAY;GAC5C,MAAM,UAAU,MAAM,UAAU,MAAM;GACtC,IAAI,YAAY,QACd,MAAM,OAAO;EAEjB;EAEA,MAAM,SAAS;EAEf,IAAI,CAAC,OAAO,SACV,MAAM,UAAU;EAGlB,MAAM,aAAa,OAAO;CAC5B;;;;;;;;;;;CAYA,AAAO,MAAM,MAA0C;EACrD,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;EACnC,IAAI,CAAC,SAAS,MAAM,UAAU,WAAW,GAAG,OAAO;EAEnD,MAAM,YAAY,CAAC,GAAG,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"profiler.mjs","names":[],"sources":["../../../../../../../core/src/benchmark/profiler.ts"],"sourcesContent":["import { NoopChannel } from \"./channels/noop.channel\";\nimport type {\n BenchmarkChannel,\n BenchmarkErrorResult,\n BenchmarkProfilerOptions,\n BenchmarkStats,\n BenchmarkSuccessResult,\n} from \"./types\";\n\ntype BenchmarkEntry = {\n latencies: number[];\n sum: number;\n total: number;\n errors: number;\n firstSeenAt: Date;\n lastSeenAt: Date;\n};\n\nexport class BenchmarkProfiler {\n private readonly maxSamples: number;\n private readonly channels: BenchmarkChannel[];\n private readonly entries = new Map<string, BenchmarkEntry>();\n private interval?: ReturnType<typeof setInterval>;\n\n public constructor(options?: BenchmarkProfilerOptions) {\n this.maxSamples = options?.maxSamples ?? 1000;\n this.channels = options?.channels ?? [new NoopChannel()];\n\n if (options?.flushEvery) {\n this.interval = setInterval(() => {\n // We use void to explicitly ignore the promise, as this is a background interval\n void this.flush();\n }, options.flushEvery);\n }\n }\n\n /**\n * Record one measurement result. Called automatically by measure() when a profiler is set.\n *\n * @param result - The success or error result from measure()\n *\n * @example\n * profiler.record(result);\n */\n public record(result: BenchmarkSuccessResult<unknown> | BenchmarkErrorResult): void {\n let entry = this.entries.get(result.name);\n\n if (!entry) {\n entry = {\n latencies: [],\n sum: 0,\n total: 0,\n errors: 0,\n firstSeenAt: result.startedAt,\n lastSeenAt: result.endedAt,\n };\n\n this.entries.set(result.name, entry);\n }\n\n entry.latencies.push(result.latency);\n entry.sum += result.latency;\n\n if (entry.latencies.length > this.maxSamples) {\n const removed = entry.latencies.shift();\n if (removed !== undefined) {\n entry.sum -= removed;\n }\n }\n\n entry.total += 1;\n\n if (!result.success) {\n entry.errors += 1;\n }\n\n entry.lastSeenAt = result.endedAt;\n }\n\n /**\n * Get aggregated stats for one operation name.\n * Computes p50/p95/p99 by sorting the ring buffer on demand.\n *\n * @param name - The operation name to get stats for.\n * @returns Stats object or undefined if no data yet.\n *\n * @example\n * const stats = profiler.stats(\"db-query\");\n */\n public stats(name: string): BenchmarkStats | undefined {\n const entry = this.entries.get(name);\n if (!entry || entry.latencies.length === 0) return undefined;\n\n const latencies = [...entry.latencies].sort((a, b) => a - b);\n const count = latencies.length;\n\n const getP = (percentile: number) => {\n const index = Math.min(count - 1, Math.floor(count * percentile));\n return latencies[index];\n };\n\n return {\n p50: getP(0.5),\n p90: getP(0.9),\n p95: getP(0.95),\n p99: getP(0.99),\n avg: Math.round((entry.sum / count) * 100) / 100,\n min: latencies[0],\n max: latencies[count - 1],\n count: entry.total,\n errors: entry.errors,\n errorRate: Math.round((entry.errors / entry.total) * 100) / 100,\n firstSeenAt: entry.firstSeenAt,\n lastSeenAt: entry.lastSeenAt,\n };\n }\n\n /**\n * Get stats for all tracked operations.\n *\n * @returns A record mapping operation names to their stats.\n *\n * @example\n * const all = profiler.allStats();\n */\n public allStats(): Record<string, BenchmarkStats> {\n const all: Record<string, BenchmarkStats> = {};\n for (const name of this.entries.keys()) {\n const operationStats = this.stats(name);\n if (operationStats) {\n all[name] = operationStats;\n }\n }\n return all;\n }\n\n /**\n * Send allStats() to all registered channels.\n *\n * @example\n * await profiler.flush();\n */\n public async flush(): Promise<void> {\n const stats = this.allStats();\n if (Object.keys(stats).length === 0) return;\n\n for (const channel of this.channels) {\n await channel.onFlush(stats);\n }\n }\n\n /**\n * Clear ring buffer for one or all operations.\n * Does NOT reset unbounded total/error counters.\n *\n * @param name - Optional operation name. If omitted, clears all ring buffers.\n *\n * @example\n * profiler.reset(\"db-query\");\n * profiler.reset();\n */\n public reset(name?: string): void {\n if (name) {\n const entry = this.entries.get(name);\n if (entry) {\n entry.latencies = [];\n entry.sum = 0;\n }\n } else {\n for (const entry of this.entries.values()) {\n entry.latencies = [];\n entry.sum = 0;\n }\n }\n }\n\n /**\n * Dispose the profiler, clearing its auto-flush interval.\n *\n * @example\n * profiler.dispose();\n */\n public dispose(): void {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = undefined;\n }\n }\n}\n"],"mappings":";;;AAkBA,IAAa,oBAAb,MAA+B;CAM7B,AAAO,YAAY,SAAoC;iCAH5B,IAAI,IAA4B;EAIzD,KAAK,aAAa,SAAS,cAAc;EACzC,KAAK,WAAW,SAAS,YAAY,CAAC,IAAI,YAAY,CAAC;EAEvD,IAAI,SAAS,YACX,KAAK,WAAW,kBAAkB;GAEhC,AAAK,KAAK,MAAM;EAClB,GAAG,QAAQ,UAAU;CAEzB;;;;;;;;;CAUA,AAAO,OAAO,QAAsE;EAClF,IAAI,QAAQ,KAAK,QAAQ,IAAI,OAAO,IAAI;EAExC,IAAI,CAAC,OAAO;GACV,QAAQ;IACN,WAAW,CAAC;IACZ,KAAK;IACL,OAAO;IACP,QAAQ;IACR,aAAa,OAAO;IACpB,YAAY,OAAO;GACrB;GAEA,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK;EACrC;EAEA,MAAM,UAAU,KAAK,OAAO,OAAO;EACnC,MAAM,OAAO,OAAO;EAEpB,IAAI,MAAM,UAAU,SAAS,KAAK,YAAY;GAC5C,MAAM,UAAU,MAAM,UAAU,MAAM;GACtC,IAAI,YAAY,QACd,MAAM,OAAO;EAEjB;EAEA,MAAM,SAAS;EAEf,IAAI,CAAC,OAAO,SACV,MAAM,UAAU;EAGlB,MAAM,aAAa,OAAO;CAC5B;;;;;;;;;;;CAYA,AAAO,MAAM,MAA0C;EACrD,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;EACnC,IAAI,CAAC,SAAS,MAAM,UAAU,WAAW,GAAG,OAAO;EAEnD,MAAM,YAAY,CAAC,GAAG,MAAM,SAAS,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;EAC3D,MAAM,QAAQ,UAAU;EAExB,MAAM,QAAQ,eAAuB;GAEnC,OAAO,UADO,KAAK,IAAI,QAAQ,GAAG,KAAK,MAAM,QAAQ,UAAU,CAC1C;EACvB;EAEA,OAAO;GACL,KAAK,KAAK,EAAG;GACb,KAAK,KAAK,EAAG;GACb,KAAK,KAAK,GAAI;GACd,KAAK,KAAK,GAAI;GACd,KAAK,KAAK,MAAO,MAAM,MAAM,QAAS,GAAG,IAAI;GAC7C,KAAK,UAAU;GACf,KAAK,UAAU,QAAQ;GACvB,OAAO,MAAM;GACb,QAAQ,MAAM;GACd,WAAW,KAAK,MAAO,MAAM,SAAS,MAAM,QAAS,GAAG,IAAI;GAC5D,aAAa,MAAM;GACnB,YAAY,MAAM;EACpB;CACF;;;;;;;;;CAUA,AAAO,WAA2C;EAChD,MAAM,MAAsC,CAAC;EAC7C,KAAK,MAAM,QAAQ,KAAK,QAAQ,KAAK,GAAG;GACtC,MAAM,iBAAiB,KAAK,MAAM,IAAI;GACtC,IAAI,gBACF,IAAI,QAAQ;EAEhB;EACA,OAAO;CACT;;;;;;;CAQA,MAAa,QAAuB;EAClC,MAAM,QAAQ,KAAK,SAAS;EAC5B,IAAI,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG;EAErC,KAAK,MAAM,WAAW,KAAK,UACzB,MAAM,QAAQ,QAAQ,KAAK;CAE/B;;;;;;;;;;;CAYA,AAAO,MAAM,MAAqB;EAChC,IAAI,MAAM;GACR,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;GACnC,IAAI,OAAO;IACT,MAAM,YAAY,CAAC;IACnB,MAAM,MAAM;GACd;EACF,OACE,KAAK,MAAM,SAAS,KAAK,QAAQ,OAAO,GAAG;GACzC,MAAM,YAAY,CAAC;GACnB,MAAM,MAAM;EACd;CAEJ;;;;;;;CAQA,AAAO,UAAgB;EACrB,IAAI,KAAK,UAAU;GACjB,cAAc,KAAK,QAAQ;GAC3B,KAAK,WAAW;EAClB;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.mts","names":[],"sources":["../../../../../../../core/src/bootstrap/setup.ts"],"mappings":";iBAGgB,sBAAA
|
|
1
|
+
{"version":3,"file":"setup.d.mts","names":[],"sources":["../../../../../../../core/src/bootstrap/setup.ts"],"mappings":";iBAGgB,sBAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.mjs","names":[],"sources":["../../../../../../../core/src/bootstrap/setup.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { environment } from \"../utils\";\n\nexport function displayEnvironmentMode() {\n const env = environment();\n\n const envColor = (env: string) => {\n switch (env) {\n case \"development\":\n return colors.yellow(env);\n case \"production\":\n return colors.green(env);\n case \"test\":\n return colors.magentaBright(env);\n }\n };\n\n console.log(\n colors.blueBright(\"ℹ\"),\n colors.yellow(`(${new Date().toISOString()})`),\n colors.orange(\"[warlock]\"),\n colors.magenta(`bootstrap`),\n colors.blueBright(`Starting application in ${envColor(env)} mode`),\n );\n}\n"],"mappings":";;;;;AAGA,SAAgB,yBAAyB;CACvC,MAAM,MAAM,YAAY;CAExB,MAAM,YAAY,QAAgB;EAChC,QAAQ,KAAR;GACE,KAAK,eACH,OAAO,OAAO,OAAO,GAAG;GAC1B,KAAK,cACH,OAAO,OAAO,MAAM,GAAG;GACzB,KAAK,QACH,OAAO,OAAO,cAAc,GAAG;EACnC;CACF;CAEA,QAAQ,IACN,OAAO,WAAW,GAAG,GACrB,OAAO,OAAO,qBAAI,IAAI,KAAK,
|
|
1
|
+
{"version":3,"file":"setup.mjs","names":[],"sources":["../../../../../../../core/src/bootstrap/setup.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { environment } from \"../utils\";\n\nexport function displayEnvironmentMode() {\n const env = environment();\n\n const envColor = (env: string) => {\n switch (env) {\n case \"development\":\n return colors.yellow(env);\n case \"production\":\n return colors.green(env);\n case \"test\":\n return colors.magentaBright(env);\n }\n };\n\n console.log(\n colors.blueBright(\"ℹ\"),\n colors.yellow(`(${new Date().toISOString()})`),\n colors.orange(\"[warlock]\"),\n colors.magenta(`bootstrap`),\n colors.blueBright(`Starting application in ${envColor(env)} mode`),\n );\n}\n"],"mappings":";;;;;AAGA,SAAgB,yBAAyB;CACvC,MAAM,MAAM,YAAY;CAExB,MAAM,YAAY,QAAgB;EAChC,QAAQ,KAAR;GACE,KAAK,eACH,OAAO,OAAO,OAAO,GAAG;GAC1B,KAAK,cACH,OAAO,OAAO,MAAM,GAAG;GACzB,KAAK,QACH,OAAO,OAAO,cAAc,GAAG;EACnC;CACF;CAEA,QAAQ,IACN,OAAO,WAAW,GAAG,GACrB,OAAO,OAAO,qBAAI,IAAI,KAAK,EAAC,CAAC,YAAY,EAAE,EAAE,GAC7C,OAAO,OAAO,WAAW,GACzB,OAAO,QAAQ,WAAW,GAC1B,OAAO,WAAW,2BAA2B,SAAS,GAAG,EAAE,MAAM,CACnE;AACF"}
|
package/esm/bootstrap.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.mts","names":[],"sources":["../../../../../../core/src/bootstrap.ts"],"mappings":";iBAKsB,SAAA,
|
|
1
|
+
{"version":3,"file":"bootstrap.d.mts","names":[],"sources":["../../../../../../core/src/bootstrap.ts"],"mappings":";iBAKsB,SAAA,IAAS,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-cache-driver.d.mts","names":[],"sources":["../../../../../../../core/src/cache/database-cache-driver.ts"],"mappings":";;;;KAQY,oBAAA;;AAAZ;;EAIE,KAAA,UAAe,UAAU;EAAA;;;EAIzB,YAAA;EAMA;;AAAG;AAGL;;EAHE,GAAA;AAAA;AAAA,cAGW,UAAA,SAAmB,KAAK;EAAA,OACrB,KAAK;AAAA;AAAA,cAGR,mBAAA,SACH,eAAA,CAAgB,mBAAA,EAAqB,oBAAA,aAClC,WAAA,CAAY,mBAAA,EAAqB,oBAAA;EADpB;;;EAMjB,IAAA;EAKe;;;EAAf,KAAA,SAAe,UAAA;EAiCkC;;;EA5BjD,UAAA,CAAW,OAAA,EAAS,oBAAA;EAgGM;;;EArFpB,eAAA,CAAgB,SAAA,WAAiB,OAAA;EA1BxB;;;EA2CT,GAAA,CAAI,GAAA,EAAK,QAAA,EAAU,KAAA,OAAY,GAAA,YAAY,OAAA;EA5CX;;;EAsFhC,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAhFvB;;;EA0GM,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EAhGN;;;EA+Gd,KAAA,
|
|
1
|
+
{"version":3,"file":"database-cache-driver.d.mts","names":[],"sources":["../../../../../../../core/src/cache/database-cache-driver.ts"],"mappings":";;;;KAQY,oBAAA;;AAAZ;;EAIE,KAAA,UAAe,UAAU;EAAA;;;EAIzB,YAAA;EAMA;;AAAG;AAGL;;EAHE,GAAA;AAAA;AAAA,cAGW,UAAA,SAAmB,KAAK;EAAA,OACrB,KAAK;AAAA;AAAA,cAGR,mBAAA,SACH,eAAA,CAAgB,mBAAA,EAAqB,oBAAA,aAClC,WAAA,CAAY,mBAAA,EAAqB,oBAAA;EADpB;;;EAMjB,IAAA;EAKe;;;EAAf,KAAA,SAAe,UAAA;EAiCkC;;;EA5BjD,UAAA,CAAW,OAAA,EAAS,oBAAA;EAgGM;;;EArFpB,eAAA,CAAgB,SAAA,WAAiB,OAAA;EA1BxB;;;EA2CT,GAAA,CAAI,GAAA,EAAK,QAAA,EAAU,KAAA,OAAY,GAAA,YAAY,OAAA;EA5CX;;;EAsFhC,GAAA,CAAI,GAAA,EAAK,QAAA,GAAQ,OAAA;EAhFvB;;;EA0GM,MAAA,CAAO,GAAA,EAAK,QAAA,GAAQ,OAAA;EAhGN;;;EA+Gd,KAAA,IAAK,OAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-cache-driver.mjs","names":[],"sources":["../../../../../../../core/src/cache/database-cache-driver.ts"],"sourcesContent":["import {\n BaseCacheDriver,\n type CacheData,\n type CacheDriver,\n type CacheKey,\n} from \"@warlock.js/cache\";\nimport { Model } from \"@warlock.js/cascade\";\n\nexport type DatabaseCacheOptions = {\n /**\n * Database model class\n */\n model?: typeof CacheModel;\n /**\n * Global prefix for the cache key\n */\n globalPrefix?: string | (() => string);\n /**\n * The default TTL for the cache in seconds\n *\n * @default Infinity\n */\n ttl?: number;\n};\n\nexport class CacheModel extends Model {\n public static table = \"cache\";\n}\n\nexport class DatabaseCacheDriver\n extends BaseCacheDriver<DatabaseCacheDriver, DatabaseCacheOptions>\n implements CacheDriver<DatabaseCacheDriver, DatabaseCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"database\";\n\n /**\n * Database model class\n */\n public model!: typeof CacheModel;\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: DatabaseCacheOptions) {\n super.setOptions(options);\n\n this.model = options.model ?? CacheModel;\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.log(\"clearing\", namespace);\n\n namespace = this.parseKey(namespace);\n\n await this.model.delete({\n namespace,\n });\n\n this.log(\"cleared\", namespace);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(key: CacheKey, value: any, ttl?: number) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"caching\", parsedKey);\n\n if (ttl === undefined) {\n ttl = this.ttl;\n }\n\n // Extract namespace: all parts except the last (e.g., \"users.id.1\" -> \"users.id\")\n const keyParts = parsedKey.split(\".\");\n const namespace = keyParts.slice(0, -1).join(\".\") || parsedKey;\n\n // Find existing cache entry or create new one (upsert pattern)\n let cacheEntry = await this.model.first({ key: parsedKey });\n\n if (cacheEntry) {\n // Update existing entry\n cacheEntry.set(\"namespace\", namespace);\n cacheEntry.set(\"data\", value);\n cacheEntry.set(\"ttl\", ttl);\n cacheEntry.set(\"expiresAt\", this.getExpiresAt(ttl) || null);\n await cacheEntry.save();\n } else {\n // Create new entry\n await this.model.create({\n key: parsedKey,\n namespace,\n data: value,\n ttl,\n expiresAt: this.getExpiresAt(ttl) || null,\n });\n }\n\n this.log(\"cached\", parsedKey);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const model = await this.model.first({\n key: parsedKey,\n });\n\n if (!model) {\n this.log(\"notFound\", parsedKey);\n return null;\n }\n\n const data: CacheData = {\n data: model.get(\"data\"),\n expiresAt: model.get(\"expiresAt\") as number | undefined,\n ttl: model.get(\"ttl\") as number | undefined,\n };\n\n return this.parseCachedData(parsedKey, data);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n await this.model.delete({\n key: parsedKey,\n });\n\n this.log(\"removed\", parsedKey);\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n if (this.options.globalPrefix) {\n this.removeNamespace(\"\");\n } else {\n await this.model.delete();\n }\n\n this.log(\"flushed\");\n }\n}\n"],"mappings":";;;;AAyBA,IAAa,aAAb,cAAgC,MAAM;;eACd;;AACxB;AAEA,IAAa,sBAAb,cACU,gBAEV;;;cAIgB;;;;;CAUd,AAAO,WAAW,SAA+B;EAC/C,MAAM,WAAW,OAAO;EAExB,KAAK,QAAQ,QAAQ,SAAS;EAE9B,OAAO;CACT;;;;CAKA,MAAa,gBAAgB,WAAmB;EAC9C,KAAK,IAAI,YAAY,SAAS;EAE9B,YAAY,KAAK,SAAS,SAAS;EAEnC,MAAM,KAAK,MAAM,OAAO,EACtB,UACF,CAAC;EAED,KAAK,IAAI,WAAW,SAAS;EAE7B,OAAO;CACT;;;;CAKA,MAAa,IAAI,KAAe,OAAY,KAAc;EACxD,MAAM,YAAY,KAAK,SAAS,GAAG;EAEnC,KAAK,IAAI,WAAW,SAAS;EAE7B,IAAI,QAAQ,QACV,MAAM,KAAK;EAKb,MAAM,YADW,UAAU,MAAM,GACR,
|
|
1
|
+
{"version":3,"file":"database-cache-driver.mjs","names":[],"sources":["../../../../../../../core/src/cache/database-cache-driver.ts"],"sourcesContent":["import {\n BaseCacheDriver,\n type CacheData,\n type CacheDriver,\n type CacheKey,\n} from \"@warlock.js/cache\";\nimport { Model } from \"@warlock.js/cascade\";\n\nexport type DatabaseCacheOptions = {\n /**\n * Database model class\n */\n model?: typeof CacheModel;\n /**\n * Global prefix for the cache key\n */\n globalPrefix?: string | (() => string);\n /**\n * The default TTL for the cache in seconds\n *\n * @default Infinity\n */\n ttl?: number;\n};\n\nexport class CacheModel extends Model {\n public static table = \"cache\";\n}\n\nexport class DatabaseCacheDriver\n extends BaseCacheDriver<DatabaseCacheDriver, DatabaseCacheOptions>\n implements CacheDriver<DatabaseCacheDriver, DatabaseCacheOptions>\n{\n /**\n * {@inheritdoc}\n */\n public name = \"database\";\n\n /**\n * Database model class\n */\n public model!: typeof CacheModel;\n\n /**\n * {@inheritdoc}\n */\n public setOptions(options: DatabaseCacheOptions) {\n super.setOptions(options);\n\n this.model = options.model ?? CacheModel;\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async removeNamespace(namespace: string) {\n this.log(\"clearing\", namespace);\n\n namespace = this.parseKey(namespace);\n\n await this.model.delete({\n namespace,\n });\n\n this.log(\"cleared\", namespace);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async set(key: CacheKey, value: any, ttl?: number) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"caching\", parsedKey);\n\n if (ttl === undefined) {\n ttl = this.ttl;\n }\n\n // Extract namespace: all parts except the last (e.g., \"users.id.1\" -> \"users.id\")\n const keyParts = parsedKey.split(\".\");\n const namespace = keyParts.slice(0, -1).join(\".\") || parsedKey;\n\n // Find existing cache entry or create new one (upsert pattern)\n let cacheEntry = await this.model.first({ key: parsedKey });\n\n if (cacheEntry) {\n // Update existing entry\n cacheEntry.set(\"namespace\", namespace);\n cacheEntry.set(\"data\", value);\n cacheEntry.set(\"ttl\", ttl);\n cacheEntry.set(\"expiresAt\", this.getExpiresAt(ttl) || null);\n await cacheEntry.save();\n } else {\n // Create new entry\n await this.model.create({\n key: parsedKey,\n namespace,\n data: value,\n ttl,\n expiresAt: this.getExpiresAt(ttl) || null,\n });\n }\n\n this.log(\"cached\", parsedKey);\n\n return this;\n }\n\n /**\n * {@inheritdoc}\n */\n public async get(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"fetching\", parsedKey);\n\n const model = await this.model.first({\n key: parsedKey,\n });\n\n if (!model) {\n this.log(\"notFound\", parsedKey);\n return null;\n }\n\n const data: CacheData = {\n data: model.get(\"data\"),\n expiresAt: model.get(\"expiresAt\") as number | undefined,\n ttl: model.get(\"ttl\") as number | undefined,\n };\n\n return this.parseCachedData(parsedKey, data);\n }\n\n /**\n * {@inheritdoc}\n */\n public async remove(key: CacheKey) {\n const parsedKey = this.parseKey(key);\n\n this.log(\"removing\", parsedKey);\n\n await this.model.delete({\n key: parsedKey,\n });\n\n this.log(\"removed\", parsedKey);\n }\n\n /**\n * {@inheritdoc}\n */\n public async flush() {\n this.log(\"flushing\");\n if (this.options.globalPrefix) {\n this.removeNamespace(\"\");\n } else {\n await this.model.delete();\n }\n\n this.log(\"flushed\");\n }\n}\n"],"mappings":";;;;AAyBA,IAAa,aAAb,cAAgC,MAAM;;eACd;;AACxB;AAEA,IAAa,sBAAb,cACU,gBAEV;;;cAIgB;;;;;CAUd,AAAO,WAAW,SAA+B;EAC/C,MAAM,WAAW,OAAO;EAExB,KAAK,QAAQ,QAAQ,SAAS;EAE9B,OAAO;CACT;;;;CAKA,MAAa,gBAAgB,WAAmB;EAC9C,KAAK,IAAI,YAAY,SAAS;EAE9B,YAAY,KAAK,SAAS,SAAS;EAEnC,MAAM,KAAK,MAAM,OAAO,EACtB,UACF,CAAC;EAED,KAAK,IAAI,WAAW,SAAS;EAE7B,OAAO;CACT;;;;CAKA,MAAa,IAAI,KAAe,OAAY,KAAc;EACxD,MAAM,YAAY,KAAK,SAAS,GAAG;EAEnC,KAAK,IAAI,WAAW,SAAS;EAE7B,IAAI,QAAQ,QACV,MAAM,KAAK;EAKb,MAAM,YADW,UAAU,MAAM,GACR,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,GAAG,KAAK;EAGrD,IAAI,aAAa,MAAM,KAAK,MAAM,MAAM,EAAE,KAAK,UAAU,CAAC;EAE1D,IAAI,YAAY;GAEd,WAAW,IAAI,aAAa,SAAS;GACrC,WAAW,IAAI,QAAQ,KAAK;GAC5B,WAAW,IAAI,OAAO,GAAG;GACzB,WAAW,IAAI,aAAa,KAAK,aAAa,GAAG,KAAK,IAAI;GAC1D,MAAM,WAAW,KAAK;EACxB,OAEE,MAAM,KAAK,MAAM,OAAO;GACtB,KAAK;GACL;GACA,MAAM;GACN;GACA,WAAW,KAAK,aAAa,GAAG,KAAK;EACvC,CAAC;EAGH,KAAK,IAAI,UAAU,SAAS;EAE5B,OAAO;CACT;;;;CAKA,MAAa,IAAI,KAAe;EAC9B,MAAM,YAAY,KAAK,SAAS,GAAG;EAEnC,KAAK,IAAI,YAAY,SAAS;EAE9B,MAAM,QAAQ,MAAM,KAAK,MAAM,MAAM,EACnC,KAAK,UACP,CAAC;EAED,IAAI,CAAC,OAAO;GACV,KAAK,IAAI,YAAY,SAAS;GAC9B,OAAO;EACT;EAEA,MAAM,OAAkB;GACtB,MAAM,MAAM,IAAI,MAAM;GACtB,WAAW,MAAM,IAAI,WAAW;GAChC,KAAK,MAAM,IAAI,KAAK;EACtB;EAEA,OAAO,KAAK,gBAAgB,WAAW,IAAI;CAC7C;;;;CAKA,MAAa,OAAO,KAAe;EACjC,MAAM,YAAY,KAAK,SAAS,GAAG;EAEnC,KAAK,IAAI,YAAY,SAAS;EAE9B,MAAM,KAAK,MAAM,OAAO,EACtB,KAAK,UACP,CAAC;EAED,KAAK,IAAI,WAAW,SAAS;CAC/B;;;;CAKA,MAAa,QAAQ;EACnB,KAAK,IAAI,UAAU;EACnB,IAAI,KAAK,QAAQ,cACf,KAAK,gBAAgB,EAAE;OAEvB,MAAM,KAAK,MAAM,OAAO;EAG1B,KAAK,IAAI,SAAS;CACpB;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-commands.manager.mjs","names":[],"sources":["../../../../../../../core/src/cli/cli-commands.manager.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { Application } from \"../application\";\r\nimport { bootstrap } from \"../bootstrap\";\r\nimport { loadConfigFiles } from \"../config/load-config-files\";\r\nimport { connectorsManager } from \"../connectors/connectors-manager\";\r\nimport { registerConfiguredConnectors } from \"../connectors/register-configured-connectors\";\r\nimport { ConnectorLifecyclePhase } from \"../connectors/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { isDevWorker } from \"../dev-server/supervisor\";\r\nimport { manifestManager } from \"../manifest/manifest-manager\";\r\nimport { appPath } from \"../utils\";\r\nimport { loadEnvironmentFiles } from \"../utils/load-environment\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { CLICommand } from \"../commands/cli-command\";\r\nimport {\r\n displayBootError,\r\n displayCommandError,\r\n displayCommandHelp,\r\n displayCommandNotFound,\r\n displayCommandSuccess,\r\n displayExecutingCommand,\r\n displayHelp,\r\n displayMissingCommand,\r\n displayMissingOptions,\r\n displayWarlockVersionInTerminal,\r\n type HelpCommandInfo,\r\n isMatchingCommandName,\r\n} from \"./cli-commands.utils\";\r\nimport { cliCommandsLoader } from \"./commands-loader\";\r\nimport { frameworkCommands } from \"./framework-cli-commands\";\r\nimport { CliOptionValueError, ParsedCliArgs, parseCliArgs } from \"./parse-cli-args\";\r\nimport { findSimilar } from \"./string-similarity\";\r\nimport { CommandActionData, ResolvedCLICommandOption } from \"../commands/types\";\r\n\r\n/**\r\n * Best-effort budget (ms) for draining stdout/stderr before a forced exit.\r\n * Bounded so a stuck stream can never hang the CLI.\r\n */\r\nconst EXIT_FLUSH_BUDGET = 500;\r\n\r\n/**\r\n * Exit with `code` once stdout/stderr have drained.\r\n *\r\n * `process.exit()` truncates writes still queued on a non-TTY stdout — a pipe,\r\n * a CI log — which is how the *reason* for a failure gets lost even when the\r\n * exit code is right. `exitCode` is set up front so that a natural exit (should\r\n * the drain outlive the process's remaining work) is non-zero too.\r\n */\r\nfunction exitAfterFlush(code: number) {\r\n process.exitCode = code;\r\n\r\n const timer = setTimeout(() => process.exit(code), EXIT_FLUSH_BUDGET);\r\n timer.unref?.();\r\n\r\n let pending = 2;\r\n const drained = () => {\r\n if (--pending > 0) return;\r\n\r\n clearTimeout(timer);\r\n process.exit(code);\r\n };\r\n\r\n // A zero-length write's callback fires once everything queued ahead of it has\r\n // been handed to the OS.\r\n process.stdout.write(\"\", drained);\r\n process.stderr.write(\"\", drained);\r\n}\r\n\r\n/**\r\n * How a command run ended, from the process's point of view.\r\n *\r\n * - `succeeded` — finished, nothing rejected afterwards; the CLI may exit 0.\r\n * - `failed` — reported an error; the exit code is owned by whoever reported it.\r\n * - `running` — a persistent command (dev/start) now owns the process.\r\n *\r\n * `execute` returns this instead of exiting on success itself. A method that\r\n * terminates the process out from under its caller cannot be awaited honestly:\r\n * the exit it schedules outlives the call, so the success it represents is\r\n * still unsettled when `execute` resolves — which is exactly how a green check\r\n * ends up printed next to the failure that contradicts it.\r\n */\r\nexport type CommandRunVerdict = \"succeeded\" | \"failed\" | \"running\";\r\n\r\n/**\r\n * Whether this process is the CHILD half of a supervised command.\r\n *\r\n * ## The finding this encodes\r\n *\r\n * `warlock dev` printing `› Running dev...` twice was NOT a double boot, and\r\n * the distinction is the whole point of this function existing rather than the\r\n * line simply being deleted. The sequence, in file order:\r\n *\r\n * 1. `execute()` prints the header (below) — BEFORE `commandPreAction`.\r\n * 2. `cli/commands/dev-server.command.ts` `preAction` calls\r\n * `superviseDevServer()` when `!isDevWorker()`.\r\n * 3. `dev-server/supervisor.ts` `spawnWorker()` re-spawns `process.argv` with\r\n * `WARLOCK_DEV_WORKER=1`, so the child runs `warlock dev` again and prints\r\n * the header too.\r\n * 4. `superviseDevServer()` returns a promise that never settles, so the\r\n * supervisor's `await commandPreAction(...)` never resolves — `loadPreloaders`\r\n * and `command.execute` are unreachable in the supervisor.\r\n *\r\n * Step 4 is why only ONE process ever loads config, connectors, app modules or\r\n * a scheduler. Verified by running it: one `APP-CODE-LOADED`, one\r\n * `Application.markBooted`, one `⚡ Warlock.js` banner, one route registration,\r\n * two processes. The `2 job(s) registered` in the original report is two jobs\r\n * in one scheduler, not two schedulers — a genuine double boot would have\r\n * printed that warning twice.\r\n *\r\n * So the defect is exactly one duplicated line, and this is the guard that\r\n * keeps it one. `tests/unit/cli/dev-command-single-boot.test.ts` fails if the\r\n * supervisor ever starts running preloaders — i.e. if the double boot the\r\n * duplicated line looked like ever becomes real.\r\n */\r\nfunction isRespawnedWorker(): boolean {\r\n return isDevWorker();\r\n}\r\n\r\nexport class CLICommandsManager {\r\n /**\r\n * List of commands\r\n */\r\n public commands: CLICommand[] = [];\r\n\r\n /**\r\n * Commands map (name -> command)\r\n */\r\n public commandsMap: Map<string, CLICommand> = new Map();\r\n\r\n /**\r\n * Alias map (alias -> command name)\r\n */\r\n public aliasMap: Map<string, string> = new Map();\r\n\r\n /**\r\n * Register the given commands\r\n */\r\n public register(...commands: CLICommand[]): this {\r\n this.commands.push(...commands);\r\n\r\n commands.forEach((command) => {\r\n const commandKey = command.name.split(\" \")[0];\r\n\r\n manifestManager.addCommandToList(command.name, {\r\n relativePath: command.commandRelativePath,\r\n source: command.commandSource || \"project\",\r\n description: command.commandDescription,\r\n alias: command.commandAlias,\r\n options: command.commandOptions.length > 0 ? command.commandOptions : undefined,\r\n });\r\n\r\n this.commandsMap.set(commandKey, command);\r\n\r\n // Register alias if exists\r\n if (command.commandAlias) {\r\n this.aliasMap.set(command.commandAlias, commandKey);\r\n }\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get command by name or alias\r\n */\r\n public getCommand(name: string): CLICommand | undefined {\r\n // First try direct lookup\r\n let command = this.commandsMap.get(name);\r\n if (command) return command;\r\n\r\n // Try alias lookup\r\n const realName = this.aliasMap.get(name);\r\n if (realName) {\r\n return this.commandsMap.get(realName);\r\n }\r\n\r\n return;\r\n }\r\n\r\n /**\r\n * Get all command names and aliases\r\n * Used for fuzzy matching suggestions\r\n */\r\n public getAllCommandNames(): string[] {\r\n const names: string[] = [];\r\n\r\n // Add all command names\r\n for (const name of this.commandsMap.keys()) {\r\n names.push(name);\r\n }\r\n\r\n // Add all aliases\r\n for (const alias of this.aliasMap.keys()) {\r\n names.push(alias);\r\n }\r\n\r\n // Also include cached commands from manifest\r\n const manifestCommands = manifestManager.commandsJson?.commands || {};\r\n for (const name of Object.keys(manifestCommands)) {\r\n const baseName = name.split(\" \")[0];\r\n if (!names.includes(baseName)) {\r\n names.push(baseName);\r\n }\r\n const alias = manifestCommands[name].alias;\r\n if (alias && !names.includes(alias)) {\r\n names.push(alias);\r\n }\r\n }\r\n\r\n return names;\r\n }\r\n\r\n /**\r\n * Start the cli manager\r\n */\r\n public async start() {\r\n // Schema-less first pass: it exists to discover the command name and the\r\n // command-independent flags (--help, --version, --no-cache). Values here are\r\n // raw strings; the typed pass below is the one whose options reach a command.\r\n const { name, options } = parseCliArgs(process.argv);\r\n\r\n if (options.noCache) {\r\n manifestManager.clearCommandsCache();\r\n // remove the commands.json file as wel\r\n await manifestManager.removeCommandsFile();\r\n }\r\n\r\n if (options.version || options.v) {\r\n await displayWarlockVersionInTerminal();\r\n process.exit(0);\r\n }\r\n\r\n // Try to load from manifest first (fastest path)\r\n await manifestManager.loadCommands();\r\n\r\n // Register framework commands first (needed for help)\r\n this.register(\r\n ...frameworkCommands.map((command) => {\r\n if (!command.commandSource) {\r\n command.commandSource = \"framework\";\r\n }\r\n return command;\r\n }),\r\n );\r\n\r\n const isHelpCommand = options.help || options.h;\r\n\r\n // Handle global help (no command)\r\n if (!name && isHelpCommand) {\r\n await this.showGlobalHelp();\r\n process.exit(0);\r\n }\r\n\r\n // Handle warm cache\r\n if (options.warmCache) {\r\n await this.warmCache();\r\n process.exit(0);\r\n }\r\n\r\n if (!name) {\r\n displayMissingCommand();\r\n process.exit(1);\r\n }\r\n\r\n const command = await this.lazyGetCommand(name);\r\n\r\n if (manifestManager.hasChanges) {\r\n await manifestManager.saveCommands();\r\n }\r\n\r\n if (!command) {\r\n // Find similar commands for suggestions\r\n const allCommandNames = this.getAllCommandNames();\r\n const suggestions = findSimilar(name, allCommandNames).map((s) => s.value);\r\n\r\n displayCommandNotFound(name, suggestions);\r\n process.exit(1);\r\n }\r\n\r\n // Handle command-specific help\r\n if (isHelpCommand) {\r\n displayCommandHelp({\r\n name: command.name,\r\n alias: command.commandAlias,\r\n description: command.commandDescription,\r\n options: command.commandOptions,\r\n });\r\n process.exit(0);\r\n }\r\n\r\n // The first parse above had no command to consult, so every value in it is\r\n // a raw string. Now that the command is resolved, re-read argv against its\r\n // declared options so a `type: \"boolean\"` option arrives as a boolean.\r\n const resolved = this.resolveCommandArgs(command);\r\n\r\n const verdict = await this.execute(command, {\r\n options: resolved.options,\r\n args: resolved.args,\r\n });\r\n\r\n // The process-level exit belongs to the entrypoint, not to `execute`.\r\n // A one-shot command can leave a handle open (a db connection, a watcher a\r\n // plugin forgot to close), so the CLI exits explicitly rather than waiting\r\n // for the loop to drain — but only on a verdict of `succeeded`. `failed`\r\n // already exited non-zero through whoever reported it, and `running` means\r\n // a persistent command owns the process now.\r\n if (verdict === \"succeeded\") {\r\n exitAfterFlush(0);\r\n }\r\n }\r\n\r\n /**\r\n * Re-parse argv against the resolved command's declared options.\r\n *\r\n * parseCliArgs runs twice on purpose. The first run produces the command\r\n * name, and there is no schema to consult yet; the second one, here, can be\r\n * type-aware. Re-reading argv (rather than post-processing the first result)\r\n * is what makes `--rollback users.ts` recoverable: by the time the first pass\r\n * returns, the swallowed positional is indistinguishable from a value.\r\n */\r\n protected resolveCommandArgs(command: CLICommand, argv: string[] = process.argv): ParsedCliArgs {\r\n try {\r\n return parseCliArgs(argv, command.commandOptions);\r\n } catch (error) {\r\n if (error instanceof CliOptionValueError) {\r\n console.log();\r\n console.log(` ${colors.redBright(\"Error:\")} ${error.message}`);\r\n console.log();\r\n process.exit(1);\r\n }\r\n\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Show global help with all commands\r\n * Uses manifest if available for fast display\r\n */\r\n protected async showGlobalHelp() {\r\n if (\r\n manifestManager.isCommandLoaded &&\r\n Object.keys(manifestManager.commandsJson?.commands || {}).length > 0\r\n ) {\r\n // Use manifest directly - no need to load command files\r\n const helpCommands: HelpCommandInfo[] = Object.entries(\r\n manifestManager.commandsJson?.commands || {},\r\n ).map(([name, cmd]) => ({\r\n name,\r\n alias: cmd.alias,\r\n description: cmd.description,\r\n source: cmd.source,\r\n }));\r\n\r\n await displayHelp(helpCommands);\r\n return;\r\n }\r\n\r\n // Fallback: No manifest, build from registered commands\r\n // This happens on first run before warm-cache\r\n await this.loadPluginsCommands();\r\n\r\n const projectCommands = await cliCommandsLoader.scanAll();\r\n\r\n this.register(...projectCommands);\r\n\r\n await manifestManager.saveCommands();\r\n\r\n // Build help info from registered commands\r\n const helpCommands: HelpCommandInfo[] = this.commands.map((cmd) => ({\r\n name: cmd.name,\r\n alias: cmd.commandAlias,\r\n description: cmd.commandDescription,\r\n source: cmd.commandSource || \"project\",\r\n }));\r\n\r\n await displayHelp(helpCommands);\r\n }\r\n\r\n /**\r\n * Warm cache - scan all project commands and save to manifest\r\n */\r\n protected async warmCache() {\r\n console.log();\r\n console.log(` ${colors.cyan(\"›\")} Scanning project commands...`);\r\n\r\n const projectCommands = await cliCommandsLoader.scanAll();\r\n\r\n this.register(...projectCommands);\r\n\r\n await manifestManager.saveCommands();\r\n\r\n console.log(\r\n ` ${colors.green(\"✔\")} Cached ${colors.bold(String(projectCommands.length))} project commands`,\r\n );\r\n console.log();\r\n }\r\n\r\n /**\r\n * Load plugins commands\r\n */\r\n protected async loadPluginsCommands() {\r\n await warlockConfigManager.load();\r\n\r\n if (warlockConfigManager.isLoaded) {\r\n this.register(\r\n ...(warlockConfigManager.get(\"cli\")?.commands || []).map((command) => {\r\n if (!command.commandSource) {\r\n command.commandSource = \"plugin\";\r\n }\r\n return command;\r\n }),\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Try to find the command based on the given command name or alias\r\n */\r\n protected async lazyGetCommand(name: string) {\r\n // first step, try to find it directly through current commands\r\n let command = this.getCommand(name);\r\n if (command) {\r\n return command;\r\n }\r\n\r\n // second step, try to find it through warlock config commands\r\n await this.loadPluginsCommands();\r\n\r\n command = this.getCommand(name);\r\n\r\n if (command) {\r\n return command;\r\n }\r\n\r\n // third step, try to find it through project commands\r\n await this.loadProjectCommands(name);\r\n\r\n command = this.getCommand(name);\r\n if (command) {\r\n return command;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Load project commands\r\n */\r\n protected async loadProjectCommands(name: string) {\r\n // first get the commands.json contents as an object\r\n const jsonCommandsFile = await manifestManager.loadCommands();\r\n\r\n if (jsonCommandsFile) {\r\n // Check by name or alias\r\n for (const fullCommandName in jsonCommandsFile.commands) {\r\n const cmdMeta = jsonCommandsFile.commands[fullCommandName];\r\n\r\n // Match by name or alias\r\n if (isMatchingCommandName(fullCommandName, name) || cmdMeta.alias === name) {\r\n const commandPath = cmdMeta.relativePath;\r\n if (commandPath) {\r\n const executedCommand = await cliCommandsLoader.load(commandPath);\r\n\r\n if (executedCommand) {\r\n executedCommand.$relativePath(commandPath);\r\n this.register(executedCommand);\r\n return;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n const command = await cliCommandsLoader.locate(name);\r\n\r\n if (command) {\r\n this.register(command);\r\n return;\r\n }\r\n }\r\n\r\n /**\r\n * Validate required options\r\n */\r\n protected validateOptions(\r\n command: CLICommand,\r\n options: Record<string, string | boolean | number>,\r\n ): ResolvedCLICommandOption[] {\r\n const missing: ResolvedCLICommandOption[] = [];\r\n\r\n command.commandOptions.forEach((opt) => {\r\n if (opt.required) {\r\n // Check if option is provided by name or alias\r\n const hasOption =\r\n options[opt.name] !== undefined || (opt.alias && options[opt.alias] !== undefined);\r\n if (!hasOption) {\r\n missing.push(opt);\r\n }\r\n }\r\n });\r\n\r\n return missing;\r\n }\r\n\r\n /**\r\n * Apply default values to options\r\n */\r\n protected applyDefaultOptions(\r\n command: CLICommand,\r\n options: Record<string, string | boolean | number>,\r\n ): Record<string, string | boolean | number> {\r\n const result = { ...options };\r\n\r\n command.commandOptions.forEach((opt) => {\r\n if (opt.defaultValue !== undefined) {\r\n const hasOption =\r\n result[opt.name] !== undefined || (opt.alias && result[opt.alias] !== undefined);\r\n if (!hasOption) {\r\n result[opt.name] = opt.defaultValue;\r\n }\r\n }\r\n\r\n // `!== undefined`, not truthiness: `-r=false` now resolves to the boolean\r\n // `false`, and a truthiness test would drop it before it ever reached the\r\n // canonical name — leaving `rollback` undefined instead of explicitly off.\r\n if (\r\n opt.alias !== undefined &&\r\n result[opt.alias] !== undefined &&\r\n result[opt.name] === undefined\r\n ) {\r\n result[opt.name] = result[opt.alias];\r\n }\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Execute the given command and report how it ended.\r\n *\r\n * Returning the verdict — rather than exiting on success from in here — is\r\n * what makes the awaited result trustworthy: see {@link CommandRunVerdict}.\r\n */\r\n public async execute(command: CLICommand, data: CommandActionData): Promise<CommandRunVerdict> {\r\n const startTime = Date.now();\r\n\r\n // Validate required options\r\n const missingOptions = this.validateOptions(command, data.options);\r\n if (missingOptions.length > 0) {\r\n displayMissingOptions(missingOptions);\r\n process.exit(1);\r\n }\r\n\r\n // Apply default values\r\n data.options = this.applyDefaultOptions(command, data.options);\r\n\r\n // ONE header per `warlock <command>` the user typed — not one per process.\r\n //\r\n // `warlock dev` is TWO processes by design: the invocation the user typed\r\n // becomes a thin supervisor (`dev-server/supervisor.ts`) that re-spawns\r\n // `process.argv` with `WARLOCK_DEV_WORKER=1`, and the child is the actual\r\n // server. Both are `warlock dev`, so both reached this line and the user saw\r\n //\r\n // › Running dev...\r\n // › Running dev...\r\n //\r\n // which reads exactly like the application booting twice. It is not — see\r\n // the block comment on `isRespawnedWorker` — but the line was still wrong,\r\n // and it re-printed on every crash-restart on top of the supervisor's own\r\n // \"restarting\" notice.\r\n //\r\n // The SUPERVISOR keeps the header, not the worker: it prints immediately\r\n // (so a slow boot is not silent), it prints once for the whole session\r\n // (restarts are already announced as restarts), and it is the process the\r\n // user actually started.\r\n if (!isRespawnedWorker()) {\r\n displayExecutingCommand(command.name);\r\n }\r\n\r\n if (command.commandPreAction) {\r\n await command.commandPreAction(data);\r\n }\r\n\r\n // load preloaders\r\n if (command.commandPreload) {\r\n try {\r\n await this.loadPreloaders(command);\r\n } catch (error) {\r\n // A preload failure (a bad import in a config file, a connector that\r\n // throws on boot, a missing module export) happens BEFORE the command's\r\n // action runs — there is no run loop to recover into, so it is ALWAYS\r\n // fatal, persistent command (dev/start) or not. Surface the real cause\r\n // and the offending file, then exit. This MUST stay outside the\r\n // command-execute try/catch below: letting a preload rejection escape\r\n // turned `warlock dev` into a silent hang — the unhandled rejection slid\r\n // past while the already-started loader worker thread kept the process\r\n // alive, freezing just after the banner with no error printed.\r\n displayBootError(command.name, error as Error);\r\n process.exit(1);\r\n }\r\n }\r\n\r\n this.captureFatalAsyncErrors(command);\r\n\r\n try {\r\n await command.execute(data);\r\n\r\n // Persistent commands (dev/start) hand control to their run loop here.\r\n // There is no success banner to print and nothing to exit for.\r\n if (command.isPersistent) return \"running\";\r\n\r\n // Yield one full event-loop turn before declaring success, and AWAIT that\r\n // yield. Node flags an unhandled rejection only after the current turn's\r\n // microtasks drain, so reporting success synchronously here fired FIRST\r\n // and erased the failure — a `build` whose async work rejected exited 0\r\n // having emitted nothing.\r\n //\r\n // The await is the load-bearing part, not the yield. Scheduling this as a\r\n // bare `setImmediate` let `execute()` RESOLVE while the verdict was still\r\n // pending: the callback outlived the call it belonged to, so the green\r\n // check could surface after the caller had already moved on — in a later\r\n // command, or after a rejection reported in the interim, which the guard\r\n // below cannot see because by then it reads the state of a run that has\r\n // already ended. Awaiting binds the banner to THIS invocation: `execute`\r\n // does not resolve until the verdict it is about to report has settled.\r\n //\r\n // `setImmediate` runs in the same loop iteration's check phase, so this\r\n // cannot hang on a lingering handle the way dropping the exit would; it\r\n // only lets `captureFatalAsyncErrors` win the race when there is one.\r\n await new Promise<void>((resolve) => setImmediate(resolve));\r\n\r\n // The yield is what gives a floating rejection the chance to be reported;\r\n // this is what acts on it. A green check printed after that error would\r\n // contradict it, and the exit that follows would reset `process.exitCode`\r\n // to 0 — re-erasing the very failure this whole path exists to surface.\r\n if (this.fatalAsyncErrorReported) return \"failed\";\r\n\r\n displayCommandSuccess(command.name, Date.now() - startTime);\r\n\r\n return \"succeeded\";\r\n } catch (error) {\r\n displayCommandError(command.name, error as Error);\r\n // Persistent commands (dev/start) own their startup-failure exits;\r\n // by the time an error reaches here they've already entered their\r\n // run loop, so it's a runtime error we log but don't crash on —\r\n // HMR or process supervisors can recover. Non-persistent\r\n // one-shot commands always exit on failure.\r\n if (!command.isPersistent) {\r\n process.exit(1);\r\n }\r\n\r\n return \"failed\";\r\n }\r\n }\r\n\r\n /**\r\n * Fail loudly on async errors that escape the command's own promise.\r\n *\r\n * `execute` awaits `command.execute(data)`, but work the command merely\r\n * *started* settles outside that await — `@mongez/events` `trigger` /\r\n * `triggerAll` are synchronous and do not await their listeners, so an async\r\n * listener that throws leaves a floating rejection behind. Node reports it at\r\n * the end of the turn, by which point the CLI had already exited 0: the\r\n * failure was erased and the command looked green.\r\n *\r\n * Persistent commands (dev/start) are exempt from the exit for the same\r\n * reason as the catch block in `execute`: by the time they reach this point\r\n * they own a run loop that is expected to survive runtime errors so HMR or a\r\n * supervisor can recover. They still print — silence is what made this class\r\n * of failure invisible in the first place.\r\n */\r\n protected fatalAsyncErrorReported = false;\r\n\r\n protected captureFatalAsyncErrors(command: CLICommand) {\r\n const onFatalAsyncError = (reason: unknown) => {\r\n // A rejection reason is not necessarily an Error (`Promise.reject(\"x\")`),\r\n // and a crash handler must never itself throw.\r\n const error = reason instanceof Error ? reason : new Error(String(reason));\r\n\r\n // Set before printing, so the deferred success banner is suppressed even\r\n // if displaying the error yields.\r\n this.fatalAsyncErrorReported = true;\r\n\r\n displayCommandError(command.name, error);\r\n\r\n if (!command.isPersistent) {\r\n exitAfterFlush(1);\r\n }\r\n };\r\n\r\n process.on(\"unhandledRejection\", onFatalAsyncError);\r\n process.on(\"uncaughtException\", onFatalAsyncError);\r\n }\r\n\r\n /**\r\n * Load preloaders\r\n */\r\n protected async loadPreloaders(command: CLICommand) {\r\n const preloaders = command.commandPreload || {};\r\n\r\n if (preloaders.runtimeStrategy) {\r\n Application.setRuntimeStrategy(preloaders.runtimeStrategy);\r\n }\r\n\r\n // Override, not a default: a command that declares an environment gets it\r\n // regardless of what the shell exported. Then read it back — `process.env`\r\n // is process-global and anything may have written to it, so a value that\r\n // did not take must fail here, loudly, rather than surface later as a\r\n // production React bundle under `warlock dev`.\r\n if (preloaders.environment) {\r\n Application.setEnvironment(preloaders.environment);\r\n\r\n if (process.env.NODE_ENV !== preloaders.environment) {\r\n throw new Error(\r\n `Command \"${command.name}\" set NODE_ENV to \"${preloaders.environment}\", but it read back as ` +\r\n `\"${process.env.NODE_ENV}\". The environment did not take, and every decision made ` +\r\n `downstream of it — env file selection, bundler dep optimisation — would be wrong.`,\r\n );\r\n }\r\n }\r\n\r\n // Env BEFORE config, unconditionally, for every command.\r\n //\r\n // `warlock.config.ts` is a module whose body runs on import, and projects\r\n // legitimately call `env(\"BUILD_OUT\", \"dist\")` in it. Loading config first\r\n // evaluated that body against an empty env store, so every such call\r\n // silently returned its default — under `build` and `start`, which never\r\n // loaded env at all, and under `dev` too, because config was loaded first\r\n // regardless. The ordering is load-bearing in the other direction as well:\r\n // `loadEnv()` reads `process.env.NODE_ENV` at call time to choose\r\n // `.env.<NODE_ENV>` over `.env`, so it must follow `setEnvironment` above.\r\n await loadEnvironmentFiles();\r\n\r\n await warlockConfigManager.load();\r\n\r\n if (preloaders.config || preloaders.bootstrap || preloaders.prestart) {\r\n await filesOrchestrator.init();\r\n }\r\n\r\n // `preloaders.env` is gone: env is loaded above for every command, so the\r\n // branch that used to serve it could only re-parse the same files and\r\n // re-override `process.env` a second time.\r\n if (preloaders.bootstrap) {\r\n await bootstrap();\r\n\r\n if (await fileExistsAsync(appPath(\"bootstrap.ts\"))) {\r\n await filesOrchestrator.load(\"src/app/bootstrap.ts\");\r\n }\r\n }\r\n\r\n // Load configuration files\r\n if (preloaders.config) {\r\n await loadConfigFiles(preloaders.config);\r\n }\r\n\r\n if (preloaders.prestart) {\r\n if (await fileExistsAsync(appPath(\"prestart.ts\"))) {\r\n await filesOrchestrator.load(\"src/app/prestart.ts\");\r\n }\r\n }\r\n\r\n // Initialize connectors.\r\n // `connectors: true` only starts the early phase here — late-phase\r\n // connectors (http, socket) start after the command's action loads\r\n // app code. Explicit lists bypass phase splitting (caller knows\r\n // exactly which connectors they want).\r\n if (preloaders.connectors) {\r\n // REGISTER before starting — the non-bundled counterpart of section 2.5\r\n // of the generated production entry, and the \"dev preloader\" path\r\n // `register-configured-connectors.ts` documents.\r\n //\r\n // Without this, `warlock.config.ts > connectors` drove the BUILD only:\r\n // `warlock build` read the array to drain each connector's contribution,\r\n // but under `warlock dev` nothing ever registered it, so an app had to\r\n // call `connectorsManager.register(...)` from its own `main.ts` to get a\r\n // connector running. Apps that did BOTH then registered the same\r\n // connector twice in production — once here from the baked config, once\r\n // from their own app code — and it booted twice, which surfaced as\r\n // `Route name \"...\" is already taken` while installing page routes.\r\n //\r\n // The array is omitted deliberately, so the function reads the config\r\n // loaded above; `expectedNames` is omitted too, because drift is a\r\n // build↔runtime relationship and dev has no build to drift from.\r\n //\r\n // The idempotency this function provides is against ITSELF — a second\r\n // call skips names the first registered. It does NOT protect against an\r\n // app calling `connectorsManager.register()` directly, which pushes\r\n // unconditionally, and which runs AFTER this point in both modes. So a\r\n // connector belongs in the config array or in app code, never both:\r\n // listing it in both is the duplicate this comment's route-name error\r\n // describes.\r\n registerConfiguredConnectors();\r\n\r\n if (preloaders.connectors === true) {\r\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Early);\r\n } else {\r\n await connectorsManager.start(preloaders.connectors);\r\n }\r\n }\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli-commands.manager.mjs","names":[],"sources":["../../../../../../../core/src/cli/cli-commands.manager.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { Application } from \"../application\";\r\nimport { bootstrap } from \"../bootstrap\";\r\nimport { loadConfigFiles } from \"../config/load-config-files\";\r\nimport { connectorsManager } from \"../connectors/connectors-manager\";\r\nimport { registerConfiguredConnectors } from \"../connectors/register-configured-connectors\";\r\nimport { ConnectorLifecyclePhase } from \"../connectors/types\";\r\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\r\nimport { isDevWorker } from \"../dev-server/supervisor\";\r\nimport { manifestManager } from \"../manifest/manifest-manager\";\r\nimport { appPath } from \"../utils\";\r\nimport { loadEnvironmentFiles } from \"../utils/load-environment\";\r\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\r\nimport { CLICommand } from \"../commands/cli-command\";\r\nimport {\r\n displayBootError,\r\n displayCommandError,\r\n displayCommandHelp,\r\n displayCommandNotFound,\r\n displayCommandSuccess,\r\n displayExecutingCommand,\r\n displayHelp,\r\n displayMissingCommand,\r\n displayMissingOptions,\r\n displayWarlockVersionInTerminal,\r\n type HelpCommandInfo,\r\n isMatchingCommandName,\r\n} from \"./cli-commands.utils\";\r\nimport { cliCommandsLoader } from \"./commands-loader\";\r\nimport { frameworkCommands } from \"./framework-cli-commands\";\r\nimport { CliOptionValueError, ParsedCliArgs, parseCliArgs } from \"./parse-cli-args\";\r\nimport { findSimilar } from \"./string-similarity\";\r\nimport { CommandActionData, ResolvedCLICommandOption } from \"../commands/types\";\r\n\r\n/**\r\n * Best-effort budget (ms) for draining stdout/stderr before a forced exit.\r\n * Bounded so a stuck stream can never hang the CLI.\r\n */\r\nconst EXIT_FLUSH_BUDGET = 500;\r\n\r\n/**\r\n * Exit with `code` once stdout/stderr have drained.\r\n *\r\n * `process.exit()` truncates writes still queued on a non-TTY stdout — a pipe,\r\n * a CI log — which is how the *reason* for a failure gets lost even when the\r\n * exit code is right. `exitCode` is set up front so that a natural exit (should\r\n * the drain outlive the process's remaining work) is non-zero too.\r\n */\r\nfunction exitAfterFlush(code: number) {\r\n process.exitCode = code;\r\n\r\n const timer = setTimeout(() => process.exit(code), EXIT_FLUSH_BUDGET);\r\n timer.unref?.();\r\n\r\n let pending = 2;\r\n const drained = () => {\r\n if (--pending > 0) return;\r\n\r\n clearTimeout(timer);\r\n process.exit(code);\r\n };\r\n\r\n // A zero-length write's callback fires once everything queued ahead of it has\r\n // been handed to the OS.\r\n process.stdout.write(\"\", drained);\r\n process.stderr.write(\"\", drained);\r\n}\r\n\r\n/**\r\n * How a command run ended, from the process's point of view.\r\n *\r\n * - `succeeded` — finished, nothing rejected afterwards; the CLI may exit 0.\r\n * - `failed` — reported an error; the exit code is owned by whoever reported it.\r\n * - `running` — a persistent command (dev/start) now owns the process.\r\n *\r\n * `execute` returns this instead of exiting on success itself. A method that\r\n * terminates the process out from under its caller cannot be awaited honestly:\r\n * the exit it schedules outlives the call, so the success it represents is\r\n * still unsettled when `execute` resolves — which is exactly how a green check\r\n * ends up printed next to the failure that contradicts it.\r\n */\r\nexport type CommandRunVerdict = \"succeeded\" | \"failed\" | \"running\";\r\n\r\n/**\r\n * Whether this process is the CHILD half of a supervised command.\r\n *\r\n * ## The finding this encodes\r\n *\r\n * `warlock dev` printing `› Running dev...` twice was NOT a double boot, and\r\n * the distinction is the whole point of this function existing rather than the\r\n * line simply being deleted. The sequence, in file order:\r\n *\r\n * 1. `execute()` prints the header (below) — BEFORE `commandPreAction`.\r\n * 2. `cli/commands/dev-server.command.ts` `preAction` calls\r\n * `superviseDevServer()` when `!isDevWorker()`.\r\n * 3. `dev-server/supervisor.ts` `spawnWorker()` re-spawns `process.argv` with\r\n * `WARLOCK_DEV_WORKER=1`, so the child runs `warlock dev` again and prints\r\n * the header too.\r\n * 4. `superviseDevServer()` returns a promise that never settles, so the\r\n * supervisor's `await commandPreAction(...)` never resolves — `loadPreloaders`\r\n * and `command.execute` are unreachable in the supervisor.\r\n *\r\n * Step 4 is why only ONE process ever loads config, connectors, app modules or\r\n * a scheduler. Verified by running it: one `APP-CODE-LOADED`, one\r\n * `Application.markBooted`, one `⚡ Warlock.js` banner, one route registration,\r\n * two processes. The `2 job(s) registered` in the original report is two jobs\r\n * in one scheduler, not two schedulers — a genuine double boot would have\r\n * printed that warning twice.\r\n *\r\n * So the defect is exactly one duplicated line, and this is the guard that\r\n * keeps it one. `tests/unit/cli/dev-command-single-boot.test.ts` fails if the\r\n * supervisor ever starts running preloaders — i.e. if the double boot the\r\n * duplicated line looked like ever becomes real.\r\n */\r\nfunction isRespawnedWorker(): boolean {\r\n return isDevWorker();\r\n}\r\n\r\nexport class CLICommandsManager {\r\n /**\r\n * List of commands\r\n */\r\n public commands: CLICommand[] = [];\r\n\r\n /**\r\n * Commands map (name -> command)\r\n */\r\n public commandsMap: Map<string, CLICommand> = new Map();\r\n\r\n /**\r\n * Alias map (alias -> command name)\r\n */\r\n public aliasMap: Map<string, string> = new Map();\r\n\r\n /**\r\n * Register the given commands\r\n */\r\n public register(...commands: CLICommand[]): this {\r\n this.commands.push(...commands);\r\n\r\n commands.forEach((command) => {\r\n const commandKey = command.name.split(\" \")[0];\r\n\r\n manifestManager.addCommandToList(command.name, {\r\n relativePath: command.commandRelativePath,\r\n source: command.commandSource || \"project\",\r\n description: command.commandDescription,\r\n alias: command.commandAlias,\r\n options: command.commandOptions.length > 0 ? command.commandOptions : undefined,\r\n });\r\n\r\n this.commandsMap.set(commandKey, command);\r\n\r\n // Register alias if exists\r\n if (command.commandAlias) {\r\n this.aliasMap.set(command.commandAlias, commandKey);\r\n }\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get command by name or alias\r\n */\r\n public getCommand(name: string): CLICommand | undefined {\r\n // First try direct lookup\r\n let command = this.commandsMap.get(name);\r\n if (command) return command;\r\n\r\n // Try alias lookup\r\n const realName = this.aliasMap.get(name);\r\n if (realName) {\r\n return this.commandsMap.get(realName);\r\n }\r\n\r\n return;\r\n }\r\n\r\n /**\r\n * Get all command names and aliases\r\n * Used for fuzzy matching suggestions\r\n */\r\n public getAllCommandNames(): string[] {\r\n const names: string[] = [];\r\n\r\n // Add all command names\r\n for (const name of this.commandsMap.keys()) {\r\n names.push(name);\r\n }\r\n\r\n // Add all aliases\r\n for (const alias of this.aliasMap.keys()) {\r\n names.push(alias);\r\n }\r\n\r\n // Also include cached commands from manifest\r\n const manifestCommands = manifestManager.commandsJson?.commands || {};\r\n for (const name of Object.keys(manifestCommands)) {\r\n const baseName = name.split(\" \")[0];\r\n if (!names.includes(baseName)) {\r\n names.push(baseName);\r\n }\r\n const alias = manifestCommands[name].alias;\r\n if (alias && !names.includes(alias)) {\r\n names.push(alias);\r\n }\r\n }\r\n\r\n return names;\r\n }\r\n\r\n /**\r\n * Start the cli manager\r\n */\r\n public async start() {\r\n // Schema-less first pass: it exists to discover the command name and the\r\n // command-independent flags (--help, --version, --no-cache). Values here are\r\n // raw strings; the typed pass below is the one whose options reach a command.\r\n const { name, options } = parseCliArgs(process.argv);\r\n\r\n if (options.noCache) {\r\n manifestManager.clearCommandsCache();\r\n // remove the commands.json file as wel\r\n await manifestManager.removeCommandsFile();\r\n }\r\n\r\n if (options.version || options.v) {\r\n await displayWarlockVersionInTerminal();\r\n process.exit(0);\r\n }\r\n\r\n // Try to load from manifest first (fastest path)\r\n await manifestManager.loadCommands();\r\n\r\n // Register framework commands first (needed for help)\r\n this.register(\r\n ...frameworkCommands.map((command) => {\r\n if (!command.commandSource) {\r\n command.commandSource = \"framework\";\r\n }\r\n return command;\r\n }),\r\n );\r\n\r\n const isHelpCommand = options.help || options.h;\r\n\r\n // Handle global help (no command)\r\n if (!name && isHelpCommand) {\r\n await this.showGlobalHelp();\r\n process.exit(0);\r\n }\r\n\r\n // Handle warm cache\r\n if (options.warmCache) {\r\n await this.warmCache();\r\n process.exit(0);\r\n }\r\n\r\n if (!name) {\r\n displayMissingCommand();\r\n process.exit(1);\r\n }\r\n\r\n const command = await this.lazyGetCommand(name);\r\n\r\n if (manifestManager.hasChanges) {\r\n await manifestManager.saveCommands();\r\n }\r\n\r\n if (!command) {\r\n // Find similar commands for suggestions\r\n const allCommandNames = this.getAllCommandNames();\r\n const suggestions = findSimilar(name, allCommandNames).map((s) => s.value);\r\n\r\n displayCommandNotFound(name, suggestions);\r\n process.exit(1);\r\n }\r\n\r\n // Handle command-specific help\r\n if (isHelpCommand) {\r\n displayCommandHelp({\r\n name: command.name,\r\n alias: command.commandAlias,\r\n description: command.commandDescription,\r\n options: command.commandOptions,\r\n });\r\n process.exit(0);\r\n }\r\n\r\n // The first parse above had no command to consult, so every value in it is\r\n // a raw string. Now that the command is resolved, re-read argv against its\r\n // declared options so a `type: \"boolean\"` option arrives as a boolean.\r\n const resolved = this.resolveCommandArgs(command);\r\n\r\n const verdict = await this.execute(command, {\r\n options: resolved.options,\r\n args: resolved.args,\r\n });\r\n\r\n // The process-level exit belongs to the entrypoint, not to `execute`.\r\n // A one-shot command can leave a handle open (a db connection, a watcher a\r\n // plugin forgot to close), so the CLI exits explicitly rather than waiting\r\n // for the loop to drain — but only on a verdict of `succeeded`. `failed`\r\n // already exited non-zero through whoever reported it, and `running` means\r\n // a persistent command owns the process now.\r\n if (verdict === \"succeeded\") {\r\n exitAfterFlush(0);\r\n }\r\n }\r\n\r\n /**\r\n * Re-parse argv against the resolved command's declared options.\r\n *\r\n * parseCliArgs runs twice on purpose. The first run produces the command\r\n * name, and there is no schema to consult yet; the second one, here, can be\r\n * type-aware. Re-reading argv (rather than post-processing the first result)\r\n * is what makes `--rollback users.ts` recoverable: by the time the first pass\r\n * returns, the swallowed positional is indistinguishable from a value.\r\n */\r\n protected resolveCommandArgs(command: CLICommand, argv: string[] = process.argv): ParsedCliArgs {\r\n try {\r\n return parseCliArgs(argv, command.commandOptions);\r\n } catch (error) {\r\n if (error instanceof CliOptionValueError) {\r\n console.log();\r\n console.log(` ${colors.redBright(\"Error:\")} ${error.message}`);\r\n console.log();\r\n process.exit(1);\r\n }\r\n\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Show global help with all commands\r\n * Uses manifest if available for fast display\r\n */\r\n protected async showGlobalHelp() {\r\n if (\r\n manifestManager.isCommandLoaded &&\r\n Object.keys(manifestManager.commandsJson?.commands || {}).length > 0\r\n ) {\r\n // Use manifest directly - no need to load command files\r\n const helpCommands: HelpCommandInfo[] = Object.entries(\r\n manifestManager.commandsJson?.commands || {},\r\n ).map(([name, cmd]) => ({\r\n name,\r\n alias: cmd.alias,\r\n description: cmd.description,\r\n source: cmd.source,\r\n }));\r\n\r\n await displayHelp(helpCommands);\r\n return;\r\n }\r\n\r\n // Fallback: No manifest, build from registered commands\r\n // This happens on first run before warm-cache\r\n await this.loadPluginsCommands();\r\n\r\n const projectCommands = await cliCommandsLoader.scanAll();\r\n\r\n this.register(...projectCommands);\r\n\r\n await manifestManager.saveCommands();\r\n\r\n // Build help info from registered commands\r\n const helpCommands: HelpCommandInfo[] = this.commands.map((cmd) => ({\r\n name: cmd.name,\r\n alias: cmd.commandAlias,\r\n description: cmd.commandDescription,\r\n source: cmd.commandSource || \"project\",\r\n }));\r\n\r\n await displayHelp(helpCommands);\r\n }\r\n\r\n /**\r\n * Warm cache - scan all project commands and save to manifest\r\n */\r\n protected async warmCache() {\r\n console.log();\r\n console.log(` ${colors.cyan(\"›\")} Scanning project commands...`);\r\n\r\n const projectCommands = await cliCommandsLoader.scanAll();\r\n\r\n this.register(...projectCommands);\r\n\r\n await manifestManager.saveCommands();\r\n\r\n console.log(\r\n ` ${colors.green(\"✔\")} Cached ${colors.bold(String(projectCommands.length))} project commands`,\r\n );\r\n console.log();\r\n }\r\n\r\n /**\r\n * Load plugins commands\r\n */\r\n protected async loadPluginsCommands() {\r\n await warlockConfigManager.load();\r\n\r\n if (warlockConfigManager.isLoaded) {\r\n this.register(\r\n ...(warlockConfigManager.get(\"cli\")?.commands || []).map((command) => {\r\n if (!command.commandSource) {\r\n command.commandSource = \"plugin\";\r\n }\r\n return command;\r\n }),\r\n );\r\n }\r\n }\r\n\r\n /**\r\n * Try to find the command based on the given command name or alias\r\n */\r\n protected async lazyGetCommand(name: string) {\r\n // first step, try to find it directly through current commands\r\n let command = this.getCommand(name);\r\n if (command) {\r\n return command;\r\n }\r\n\r\n // second step, try to find it through warlock config commands\r\n await this.loadPluginsCommands();\r\n\r\n command = this.getCommand(name);\r\n\r\n if (command) {\r\n return command;\r\n }\r\n\r\n // third step, try to find it through project commands\r\n await this.loadProjectCommands(name);\r\n\r\n command = this.getCommand(name);\r\n if (command) {\r\n return command;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Load project commands\r\n */\r\n protected async loadProjectCommands(name: string) {\r\n // first get the commands.json contents as an object\r\n const jsonCommandsFile = await manifestManager.loadCommands();\r\n\r\n if (jsonCommandsFile) {\r\n // Check by name or alias\r\n for (const fullCommandName in jsonCommandsFile.commands) {\r\n const cmdMeta = jsonCommandsFile.commands[fullCommandName];\r\n\r\n // Match by name or alias\r\n if (isMatchingCommandName(fullCommandName, name) || cmdMeta.alias === name) {\r\n const commandPath = cmdMeta.relativePath;\r\n if (commandPath) {\r\n const executedCommand = await cliCommandsLoader.load(commandPath);\r\n\r\n if (executedCommand) {\r\n executedCommand.$relativePath(commandPath);\r\n this.register(executedCommand);\r\n return;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n const command = await cliCommandsLoader.locate(name);\r\n\r\n if (command) {\r\n this.register(command);\r\n return;\r\n }\r\n }\r\n\r\n /**\r\n * Validate required options\r\n */\r\n protected validateOptions(\r\n command: CLICommand,\r\n options: Record<string, string | boolean | number>,\r\n ): ResolvedCLICommandOption[] {\r\n const missing: ResolvedCLICommandOption[] = [];\r\n\r\n command.commandOptions.forEach((opt) => {\r\n if (opt.required) {\r\n // Check if option is provided by name or alias\r\n const hasOption =\r\n options[opt.name] !== undefined || (opt.alias && options[opt.alias] !== undefined);\r\n if (!hasOption) {\r\n missing.push(opt);\r\n }\r\n }\r\n });\r\n\r\n return missing;\r\n }\r\n\r\n /**\r\n * Apply default values to options\r\n */\r\n protected applyDefaultOptions(\r\n command: CLICommand,\r\n options: Record<string, string | boolean | number>,\r\n ): Record<string, string | boolean | number> {\r\n const result = { ...options };\r\n\r\n command.commandOptions.forEach((opt) => {\r\n if (opt.defaultValue !== undefined) {\r\n const hasOption =\r\n result[opt.name] !== undefined || (opt.alias && result[opt.alias] !== undefined);\r\n if (!hasOption) {\r\n result[opt.name] = opt.defaultValue;\r\n }\r\n }\r\n\r\n // `!== undefined`, not truthiness: `-r=false` now resolves to the boolean\r\n // `false`, and a truthiness test would drop it before it ever reached the\r\n // canonical name — leaving `rollback` undefined instead of explicitly off.\r\n if (\r\n opt.alias !== undefined &&\r\n result[opt.alias] !== undefined &&\r\n result[opt.name] === undefined\r\n ) {\r\n result[opt.name] = result[opt.alias];\r\n }\r\n });\r\n\r\n return result;\r\n }\r\n\r\n /**\r\n * Execute the given command and report how it ended.\r\n *\r\n * Returning the verdict — rather than exiting on success from in here — is\r\n * what makes the awaited result trustworthy: see {@link CommandRunVerdict}.\r\n */\r\n public async execute(command: CLICommand, data: CommandActionData): Promise<CommandRunVerdict> {\r\n const startTime = Date.now();\r\n\r\n // Validate required options\r\n const missingOptions = this.validateOptions(command, data.options);\r\n if (missingOptions.length > 0) {\r\n displayMissingOptions(missingOptions);\r\n process.exit(1);\r\n }\r\n\r\n // Apply default values\r\n data.options = this.applyDefaultOptions(command, data.options);\r\n\r\n // ONE header per `warlock <command>` the user typed — not one per process.\r\n //\r\n // `warlock dev` is TWO processes by design: the invocation the user typed\r\n // becomes a thin supervisor (`dev-server/supervisor.ts`) that re-spawns\r\n // `process.argv` with `WARLOCK_DEV_WORKER=1`, and the child is the actual\r\n // server. Both are `warlock dev`, so both reached this line and the user saw\r\n //\r\n // › Running dev...\r\n // › Running dev...\r\n //\r\n // which reads exactly like the application booting twice. It is not — see\r\n // the block comment on `isRespawnedWorker` — but the line was still wrong,\r\n // and it re-printed on every crash-restart on top of the supervisor's own\r\n // \"restarting\" notice.\r\n //\r\n // The SUPERVISOR keeps the header, not the worker: it prints immediately\r\n // (so a slow boot is not silent), it prints once for the whole session\r\n // (restarts are already announced as restarts), and it is the process the\r\n // user actually started.\r\n if (!isRespawnedWorker()) {\r\n displayExecutingCommand(command.name);\r\n }\r\n\r\n if (command.commandPreAction) {\r\n await command.commandPreAction(data);\r\n }\r\n\r\n // load preloaders\r\n if (command.commandPreload) {\r\n try {\r\n await this.loadPreloaders(command);\r\n } catch (error) {\r\n // A preload failure (a bad import in a config file, a connector that\r\n // throws on boot, a missing module export) happens BEFORE the command's\r\n // action runs — there is no run loop to recover into, so it is ALWAYS\r\n // fatal, persistent command (dev/start) or not. Surface the real cause\r\n // and the offending file, then exit. This MUST stay outside the\r\n // command-execute try/catch below: letting a preload rejection escape\r\n // turned `warlock dev` into a silent hang — the unhandled rejection slid\r\n // past while the already-started loader worker thread kept the process\r\n // alive, freezing just after the banner with no error printed.\r\n displayBootError(command.name, error as Error);\r\n process.exit(1);\r\n }\r\n }\r\n\r\n this.captureFatalAsyncErrors(command);\r\n\r\n try {\r\n await command.execute(data);\r\n\r\n // Persistent commands (dev/start) hand control to their run loop here.\r\n // There is no success banner to print and nothing to exit for.\r\n if (command.isPersistent) return \"running\";\r\n\r\n // Yield one full event-loop turn before declaring success, and AWAIT that\r\n // yield. Node flags an unhandled rejection only after the current turn's\r\n // microtasks drain, so reporting success synchronously here fired FIRST\r\n // and erased the failure — a `build` whose async work rejected exited 0\r\n // having emitted nothing.\r\n //\r\n // The await is the load-bearing part, not the yield. Scheduling this as a\r\n // bare `setImmediate` let `execute()` RESOLVE while the verdict was still\r\n // pending: the callback outlived the call it belonged to, so the green\r\n // check could surface after the caller had already moved on — in a later\r\n // command, or after a rejection reported in the interim, which the guard\r\n // below cannot see because by then it reads the state of a run that has\r\n // already ended. Awaiting binds the banner to THIS invocation: `execute`\r\n // does not resolve until the verdict it is about to report has settled.\r\n //\r\n // `setImmediate` runs in the same loop iteration's check phase, so this\r\n // cannot hang on a lingering handle the way dropping the exit would; it\r\n // only lets `captureFatalAsyncErrors` win the race when there is one.\r\n await new Promise<void>((resolve) => setImmediate(resolve));\r\n\r\n // The yield is what gives a floating rejection the chance to be reported;\r\n // this is what acts on it. A green check printed after that error would\r\n // contradict it, and the exit that follows would reset `process.exitCode`\r\n // to 0 — re-erasing the very failure this whole path exists to surface.\r\n if (this.fatalAsyncErrorReported) return \"failed\";\r\n\r\n displayCommandSuccess(command.name, Date.now() - startTime);\r\n\r\n return \"succeeded\";\r\n } catch (error) {\r\n displayCommandError(command.name, error as Error);\r\n // Persistent commands (dev/start) own their startup-failure exits;\r\n // by the time an error reaches here they've already entered their\r\n // run loop, so it's a runtime error we log but don't crash on —\r\n // HMR or process supervisors can recover. Non-persistent\r\n // one-shot commands always exit on failure.\r\n if (!command.isPersistent) {\r\n process.exit(1);\r\n }\r\n\r\n return \"failed\";\r\n }\r\n }\r\n\r\n /**\r\n * Fail loudly on async errors that escape the command's own promise.\r\n *\r\n * `execute` awaits `command.execute(data)`, but work the command merely\r\n * *started* settles outside that await — `@mongez/events` `trigger` /\r\n * `triggerAll` are synchronous and do not await their listeners, so an async\r\n * listener that throws leaves a floating rejection behind. Node reports it at\r\n * the end of the turn, by which point the CLI had already exited 0: the\r\n * failure was erased and the command looked green.\r\n *\r\n * Persistent commands (dev/start) are exempt from the exit for the same\r\n * reason as the catch block in `execute`: by the time they reach this point\r\n * they own a run loop that is expected to survive runtime errors so HMR or a\r\n * supervisor can recover. They still print — silence is what made this class\r\n * of failure invisible in the first place.\r\n */\r\n protected fatalAsyncErrorReported = false;\r\n\r\n protected captureFatalAsyncErrors(command: CLICommand) {\r\n const onFatalAsyncError = (reason: unknown) => {\r\n // A rejection reason is not necessarily an Error (`Promise.reject(\"x\")`),\r\n // and a crash handler must never itself throw.\r\n const error = reason instanceof Error ? reason : new Error(String(reason));\r\n\r\n // Set before printing, so the deferred success banner is suppressed even\r\n // if displaying the error yields.\r\n this.fatalAsyncErrorReported = true;\r\n\r\n displayCommandError(command.name, error);\r\n\r\n if (!command.isPersistent) {\r\n exitAfterFlush(1);\r\n }\r\n };\r\n\r\n process.on(\"unhandledRejection\", onFatalAsyncError);\r\n process.on(\"uncaughtException\", onFatalAsyncError);\r\n }\r\n\r\n /**\r\n * Load preloaders\r\n */\r\n protected async loadPreloaders(command: CLICommand) {\r\n const preloaders = command.commandPreload || {};\r\n\r\n if (preloaders.runtimeStrategy) {\r\n Application.setRuntimeStrategy(preloaders.runtimeStrategy);\r\n }\r\n\r\n // Override, not a default: a command that declares an environment gets it\r\n // regardless of what the shell exported. Then read it back — `process.env`\r\n // is process-global and anything may have written to it, so a value that\r\n // did not take must fail here, loudly, rather than surface later as a\r\n // production React bundle under `warlock dev`.\r\n if (preloaders.environment) {\r\n Application.setEnvironment(preloaders.environment);\r\n\r\n if (process.env.NODE_ENV !== preloaders.environment) {\r\n throw new Error(\r\n `Command \"${command.name}\" set NODE_ENV to \"${preloaders.environment}\", but it read back as ` +\r\n `\"${process.env.NODE_ENV}\". The environment did not take, and every decision made ` +\r\n `downstream of it — env file selection, bundler dep optimisation — would be wrong.`,\r\n );\r\n }\r\n }\r\n\r\n // Env BEFORE config, unconditionally, for every command.\r\n //\r\n // `warlock.config.ts` is a module whose body runs on import, and projects\r\n // legitimately call `env(\"BUILD_OUT\", \"dist\")` in it. Loading config first\r\n // evaluated that body against an empty env store, so every such call\r\n // silently returned its default — under `build` and `start`, which never\r\n // loaded env at all, and under `dev` too, because config was loaded first\r\n // regardless. The ordering is load-bearing in the other direction as well:\r\n // `loadEnv()` reads `process.env.NODE_ENV` at call time to choose\r\n // `.env.<NODE_ENV>` over `.env`, so it must follow `setEnvironment` above.\r\n await loadEnvironmentFiles();\r\n\r\n await warlockConfigManager.load();\r\n\r\n if (preloaders.config || preloaders.bootstrap || preloaders.prestart) {\r\n await filesOrchestrator.init();\r\n }\r\n\r\n // `preloaders.env` is gone: env is loaded above for every command, so the\r\n // branch that used to serve it could only re-parse the same files and\r\n // re-override `process.env` a second time.\r\n if (preloaders.bootstrap) {\r\n await bootstrap();\r\n\r\n if (await fileExistsAsync(appPath(\"bootstrap.ts\"))) {\r\n await filesOrchestrator.load(\"src/app/bootstrap.ts\");\r\n }\r\n }\r\n\r\n // Load configuration files\r\n if (preloaders.config) {\r\n await loadConfigFiles(preloaders.config);\r\n }\r\n\r\n if (preloaders.prestart) {\r\n if (await fileExistsAsync(appPath(\"prestart.ts\"))) {\r\n await filesOrchestrator.load(\"src/app/prestart.ts\");\r\n }\r\n }\r\n\r\n // Initialize connectors.\r\n // `connectors: true` only starts the early phase here — late-phase\r\n // connectors (http, socket) start after the command's action loads\r\n // app code. Explicit lists bypass phase splitting (caller knows\r\n // exactly which connectors they want).\r\n if (preloaders.connectors) {\r\n // REGISTER before starting — the non-bundled counterpart of section 2.5\r\n // of the generated production entry, and the \"dev preloader\" path\r\n // `register-configured-connectors.ts` documents.\r\n //\r\n // Without this, `warlock.config.ts > connectors` drove the BUILD only:\r\n // `warlock build` read the array to drain each connector's contribution,\r\n // but under `warlock dev` nothing ever registered it, so an app had to\r\n // call `connectorsManager.register(...)` from its own `main.ts` to get a\r\n // connector running. Apps that did BOTH then registered the same\r\n // connector twice in production — once here from the baked config, once\r\n // from their own app code — and it booted twice, which surfaced as\r\n // `Route name \"...\" is already taken` while installing page routes.\r\n //\r\n // The array is omitted deliberately, so the function reads the config\r\n // loaded above; `expectedNames` is omitted too, because drift is a\r\n // build↔runtime relationship and dev has no build to drift from.\r\n //\r\n // The idempotency this function provides is against ITSELF — a second\r\n // call skips names the first registered. It does NOT protect against an\r\n // app calling `connectorsManager.register()` directly, which pushes\r\n // unconditionally, and which runs AFTER this point in both modes. So a\r\n // connector belongs in the config array or in app code, never both:\r\n // listing it in both is the duplicate this comment's route-name error\r\n // describes.\r\n registerConfiguredConnectors();\r\n\r\n if (preloaders.connectors === true) {\r\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Early);\r\n } else {\r\n await connectorsManager.start(preloaders.connectors);\r\n }\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,MAAM,oBAAoB;;;;;;;;;AAU1B,SAAS,eAAe,MAAc;CACpC,QAAQ,WAAW;CAEnB,MAAM,QAAQ,iBAAiB,QAAQ,KAAK,IAAI,GAAG,iBAAiB;CACpE,MAAM,QAAQ;CAEd,IAAI,UAAU;CACd,MAAM,gBAAgB;EACpB,IAAI,EAAE,UAAU,GAAG;EAEnB,aAAa,KAAK;EAClB,QAAQ,KAAK,IAAI;CACnB;CAIA,QAAQ,OAAO,MAAM,IAAI,OAAO;CAChC,QAAQ,OAAO,MAAM,IAAI,OAAO;AAClC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAS,oBAA6B;CACpC,OAAO,YAAY;AACrB;AAEA,IAAa,qBAAb,MAAgC;;kBAIE,CAAC;qCAKa,IAAI,IAAI;kCAKf,IAAI,IAAI;iCA4hBX;;;;;CAvhBpC,AAAO,SAAS,GAAG,UAA8B;EAC/C,KAAK,SAAS,KAAK,GAAG,QAAQ;EAE9B,SAAS,SAAS,YAAY;GAC5B,MAAM,aAAa,QAAQ,KAAK,MAAM,GAAG,CAAC,CAAC;GAE3C,gBAAgB,iBAAiB,QAAQ,MAAM;IAC7C,cAAc,QAAQ;IACtB,QAAQ,QAAQ,iBAAiB;IACjC,aAAa,QAAQ;IACrB,OAAO,QAAQ;IACf,SAAS,QAAQ,eAAe,SAAS,IAAI,QAAQ,iBAAiB;GACxE,CAAC;GAED,KAAK,YAAY,IAAI,YAAY,OAAO;GAGxC,IAAI,QAAQ,cACV,KAAK,SAAS,IAAI,QAAQ,cAAc,UAAU;EAEtD,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAO,WAAW,MAAsC;EAEtD,IAAI,UAAU,KAAK,YAAY,IAAI,IAAI;EACvC,IAAI,SAAS,OAAO;EAGpB,MAAM,WAAW,KAAK,SAAS,IAAI,IAAI;EACvC,IAAI,UACF,OAAO,KAAK,YAAY,IAAI,QAAQ;CAIxC;;;;;CAMA,AAAO,qBAA+B;EACpC,MAAM,QAAkB,CAAC;EAGzB,KAAK,MAAM,QAAQ,KAAK,YAAY,KAAK,GACvC,MAAM,KAAK,IAAI;EAIjB,KAAK,MAAM,SAAS,KAAK,SAAS,KAAK,GACrC,MAAM,KAAK,KAAK;EAIlB,MAAM,mBAAmB,gBAAgB,cAAc,YAAY,CAAC;EACpE,KAAK,MAAM,QAAQ,OAAO,KAAK,gBAAgB,GAAG;GAChD,MAAM,WAAW,KAAK,MAAM,GAAG,CAAC,CAAC;GACjC,IAAI,CAAC,MAAM,SAAS,QAAQ,GAC1B,MAAM,KAAK,QAAQ;GAErB,MAAM,QAAQ,iBAAiB,KAAK,CAAC;GACrC,IAAI,SAAS,CAAC,MAAM,SAAS,KAAK,GAChC,MAAM,KAAK,KAAK;EAEpB;EAEA,OAAO;CACT;;;;CAKA,MAAa,QAAQ;EAInB,MAAM,EAAE,MAAM,YAAY,aAAa,QAAQ,IAAI;EAEnD,IAAI,QAAQ,SAAS;GACnB,gBAAgB,mBAAmB;GAEnC,MAAM,gBAAgB,mBAAmB;EAC3C;EAEA,IAAI,QAAQ,WAAW,QAAQ,GAAG;GAChC,MAAM,gCAAgC;GACtC,QAAQ,KAAK,CAAC;EAChB;EAGA,MAAM,gBAAgB,aAAa;EAGnC,KAAK,SACH,GAAG,kBAAkB,KAAK,YAAY;GACpC,IAAI,CAAC,QAAQ,eACX,QAAQ,gBAAgB;GAE1B,OAAO;EACT,CAAC,CACH;EAEA,MAAM,gBAAgB,QAAQ,QAAQ,QAAQ;EAG9C,IAAI,CAAC,QAAQ,eAAe;GAC1B,MAAM,KAAK,eAAe;GAC1B,QAAQ,KAAK,CAAC;EAChB;EAGA,IAAI,QAAQ,WAAW;GACrB,MAAM,KAAK,UAAU;GACrB,QAAQ,KAAK,CAAC;EAChB;EAEA,IAAI,CAAC,MAAM;GACT,sBAAsB;GACtB,QAAQ,KAAK,CAAC;EAChB;EAEA,MAAM,UAAU,MAAM,KAAK,eAAe,IAAI;EAE9C,IAAI,gBAAgB,YAClB,MAAM,gBAAgB,aAAa;EAGrC,IAAI,CAAC,SAAS;GAKZ,uBAAuB,MAFH,YAAY,MADR,KAAK,mBACuB,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,KAE7B,CAAC;GACxC,QAAQ,KAAK,CAAC;EAChB;EAGA,IAAI,eAAe;GACjB,mBAAmB;IACjB,MAAM,QAAQ;IACd,OAAO,QAAQ;IACf,aAAa,QAAQ;IACrB,SAAS,QAAQ;GACnB,CAAC;GACD,QAAQ,KAAK,CAAC;EAChB;EAKA,MAAM,WAAW,KAAK,mBAAmB,OAAO;EAahD,IAAI,MAXkB,KAAK,QAAQ,SAAS;GAC1C,SAAS,SAAS;GAClB,MAAM,SAAS;EACjB,CAAC,MAQe,aACd,eAAe,CAAC;CAEpB;;;;;;;;;;CAWA,AAAU,mBAAmB,SAAqB,OAAiB,QAAQ,MAAqB;EAC9F,IAAI;GACF,OAAO,aAAa,MAAM,QAAQ,cAAc;EAClD,SAAS,OAAO;GACd,IAAI,iBAAiB,qBAAqB;IACxC,QAAQ,IAAI;IACZ,QAAQ,IAAI,KAAK,OAAO,UAAU,QAAQ,EAAE,GAAG,MAAM,SAAS;IAC9D,QAAQ,IAAI;IACZ,QAAQ,KAAK,CAAC;GAChB;GAEA,MAAM;EACR;CACF;;;;;CAMA,MAAgB,iBAAiB;EAC/B,IACE,gBAAgB,mBAChB,OAAO,KAAK,gBAAgB,cAAc,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,GACnE;GAWA,MAAM,YATkC,OAAO,QAC7C,gBAAgB,cAAc,YAAY,CAAC,CAC7C,CAAC,CAAC,KAAK,CAAC,MAAM,UAAU;IACtB;IACA,OAAO,IAAI;IACX,aAAa,IAAI;IACjB,QAAQ,IAAI;GACd,EAE6B,CAAC;GAC9B;EACF;EAIA,MAAM,KAAK,oBAAoB;EAE/B,MAAM,kBAAkB,MAAM,kBAAkB,QAAQ;EAExD,KAAK,SAAS,GAAG,eAAe;EAEhC,MAAM,gBAAgB,aAAa;EAUnC,MAAM,YAPkC,KAAK,SAAS,KAAK,SAAS;GAClE,MAAM,IAAI;GACV,OAAO,IAAI;GACX,aAAa,IAAI;GACjB,QAAQ,IAAI,iBAAiB;EAC/B,EAE6B,CAAC;CAChC;;;;CAKA,MAAgB,YAAY;EAC1B,QAAQ,IAAI;EACZ,QAAQ,IAAI,KAAK,OAAO,KAAK,KAAK,EAAE,8BAA8B;EAElE,MAAM,kBAAkB,MAAM,kBAAkB,QAAQ;EAExD,KAAK,SAAS,GAAG,eAAe;EAEhC,MAAM,gBAAgB,aAAa;EAEnC,QAAQ,IACN,KAAK,OAAO,MAAM,KAAK,EAAE,UAAU,OAAO,KAAK,OAAO,gBAAgB,MAAM,CAAC,EAAE,kBACjF;EACA,QAAQ,IAAI;CACd;;;;CAKA,MAAgB,sBAAsB;EACpC,MAAM,qBAAqB,KAAK;EAEhC,IAAI,qBAAqB,UACvB,KAAK,SACH,IAAI,qBAAqB,IAAI,KAAK,CAAC,EAAE,YAAY,CAAC,EAAC,CAAE,KAAK,YAAY;GACpE,IAAI,CAAC,QAAQ,eACX,QAAQ,gBAAgB;GAE1B,OAAO;EACT,CAAC,CACH;CAEJ;;;;CAKA,MAAgB,eAAe,MAAc;EAE3C,IAAI,UAAU,KAAK,WAAW,IAAI;EAClC,IAAI,SACF,OAAO;EAIT,MAAM,KAAK,oBAAoB;EAE/B,UAAU,KAAK,WAAW,IAAI;EAE9B,IAAI,SACF,OAAO;EAIT,MAAM,KAAK,oBAAoB,IAAI;EAEnC,UAAU,KAAK,WAAW,IAAI;EAC9B,IAAI,SACF,OAAO;EAGT,OAAO;CACT;;;;CAKA,MAAgB,oBAAoB,MAAc;EAEhD,MAAM,mBAAmB,MAAM,gBAAgB,aAAa;EAE5D,IAAI,kBAEF,KAAK,MAAM,mBAAmB,iBAAiB,UAAU;GACvD,MAAM,UAAU,iBAAiB,SAAS;GAG1C,IAAI,sBAAsB,iBAAiB,IAAI,KAAK,QAAQ,UAAU,MAAM;IAC1E,MAAM,cAAc,QAAQ;IAC5B,IAAI,aAAa;KACf,MAAM,kBAAkB,MAAM,kBAAkB,KAAK,WAAW;KAEhE,IAAI,iBAAiB;MACnB,gBAAgB,cAAc,WAAW;MACzC,KAAK,SAAS,eAAe;MAC7B;KACF;IACF;GACF;EACF;EAGF,MAAM,UAAU,MAAM,kBAAkB,OAAO,IAAI;EAEnD,IAAI,SAAS;GACX,KAAK,SAAS,OAAO;GACrB;EACF;CACF;;;;CAKA,AAAU,gBACR,SACA,SAC4B;EAC5B,MAAM,UAAsC,CAAC;EAE7C,QAAQ,eAAe,SAAS,QAAQ;GACtC,IAAI,IAAI,UAIN;QAAI,EADF,QAAQ,IAAI,UAAU,UAAc,IAAI,SAAS,QAAQ,IAAI,WAAW,SAExE,QAAQ,KAAK,GAAG;GAClB;EAEJ,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAU,oBACR,SACA,SAC2C;EAC3C,MAAM,SAAS,EAAE,GAAG,QAAQ;EAE5B,QAAQ,eAAe,SAAS,QAAQ;GACtC,IAAI,IAAI,iBAAiB,QAGvB;QAAI,EADF,OAAO,IAAI,UAAU,UAAc,IAAI,SAAS,OAAO,IAAI,WAAW,SAEtE,OAAO,IAAI,QAAQ,IAAI;GACzB;GAMF,IACE,IAAI,UAAU,UACd,OAAO,IAAI,WAAW,UACtB,OAAO,IAAI,UAAU,QAErB,OAAO,IAAI,QAAQ,OAAO,IAAI;EAElC,CAAC;EAED,OAAO;CACT;;;;;;;CAQA,MAAa,QAAQ,SAAqB,MAAqD;EAC7F,MAAM,YAAY,KAAK,IAAI;EAG3B,MAAM,iBAAiB,KAAK,gBAAgB,SAAS,KAAK,OAAO;EACjE,IAAI,eAAe,SAAS,GAAG;GAC7B,sBAAsB,cAAc;GACpC,QAAQ,KAAK,CAAC;EAChB;EAGA,KAAK,UAAU,KAAK,oBAAoB,SAAS,KAAK,OAAO;EAqB7D,IAAI,CAAC,kBAAkB,GACrB,wBAAwB,QAAQ,IAAI;EAGtC,IAAI,QAAQ,kBACV,MAAM,QAAQ,iBAAiB,IAAI;EAIrC,IAAI,QAAQ,gBACV,IAAI;GACF,MAAM,KAAK,eAAe,OAAO;EACnC,SAAS,OAAO;GAUd,iBAAiB,QAAQ,MAAM,KAAc;GAC7C,QAAQ,KAAK,CAAC;EAChB;EAGF,KAAK,wBAAwB,OAAO;EAEpC,IAAI;GACF,MAAM,QAAQ,QAAQ,IAAI;GAI1B,IAAI,QAAQ,cAAc,OAAO;GAoBjC,MAAM,IAAI,SAAe,YAAY,aAAa,OAAO,CAAC;GAM1D,IAAI,KAAK,yBAAyB,OAAO;GAEzC,sBAAsB,QAAQ,MAAM,KAAK,IAAI,IAAI,SAAS;GAE1D,OAAO;EACT,SAAS,OAAO;GACd,oBAAoB,QAAQ,MAAM,KAAc;GAMhD,IAAI,CAAC,QAAQ,cACX,QAAQ,KAAK,CAAC;GAGhB,OAAO;EACT;CACF;CAoBA,AAAU,wBAAwB,SAAqB;EACrD,MAAM,qBAAqB,WAAoB;GAG7C,MAAM,QAAQ,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC;GAIzE,KAAK,0BAA0B;GAE/B,oBAAoB,QAAQ,MAAM,KAAK;GAEvC,IAAI,CAAC,QAAQ,cACX,eAAe,CAAC;EAEpB;EAEA,QAAQ,GAAG,sBAAsB,iBAAiB;EAClD,QAAQ,GAAG,qBAAqB,iBAAiB;CACnD;;;;CAKA,MAAgB,eAAe,SAAqB;EAClD,MAAM,aAAa,QAAQ,kBAAkB,CAAC;EAE9C,IAAI,WAAW,iBACb,YAAY,mBAAmB,WAAW,eAAe;EAQ3D,IAAI,WAAW,aAAa;GAC1B,YAAY,eAAe,WAAW,WAAW;GAEjD,IAAI,QAAQ,IAAI,aAAa,WAAW,aACtC,MAAM,IAAI,MACR,YAAY,QAAQ,KAAK,qBAAqB,WAAW,YAAY,0BAC/D,QAAQ,IAAI,SAAS,2IAE7B;EAEJ;EAYA,MAAM,qBAAqB;EAE3B,MAAM,qBAAqB,KAAK;EAEhC,IAAI,WAAW,UAAU,WAAW,aAAa,WAAW,UAC1D,MAAM,kBAAkB,KAAK;EAM/B,IAAI,WAAW,WAAW;GACxB,MAAM,UAAU;GAEhB,IAAI,MAAM,gBAAgB,QAAQ,cAAc,CAAC,GAC/C,MAAM,kBAAkB,KAAK,sBAAsB;EAEvD;EAGA,IAAI,WAAW,QACb,MAAM,gBAAgB,WAAW,MAAM;EAGzC,IAAI,WAAW,UACb;OAAI,MAAM,gBAAgB,QAAQ,aAAa,CAAC,GAC9C,MAAM,kBAAkB,KAAK,qBAAqB;EACpD;EAQF,IAAI,WAAW,YAAY;GAyBzB,6BAA6B;GAE7B,IAAI,WAAW,eAAe,MAC5B,MAAM,kBAAkB,kBAAwC;QAEhE,MAAM,kBAAkB,MAAM,WAAW,UAAU;EAEvD;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-commands.utils.mjs","names":[],"sources":["../../../../../../../core/src/cli/cli-commands.utils.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { Environment } from \"../utils\";\nimport { getWarlockVersion } from \"../utils/framework-vesion\";\n\nexport function isMatchingCommandName(commandName: string, targetingCommandName: string) {\n return commandName.split(\" \")[0] === targetingCommandName;\n}\n\n/**\n * Display the Warlock.js version banner\n */\nexport async function displayWarlockVersionInTerminal() {\n const version = await getWarlockVersion();\n console.log(`⚡ ${colors.bold(\"Warlock.js\")} ${colors.greenBright(`v${version}`)}`);\n}\n\ntype StartBannerOptions = {\n environment: Environment;\n};\n\nfunction getTextColorMethod(environment: Environment) {\n switch (environment) {\n case \"development\":\n return colors.yellowBright;\n case \"production\":\n return colors.greenBright;\n case \"test\":\n return colors.blueBright;\n default:\n return colors.whiteBright;\n }\n}\n\n/**\n * Display CLI startup banner\n */\nexport async function displayStartupBanner({ environment }: StartBannerOptions) {\n const version = await getWarlockVersion();\n const textColorMethod = getTextColorMethod(environment);\n console.log(` ⚡ ${colors.bold(textColorMethod(\"Warlock.js\"))} ${colors.dim(`v${version}`)}`);\n console.log();\n}\n\ntype ProductionReadyBannerOptions = {\n bootDurationMs?: number;\n};\n\n/**\n * Announce that the production server is up — printed on **stdout**, and only\n * after the child process reported a completed boot.\n *\n * Stdout is the channel a supervisor or CI gate greps to decide the service is\n * healthy, so nothing optimistic may be written to it: this function is the\n * single place allowed to say \"started\", and it is called from exactly one\n * place, the `warlock:ready` handler in the `start` command.\n */\nexport async function displayProductionReadyBanner({\n bootDurationMs,\n}: ProductionReadyBannerOptions = {}) {\n const version = await getWarlockVersion();\n const duration = bootDurationMs ? colors.dim(` in ${bootDurationMs}ms`) : \"\";\n\n console.log(\n ` ⚡ ${colors.bold(colors.greenBright(\"Warlock.js\"))} ${colors.dim(`v${version}`)} ${colors.green(\"✔\")} production server started${duration}`,\n );\n console.log();\n}\n\n/**\n * Report that the production server died before it ever served anything.\n *\n * Written to **stderr** only. `warlock start` inherits both child streams, and\n * production launchers commonly merge them (`2>&1`), so mirroring this block to\n * stdout prints every line twice. The command's non-zero exit code is the\n * machine-readable failure signal; stdout remains reserved for the ready\n * banner.\n *\n * `causeWasCaptured` gates the \"the cause is printed above\" line. The\n * supervisor cannot always guarantee the child wrote anything before it\n * died — an import that throws before the logger configures its channels,\n * a bundle with no console channel at all — and claiming a cause is above\n * when nothing was ever captured sends the developer looking for output\n * that doesn't exist, which is worse than admitting the gap.\n */\nexport function displayProductionStartFailure(\n exitCode: number,\n causeWasCaptured: boolean,\n) {\n const causeLine = causeWasCaptured\n ? ` ${colors.dim(\"the cause is printed above, in the application's own output\")}`\n : ` ${colors.dim(\"no output was captured from the application process — its cause did not reach this terminal\")}`;\n\n const lines = [\n \"\",\n ` ${colors.red(\"✖\")} ${colors.bold(\"warlock start\")} failed — the server never finished booting`,\n ` ${colors.dim(`the application process exited with code ${exitCode}`)}`,\n causeLine,\n \"\",\n ];\n\n for (const line of lines) {\n console.error(line);\n }\n}\n\n/**\n * Note that a running child has not reported readiness yet.\n *\n * Emitted on **stderr** only. The process may be perfectly healthy — an older\n * bundle has no readiness signal at all — so saying anything on stdout would be\n * the very false-green this channel exists to prevent.\n *\n * The wording offers the likely causes rather than asserting one. It cannot\n * distinguish an old bundle from a slow boot from a boot that is about to fail,\n * and an earlier version claimed \"the bundle predates readiness reporting\" —\n * which was simply wrong when a current bundle was mid-crash, and sent the\n * reader after the wrong thing.\n */\nexport function displayMissingReadinessNotice(waitedMs: number) {\n console.error();\n console.error(\n ` ${colors.yellow(\"!\")} still running after ${Math.round(waitedMs / 1000)}s with no readiness signal`,\n );\n console.error(` ${colors.dim(\"either the boot is still in progress, or this bundle was built\")}`);\n console.error(\n ` ${colors.dim(\"before readiness reporting — re-run\")} ${colors.cyan(\"warlock build\")} ${colors.dim(\"if the banner never appears\")}`,\n );\n console.error();\n}\n\n/**\n * Display command execution header\n */\nexport function displayExecutingCommand(commandName: string) {\n console.log(` ${colors.cyan(\"›\")} Running ${colors.bold(colors.white(commandName))}...`);\n console.log();\n}\n\n/**\n * Display command not found error with optional suggestions\n */\nexport function displayCommandNotFound(commandName: string, suggestions?: string[]) {\n console.log();\n console.log(` ${colors.red(\"✖\")} Command ${colors.magenta(commandName)} not found`);\n\n if (suggestions && suggestions.length > 0) {\n console.log();\n console.log(` ${colors.yellow(\"Did you mean?\")}`);\n suggestions.forEach((suggestion) => {\n console.log(` ${colors.cyan(\"→\")} ${colors.white(suggestion)}`);\n });\n }\n\n console.log();\n console.log(\n ` ${colors.dim(\"Run\")} ${colors.cyan(\"warlock --help\")} ${colors.dim(\"to see available commands\")}`,\n );\n console.log();\n}\n\n/**\n * Display missing command error\n */\nexport function displayMissingCommand() {\n console.log();\n console.log(` ${colors.red(\"✖\")} No command specified`);\n console.log(\n ` ${colors.dim(\"Run\")} ${colors.cyan(\"warlock --help\")} ${colors.dim(\"to see available commands\")}`,\n );\n console.log();\n}\n\n/**\n * Display command success message\n */\nexport function displayCommandSuccess(commandName: string, durationMs?: number) {\n const duration = durationMs ? colors.dim(` (${durationMs}ms)`) : \"\";\n console.log();\n console.log(\n ` ${colors.green(\"✔\")} ${colors.bold(commandName)} completed successfully${duration}`,\n );\n console.log();\n}\n\n/**\n * Display command error message\n */\nexport function displayCommandError(commandName: string, error: Error) {\n console.log();\n console.log(` ${colors.red(\"✖\")} ${colors.bold(commandName)} failed`);\n console.log(` ${colors.dim(error.message)}`);\n console.log();\n}\n\n/**\n * Display a FATAL boot/preload failure and exit information.\n *\n * Preload failures — a bad import in a config file, a connector that throws\n * on startup, a missing module export — happen BEFORE the command's run loop\n * exists, so there is nothing to recover into: they are always fatal. Unlike\n * `displayCommandError`, this prints the full stack (not just `error.message`)\n * because the stack names the exact file + line of the offending import, which\n * is the single most useful clue when a config file pulls in a broken module.\n *\n * Surfacing this loudly is the difference between a clear error and a silent\n * hang: an unhandled preload rejection used to escape while the already-started\n * loader worker thread kept the process alive, leaving `warlock dev` frozen\n * just after the banner with no message at all.\n *\n * @example\n * // SyntaxError: The requested module '@warlock.js/cascade' does not\n * // provide an export named 'belongsTo'\n * // at src/app/.../permission.model.ts:2\n */\nexport function displayBootError(commandName: string, error: Error) {\n console.log();\n console.log(` ${colors.red(\"✖\")} ${colors.bold(commandName)} failed to start`);\n console.log(` ${colors.red(error.message)}`);\n if (error.stack) {\n console.log();\n console.log(colors.dim(error.stack));\n }\n console.log();\n}\n\n/**\n * Display missing required options error\n */\nexport function displayMissingOptions(options: { name: string; text: string }[]) {\n console.log();\n console.log(` ${colors.red(\"✖\")} Missing required options:`);\n options.forEach((opt) => {\n console.log(` ${colors.yellow(opt.text)} ${colors.dim(`(--${opt.name})`)}`);\n });\n console.log();\n}\n\n/**\n * Command info for help display\n */\nexport type HelpCommandInfo = {\n name: string;\n alias?: string;\n description?: string;\n source: \"framework\" | \"plugin\" | \"project\";\n};\n\n/**\n * Display global help with all commands grouped by source\n */\nexport async function displayHelp(commands: HelpCommandInfo[]) {\n const version = await getWarlockVersion();\n\n console.log();\n console.log(\n ` ⚡ ${colors.bold(colors.yellowBright(\"Warlock.js\"))} CLI ${colors.dim(`v${version}`)}`,\n );\n console.log();\n console.log(\n ` ${colors.bold(\"Usage:\")} ${colors.cyan(\"warlock\")} ${colors.dim(\"<command>\")} ${colors.dim(\"[options]\")}`,\n );\n console.log();\n\n // Group by source\n const grouped: Record<string, HelpCommandInfo[]> = {\n framework: [],\n plugin: [],\n project: [],\n };\n\n commands.forEach((cmd) => {\n grouped[cmd.source]?.push(cmd);\n });\n\n // Display each group\n const groupLabels: Record<string, string> = {\n framework: \"Framework Commands\",\n plugin: \"Plugin Commands\",\n project: \"Project Commands\",\n };\n\n for (const [source, cmds] of Object.entries(grouped)) {\n if (cmds.length === 0) continue;\n\n console.log(` ${colors.bold(colors.white(groupLabels[source]))}`);\n console.log();\n\n // Find max name length for alignment\n const maxLen = Math.max(...cmds.map((c) => c.name.length + (c.alias ? c.alias.length + 4 : 0)));\n\n cmds.forEach((cmd) => {\n const aliasStr = cmd.alias ? colors.dim(` (${cmd.alias})`) : \"\";\n const nameWithAlias = cmd.name + (cmd.alias ? ` (${cmd.alias})` : \"\");\n const padding = \" \".repeat(maxLen - nameWithAlias.length + 2);\n // const desc = cmd.description || colors.dim(\"No description\");\n const desc = cmd.description || \"\";\n console.log(` ${colors.cyan(cmd.name)}${aliasStr}${padding}${desc}`);\n });\n console.log();\n }\n\n // Display global flags\n console.log(` ${colors.bold(colors.white(\"Global Flags\"))}`);\n console.log();\n\n const globalFlags = [\n { flag: \"--help, -h\", description: \"Show help for a command\" },\n { flag: \"--version, -v\", description: \"Show Warlock version\" },\n { flag: \"--no-cache\", description: \"Force reload without cache\" },\n { flag: \"--warm-cache\", description: \"Pre-cache all project commands\" },\n ];\n\n const maxFlagLen = Math.max(...globalFlags.map((f) => f.flag.length));\n\n globalFlags.forEach(({ flag, description }) => {\n const padding = \" \".repeat(maxFlagLen - flag.length + 2);\n console.log(` ${colors.yellow(flag)}${padding}${description}`);\n });\n console.log();\n\n console.log(\n ` ${colors.dim(\"Run\")} ${colors.cyan(\"warlock <command> --help\")} ${colors.dim(\"for command-specific help\")}`,\n );\n console.log();\n}\n\n/**\n * Display help for a specific command\n */\nexport function displayCommandHelp(command: {\n name: string;\n alias?: string;\n description?: string;\n options?: { name: string; text: string; description?: string; required?: boolean }[];\n}) {\n console.log();\n console.log(\n ` ${colors.bold(colors.cyan(command.name))}${command.alias ? colors.dim(` (${command.alias})`) : \"\"}`,\n );\n\n if (command.description) {\n console.log(` ${command.description}`);\n }\n console.log();\n\n if (command.options && command.options.length > 0) {\n console.log(` ${colors.bold(\"Options:\")}`);\n console.log();\n\n const maxLen = Math.max(...command.options.map((o) => o.text.length));\n\n command.options.forEach((opt) => {\n const padding = \" \".repeat(maxLen - opt.text.length + 2);\n const required = opt.required ? colors.red(\" (required)\") : \"\";\n const desc = opt.description || \"\";\n console.log(` ${colors.green(opt.text)}${padding}${desc}${required}`);\n });\n console.log();\n } else {\n console.log(` ${colors.dim(\"No options available\")}`);\n console.log();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli-commands.utils.mjs","names":[],"sources":["../../../../../../../core/src/cli/cli-commands.utils.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { Environment } from \"../utils\";\nimport { getWarlockVersion } from \"../utils/framework-vesion\";\n\nexport function isMatchingCommandName(commandName: string, targetingCommandName: string) {\n return commandName.split(\" \")[0] === targetingCommandName;\n}\n\n/**\n * Display the Warlock.js version banner\n */\nexport async function displayWarlockVersionInTerminal() {\n const version = await getWarlockVersion();\n console.log(`⚡ ${colors.bold(\"Warlock.js\")} ${colors.greenBright(`v${version}`)}`);\n}\n\ntype StartBannerOptions = {\n environment: Environment;\n};\n\nfunction getTextColorMethod(environment: Environment) {\n switch (environment) {\n case \"development\":\n return colors.yellowBright;\n case \"production\":\n return colors.greenBright;\n case \"test\":\n return colors.blueBright;\n default:\n return colors.whiteBright;\n }\n}\n\n/**\n * Display CLI startup banner\n */\nexport async function displayStartupBanner({ environment }: StartBannerOptions) {\n const version = await getWarlockVersion();\n const textColorMethod = getTextColorMethod(environment);\n console.log(` ⚡ ${colors.bold(textColorMethod(\"Warlock.js\"))} ${colors.dim(`v${version}`)}`);\n console.log();\n}\n\ntype ProductionReadyBannerOptions = {\n bootDurationMs?: number;\n};\n\n/**\n * Announce that the production server is up — printed on **stdout**, and only\n * after the child process reported a completed boot.\n *\n * Stdout is the channel a supervisor or CI gate greps to decide the service is\n * healthy, so nothing optimistic may be written to it: this function is the\n * single place allowed to say \"started\", and it is called from exactly one\n * place, the `warlock:ready` handler in the `start` command.\n */\nexport async function displayProductionReadyBanner({\n bootDurationMs,\n}: ProductionReadyBannerOptions = {}) {\n const version = await getWarlockVersion();\n const duration = bootDurationMs ? colors.dim(` in ${bootDurationMs}ms`) : \"\";\n\n console.log(\n ` ⚡ ${colors.bold(colors.greenBright(\"Warlock.js\"))} ${colors.dim(`v${version}`)} ${colors.green(\"✔\")} production server started${duration}`,\n );\n console.log();\n}\n\n/**\n * Report that the production server died before it ever served anything.\n *\n * Written to **stderr** only. `warlock start` inherits both child streams, and\n * production launchers commonly merge them (`2>&1`), so mirroring this block to\n * stdout prints every line twice. The command's non-zero exit code is the\n * machine-readable failure signal; stdout remains reserved for the ready\n * banner.\n *\n * `causeWasCaptured` gates the \"the cause is printed above\" line. The\n * supervisor cannot always guarantee the child wrote anything before it\n * died — an import that throws before the logger configures its channels,\n * a bundle with no console channel at all — and claiming a cause is above\n * when nothing was ever captured sends the developer looking for output\n * that doesn't exist, which is worse than admitting the gap.\n */\nexport function displayProductionStartFailure(\n exitCode: number,\n causeWasCaptured: boolean,\n) {\n const causeLine = causeWasCaptured\n ? ` ${colors.dim(\"the cause is printed above, in the application's own output\")}`\n : ` ${colors.dim(\"no output was captured from the application process — its cause did not reach this terminal\")}`;\n\n const lines = [\n \"\",\n ` ${colors.red(\"✖\")} ${colors.bold(\"warlock start\")} failed — the server never finished booting`,\n ` ${colors.dim(`the application process exited with code ${exitCode}`)}`,\n causeLine,\n \"\",\n ];\n\n for (const line of lines) {\n console.error(line);\n }\n}\n\n/**\n * Note that a running child has not reported readiness yet.\n *\n * Emitted on **stderr** only. The process may be perfectly healthy — an older\n * bundle has no readiness signal at all — so saying anything on stdout would be\n * the very false-green this channel exists to prevent.\n *\n * The wording offers the likely causes rather than asserting one. It cannot\n * distinguish an old bundle from a slow boot from a boot that is about to fail,\n * and an earlier version claimed \"the bundle predates readiness reporting\" —\n * which was simply wrong when a current bundle was mid-crash, and sent the\n * reader after the wrong thing.\n */\nexport function displayMissingReadinessNotice(waitedMs: number) {\n console.error();\n console.error(\n ` ${colors.yellow(\"!\")} still running after ${Math.round(waitedMs / 1000)}s with no readiness signal`,\n );\n console.error(` ${colors.dim(\"either the boot is still in progress, or this bundle was built\")}`);\n console.error(\n ` ${colors.dim(\"before readiness reporting — re-run\")} ${colors.cyan(\"warlock build\")} ${colors.dim(\"if the banner never appears\")}`,\n );\n console.error();\n}\n\n/**\n * Display command execution header\n */\nexport function displayExecutingCommand(commandName: string) {\n console.log(` ${colors.cyan(\"›\")} Running ${colors.bold(colors.white(commandName))}...`);\n console.log();\n}\n\n/**\n * Display command not found error with optional suggestions\n */\nexport function displayCommandNotFound(commandName: string, suggestions?: string[]) {\n console.log();\n console.log(` ${colors.red(\"✖\")} Command ${colors.magenta(commandName)} not found`);\n\n if (suggestions && suggestions.length > 0) {\n console.log();\n console.log(` ${colors.yellow(\"Did you mean?\")}`);\n suggestions.forEach((suggestion) => {\n console.log(` ${colors.cyan(\"→\")} ${colors.white(suggestion)}`);\n });\n }\n\n console.log();\n console.log(\n ` ${colors.dim(\"Run\")} ${colors.cyan(\"warlock --help\")} ${colors.dim(\"to see available commands\")}`,\n );\n console.log();\n}\n\n/**\n * Display missing command error\n */\nexport function displayMissingCommand() {\n console.log();\n console.log(` ${colors.red(\"✖\")} No command specified`);\n console.log(\n ` ${colors.dim(\"Run\")} ${colors.cyan(\"warlock --help\")} ${colors.dim(\"to see available commands\")}`,\n );\n console.log();\n}\n\n/**\n * Display command success message\n */\nexport function displayCommandSuccess(commandName: string, durationMs?: number) {\n const duration = durationMs ? colors.dim(` (${durationMs}ms)`) : \"\";\n console.log();\n console.log(\n ` ${colors.green(\"✔\")} ${colors.bold(commandName)} completed successfully${duration}`,\n );\n console.log();\n}\n\n/**\n * Display command error message\n */\nexport function displayCommandError(commandName: string, error: Error) {\n console.log();\n console.log(` ${colors.red(\"✖\")} ${colors.bold(commandName)} failed`);\n console.log(` ${colors.dim(error.message)}`);\n console.log();\n}\n\n/**\n * Display a FATAL boot/preload failure and exit information.\n *\n * Preload failures — a bad import in a config file, a connector that throws\n * on startup, a missing module export — happen BEFORE the command's run loop\n * exists, so there is nothing to recover into: they are always fatal. Unlike\n * `displayCommandError`, this prints the full stack (not just `error.message`)\n * because the stack names the exact file + line of the offending import, which\n * is the single most useful clue when a config file pulls in a broken module.\n *\n * Surfacing this loudly is the difference between a clear error and a silent\n * hang: an unhandled preload rejection used to escape while the already-started\n * loader worker thread kept the process alive, leaving `warlock dev` frozen\n * just after the banner with no message at all.\n *\n * @example\n * // SyntaxError: The requested module '@warlock.js/cascade' does not\n * // provide an export named 'belongsTo'\n * // at src/app/.../permission.model.ts:2\n */\nexport function displayBootError(commandName: string, error: Error) {\n console.log();\n console.log(` ${colors.red(\"✖\")} ${colors.bold(commandName)} failed to start`);\n console.log(` ${colors.red(error.message)}`);\n if (error.stack) {\n console.log();\n console.log(colors.dim(error.stack));\n }\n console.log();\n}\n\n/**\n * Display missing required options error\n */\nexport function displayMissingOptions(options: { name: string; text: string }[]) {\n console.log();\n console.log(` ${colors.red(\"✖\")} Missing required options:`);\n options.forEach((opt) => {\n console.log(` ${colors.yellow(opt.text)} ${colors.dim(`(--${opt.name})`)}`);\n });\n console.log();\n}\n\n/**\n * Command info for help display\n */\nexport type HelpCommandInfo = {\n name: string;\n alias?: string;\n description?: string;\n source: \"framework\" | \"plugin\" | \"project\";\n};\n\n/**\n * Display global help with all commands grouped by source\n */\nexport async function displayHelp(commands: HelpCommandInfo[]) {\n const version = await getWarlockVersion();\n\n console.log();\n console.log(\n ` ⚡ ${colors.bold(colors.yellowBright(\"Warlock.js\"))} CLI ${colors.dim(`v${version}`)}`,\n );\n console.log();\n console.log(\n ` ${colors.bold(\"Usage:\")} ${colors.cyan(\"warlock\")} ${colors.dim(\"<command>\")} ${colors.dim(\"[options]\")}`,\n );\n console.log();\n\n // Group by source\n const grouped: Record<string, HelpCommandInfo[]> = {\n framework: [],\n plugin: [],\n project: [],\n };\n\n commands.forEach((cmd) => {\n grouped[cmd.source]?.push(cmd);\n });\n\n // Display each group\n const groupLabels: Record<string, string> = {\n framework: \"Framework Commands\",\n plugin: \"Plugin Commands\",\n project: \"Project Commands\",\n };\n\n for (const [source, cmds] of Object.entries(grouped)) {\n if (cmds.length === 0) continue;\n\n console.log(` ${colors.bold(colors.white(groupLabels[source]))}`);\n console.log();\n\n // Find max name length for alignment\n const maxLen = Math.max(...cmds.map((c) => c.name.length + (c.alias ? c.alias.length + 4 : 0)));\n\n cmds.forEach((cmd) => {\n const aliasStr = cmd.alias ? colors.dim(` (${cmd.alias})`) : \"\";\n const nameWithAlias = cmd.name + (cmd.alias ? ` (${cmd.alias})` : \"\");\n const padding = \" \".repeat(maxLen - nameWithAlias.length + 2);\n // const desc = cmd.description || colors.dim(\"No description\");\n const desc = cmd.description || \"\";\n console.log(` ${colors.cyan(cmd.name)}${aliasStr}${padding}${desc}`);\n });\n console.log();\n }\n\n // Display global flags\n console.log(` ${colors.bold(colors.white(\"Global Flags\"))}`);\n console.log();\n\n const globalFlags = [\n { flag: \"--help, -h\", description: \"Show help for a command\" },\n { flag: \"--version, -v\", description: \"Show Warlock version\" },\n { flag: \"--no-cache\", description: \"Force reload without cache\" },\n { flag: \"--warm-cache\", description: \"Pre-cache all project commands\" },\n ];\n\n const maxFlagLen = Math.max(...globalFlags.map((f) => f.flag.length));\n\n globalFlags.forEach(({ flag, description }) => {\n const padding = \" \".repeat(maxFlagLen - flag.length + 2);\n console.log(` ${colors.yellow(flag)}${padding}${description}`);\n });\n console.log();\n\n console.log(\n ` ${colors.dim(\"Run\")} ${colors.cyan(\"warlock <command> --help\")} ${colors.dim(\"for command-specific help\")}`,\n );\n console.log();\n}\n\n/**\n * Display help for a specific command\n */\nexport function displayCommandHelp(command: {\n name: string;\n alias?: string;\n description?: string;\n options?: { name: string; text: string; description?: string; required?: boolean }[];\n}) {\n console.log();\n console.log(\n ` ${colors.bold(colors.cyan(command.name))}${command.alias ? colors.dim(` (${command.alias})`) : \"\"}`,\n );\n\n if (command.description) {\n console.log(` ${command.description}`);\n }\n console.log();\n\n if (command.options && command.options.length > 0) {\n console.log(` ${colors.bold(\"Options:\")}`);\n console.log();\n\n const maxLen = Math.max(...command.options.map((o) => o.text.length));\n\n command.options.forEach((opt) => {\n const padding = \" \".repeat(maxLen - opt.text.length + 2);\n const required = opt.required ? colors.red(\" (required)\") : \"\";\n const desc = opt.description || \"\";\n console.log(` ${colors.green(opt.text)}${padding}${desc}${required}`);\n });\n console.log();\n } else {\n console.log(` ${colors.dim(\"No options available\")}`);\n console.log();\n }\n}\n"],"mappings":"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"}
|