@warlock.js/core 5.24.0 → 5.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -1
- package/bin/warlock.js +0 -0
- package/esm/application/application.d.mts +21 -0
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs +29 -1
- package/esm/application/application.mjs.map +1 -1
- package/esm/application/boot-signal.d.mts +2 -1
- package/esm/application/boot-signal.d.mts.map +1 -1
- package/esm/application/boot-signal.mjs.map +1 -1
- package/esm/application/index.d.mts +1 -0
- package/esm/application/index.mjs +1 -0
- package/esm/application/roles.d.mts +72 -0
- package/esm/application/roles.d.mts.map +1 -0
- package/esm/application/roles.mjs +124 -0
- package/esm/application/roles.mjs.map +1 -0
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/create-database.command.mjs.map +1 -1
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/index.mjs +2 -0
- package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +170 -0
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/doctor-command.action.mjs +7 -2
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
- package/esm/cli/commands/doctor.command.mjs +5 -0
- package/esm/cli/commands/doctor.command.mjs.map +1 -1
- package/esm/cli/commands/drop-tables.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs +1 -12
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/path-resolver.mjs.map +1 -1
- package/esm/cli/commands/migrate.command.mjs.map +1 -1
- package/esm/cli/commands/routes.command.mjs.map +1 -1
- package/esm/cli/commands/seed.command.mjs.map +1 -1
- package/esm/cli/commands/start-production.command.mjs +54 -5
- package/esm/cli/commands/start-production.command.mjs.map +1 -1
- package/esm/cli/commands/storage-put.action.mjs.map +1 -1
- package/esm/cli/commands/storage-put.command.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs +20 -1
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/commands/update.command.mjs.map +1 -1
- package/esm/cli/commands-loader.mjs.map +1 -1
- package/esm/config/config-loader.d.mts.map +1 -1
- package/esm/config/config-loader.mjs +3 -1
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/database-connector.mjs.map +1 -1
- package/esm/connectors/herald-connector.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +7 -0
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.mjs.map +1 -1
- package/esm/connectors/mail-connector.mjs.map +1 -1
- package/esm/connectors/types.d.mts +8 -0
- package/esm/connectors/types.d.mts.map +1 -1
- package/esm/connectors/types.mjs.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 +4 -3
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/models/database-log/database-log.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs +2 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/dev-server/dependency-graph.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +2 -0
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/layer-executor.mjs.map +1 -1
- package/esm/dev-server/legacy-locales-warning.mjs +16 -0
- package/esm/dev-server/legacy-locales-warning.mjs.map +1 -0
- package/esm/dev-server/manifest-manager.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs +26 -5
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/register-installed-devtools.mjs +47 -0
- package/esm/dev-server/register-installed-devtools.mjs.map +1 -0
- package/esm/dev-server/run-connector-typings.mjs +28 -0
- package/esm/dev-server/run-connector-typings.mjs.map +1 -0
- package/esm/dev-server/special-file-patterns.mjs +5 -3
- package/esm/dev-server/special-file-patterns.mjs.map +1 -1
- package/esm/dev-server/special-files-collector.d.mts +2 -2
- package/esm/dev-server/special-files-collector.d.mts.map +1 -1
- package/esm/dev-server/special-files-collector.mjs +7 -4
- package/esm/dev-server/special-files-collector.mjs.map +1 -1
- package/esm/dev-server/translation-keys-sources.mjs +7 -3
- package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +27 -4
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/encryption/password.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs +3 -1
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/features/access.feature.mjs +38 -18
- package/esm/generations/features/access.feature.mjs.map +1 -1
- package/esm/generations/features/ai-anthropic.feature.mjs.map +1 -1
- package/esm/generations/features/ai-bedrock.feature.mjs.map +1 -1
- package/esm/generations/features/ai-google.feature.mjs.map +1 -1
- package/esm/generations/features/ai-ollama.feature.mjs.map +1 -1
- package/esm/generations/features/ai-openai.feature.mjs.map +1 -1
- package/esm/generations/features/ai-panoptic.feature.mjs.map +1 -1
- package/esm/generations/features/ai-tools.feature.mjs.map +1 -1
- package/esm/generations/features/ai-workspace.feature.mjs.map +1 -1
- package/esm/generations/features/ai.feature.mjs.map +1 -1
- package/esm/generations/features/bull-board.feature.mjs +2 -2
- package/esm/generations/features/bull-board.feature.mjs.map +1 -1
- package/esm/generations/features/devtools.feature.mjs +23 -0
- package/esm/generations/features/devtools.feature.mjs.map +1 -0
- package/esm/generations/features/herald.feature.mjs.map +1 -1
- package/esm/generations/features/image.feature.mjs.map +1 -1
- package/esm/generations/features/index.mjs +2 -0
- package/esm/generations/features/index.mjs.map +1 -1
- package/esm/generations/features/mail.feature.mjs.map +1 -1
- package/esm/generations/features/mongodb.feature.mjs.map +1 -1
- package/esm/generations/features/notifications.feature.mjs.map +1 -1
- package/esm/generations/features/postgres.feature.mjs.map +1 -1
- package/esm/generations/features/react-email.feature.mjs.map +1 -1
- package/esm/generations/features/react.feature.mjs.map +1 -1
- package/esm/generations/features/redis.feature.mjs.map +1 -1
- package/esm/generations/features/s3.feature.mjs.map +1 -1
- package/esm/generations/features/scheduler.feature.mjs.map +1 -1
- package/esm/generations/features/ses.feature.mjs.map +1 -1
- package/esm/generations/features/socket.feature.mjs.map +1 -1
- package/esm/generations/features/test.feature.mjs +6 -2
- package/esm/generations/features/test.feature.mjs.map +1 -1
- package/esm/generations/features/types.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/http/build-rate-limit-options.d.mts +8 -2
- package/esm/http/build-rate-limit-options.d.mts.map +1 -1
- package/esm/http/build-rate-limit-options.mjs +1 -0
- package/esm/http/build-rate-limit-options.mjs.map +1 -1
- package/esm/http/database/RequestLog.mjs.map +1 -1
- package/esm/http/errors/errors.d.mts +1 -17
- package/esm/http/errors/errors.d.mts.map +1 -1
- package/esm/http/errors/errors.mjs +1 -20
- package/esm/http/errors/errors.mjs.map +1 -1
- package/esm/http/index.d.mts +4 -3
- package/esm/http/index.mjs +3 -2
- package/esm/http/middleware/inject-request-context.d.mts +1 -1
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +9 -1
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.d.mts.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.mjs +5 -1
- package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
- package/esm/http/request.d.mts +7 -24
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +1 -22
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts +5 -5
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +4 -2
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server-rebuilt.d.mts +20 -0
- package/esm/http/server-rebuilt.d.mts.map +1 -0
- package/esm/http/server-rebuilt.mjs +21 -0
- package/esm/http/server-rebuilt.mjs.map +1 -0
- package/esm/http/tracing/index.mjs +1 -1
- package/esm/http/tracing/tracing-dispatcher.d.mts +13 -6
- package/esm/http/tracing/tracing-dispatcher.d.mts.map +1 -1
- package/esm/http/tracing/tracing-dispatcher.mjs +46 -14
- package/esm/http/tracing/tracing-dispatcher.mjs.map +1 -1
- package/esm/http/tracing/tracing.type.d.mts +2 -1
- package/esm/http/tracing/tracing.type.d.mts.map +1 -1
- package/esm/http/types.d.mts +9 -0
- package/esm/http/types.d.mts.map +1 -1
- package/esm/image/image.mjs.map +1 -1
- package/esm/index.d.mts +8 -4
- package/esm/index.mjs +8 -3
- package/esm/loader/app-module-importer.d.mts +17 -0
- package/esm/loader/app-module-importer.d.mts.map +1 -0
- package/esm/loader/app-module-importer.mjs +20 -0
- package/esm/loader/app-module-importer.mjs.map +1 -0
- package/esm/localization/index.mjs +4 -0
- package/esm/localization/parse-locale-dictionary.d.mts +23 -0
- package/esm/localization/parse-locale-dictionary.d.mts.map +1 -0
- package/esm/localization/parse-locale-dictionary.mjs +215 -0
- package/esm/localization/parse-locale-dictionary.mjs.map +1 -0
- package/esm/localization/register-locale-dictionary.d.mts +12 -0
- package/esm/localization/register-locale-dictionary.d.mts.map +1 -0
- package/esm/localization/register-locale-dictionary.mjs +58 -0
- package/esm/localization/register-locale-dictionary.mjs.map +1 -0
- package/esm/logger/logger.mjs.map +1 -1
- package/esm/mail/events.d.mts +7 -0
- package/esm/mail/events.d.mts.map +1 -1
- package/esm/mail/events.mjs +7 -0
- package/esm/mail/events.mjs.map +1 -1
- package/esm/mail/send-mail.d.mts.map +1 -1
- package/esm/mail/send-mail.mjs +23 -2
- package/esm/mail/send-mail.mjs.map +1 -1
- package/esm/mail/types.d.mts +5 -1
- package/esm/mail/types.d.mts.map +1 -1
- package/esm/mail/types.mjs.map +1 -1
- package/esm/manifest/manifest-manager.mjs.map +1 -1
- package/esm/production/build-contributions.mjs +19 -3
- package/esm/production/build-contributions.mjs.map +1 -1
- package/esm/production/production-builder.mjs +39 -10
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs +6 -0
- package/esm/production/production-supervisor.mjs.map +1 -1
- package/esm/production/resolve-build-config.d.mts +1 -1
- package/esm/production/resolve-build-config.mjs +15 -0
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/production/route-registration-child.mjs +14 -2
- package/esm/production/route-registration-child.mjs.map +1 -1
- package/esm/production/route-registration-snapshot.mjs +9 -2
- package/esm/production/route-registration-snapshot.mjs.map +1 -1
- package/esm/resource/resource-field-builder.mjs +1 -1
- package/esm/resource/resource-field-builder.mjs.map +1 -1
- package/esm/resource/resource.d.mts +5 -0
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs +17 -6
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +1 -0
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +10 -1
- package/esm/router/types.d.mts.map +1 -1
- package/esm/tests/index.d.mts +3 -2
- package/esm/tests/index.mjs +3 -2
- package/esm/tests/start-http-development-server.d.mts +10 -0
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +16 -2
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/vitest-setup.d.mts +16 -1
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs +20 -2
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/use-cases/use-case-events.mjs.map +1 -1
- package/esm/utils/is-test-file.mjs +15 -0
- package/esm/utils/is-test-file.mjs.map +1 -0
- package/esm/utils/normalized-path.d.mts +3 -1
- package/esm/utils/normalized-path.d.mts.map +1 -1
- package/esm/utils/normalized-path.mjs +4 -2
- package/esm/utils/normalized-path.mjs.map +1 -1
- package/esm/utils/paths.mjs.map +1 -1
- package/esm/validation/plugins/database-plugin.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +57 -4
- package/esm/validation/validateAll.mjs.map +1 -1
- package/esm/warlock-config/default-configurations.mjs +1 -0
- package/esm/warlock-config/default-configurations.mjs.map +1 -1
- package/esm/warlock-config/types.d.mts +7 -0
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/llms-full.txt +382 -214
- package/llms.txt +1 -1
- package/package.json +23 -19
- package/skills/README.md +1 -1
- package/skills/configure-app/SKILL.md +2 -0
- package/skills/create-controller/SKILL.md +4 -0
- package/skills/define-resource/SKILL.md +4 -0
- package/skills/request-tracing/SKILL.md +38 -15
- package/skills/run-app/SKILL.md +71 -34
- package/skills/send-mail/SKILL.md +55 -25
- package/skills/test-http/SKILL.md +28 -9
- package/skills/test-service/SKILL.md +61 -40
- package/skills/use-localization/SKILL.md +39 -26
- package/skills/warlock-conventions/SKILL.md +26 -18
- package/skills/write-cli-command/SKILL.md +50 -45
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boot-for-diagnostics.mjs","names":[],"sources":["../../../../../../../../../core/src/cli/commands/doctor/boot-for-diagnostics.ts"],"sourcesContent":["import { config } from \"../../../config/config-getter\";\r\nimport { connectorsManager } from \"../../../connectors/connectors-manager\";\r\nimport { registerConfiguredConnectors } from \"../../../connectors/register-configured-connectors\";\r\nimport { ConnectorLifecyclePhase } from \"../../../connectors/types\";\r\nimport { container } from \"../../../container\";\r\nimport { filesOrchestrator } from \"../../../dev-server/files-orchestrator\";\r\nimport { ModuleLoadError } from \"../../../dev-server/module-loader\";\r\nimport { router } from \"../../../router/router\";\r\nimport type { DoctorBootContext, ModuleFailure } from \"./check.types\";\r\n\r\n/**\r\n * @fileoverview The boot pass `warlock doctor` runs before its checks.\r\n *\r\n * THE RULE: **doctor boots, it never starts.**\r\n *\r\n * The connector contract already splits the two things doctor needs to keep\r\n * apart. `boot()` is where a connector REGISTERS — the HTTP connector builds a\r\n * Fastify instance and adds `/health` + `/ready` to it, the web connector\r\n * installs one route per page on the app router — and `start()` is where it\r\n * CONNECTS: opens the database, dials the cache, binds the port. Doctor runs\r\n * every `boot()` and no `start()`, so it sees the exact surface a served app\r\n * would have while opening no connection and binding no port.\r\n *\r\n * That seam is what makes the checks true. Before it existed here, doctor\r\n * preloaded config and framework bootstrap only: no app module was ever\r\n * imported, so no route module ever ran, so `routes` reported `0` on an app\r\n * `warlock dev` served 14 routes for — while `health` reported the endpoints as\r\n * \"registered\" from a config flag alone. Two checks, one app, contradictory\r\n * answers, and the wrong one was the one users acted on.\r\n *\r\n * ORDERING mirrors `DevelopmentServer.start()` exactly, because the route\r\n * surface is order-dependent: early connectors boot, then models (decorator\r\n * registries the route modules resolve against), then locales/events/mains/\r\n * routes, then late connectors — which is where `web` turns pages into routes.\r\n * Get the order wrong and the count is wrong, which is the defect this replaces.\r\n *\r\n * NOT read-only in one narrow sense: it imports the app's own modules, so\r\n * whatever a module does at import time happens. That is unavoidable — routes\r\n * only exist as the side effect of importing the file that declares them — and\r\n * it is the same import the dev server performs. It also refreshes\r\n * `.warlock/manifest.json`, the framework's own file cache. Nothing in the\r\n * app's data, and no network, is touched.\r\n */\r\n\r\n/**\r\n * The one connector whose `boot()` can bind a port. See {@link shouldSkipBoot}.\r\n */\r\nconst SOCKET_CONNECTOR = \"socket\";\r\n\r\n/**\r\n * Normalize anything thrown into a one-line message.\r\n */\r\nfunction messageOf(error: unknown): string {\r\n return error instanceof Error ? error.message : String(error);\r\n}\r\n\r\n/**\r\n * Whether doctor must refuse to boot this connector, and why.\r\n *\r\n * Exactly one case, and it is not a taste call: `SocketConnector.boot()` shares\r\n * the HTTP connector's raw server when there is one, and CREATES its own —\r\n * `server.listen(socketConfig.port)` — when there is not. The HTTP connector\r\n * boots first (lower priority number), so the shared path is what normally\r\n * happens; an app that configures `socket` without `http` is the exception, and\r\n * for that app booting socket would make a diagnostic command open a listening\r\n * port. Detected by asking the container what HTTP actually left behind rather\r\n * than by re-deriving the condition from config.\r\n */\r\nfunction shouldSkipBoot(name: string): string | undefined {\r\n if (name === SOCKET_CONNECTOR && !container.has(\"http.server\")) {\r\n return \"no http connector to share a server with — booting it would bind a port\";\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\n/**\r\n * Boot every registered connector in one lifecycle phase, isolating failures.\r\n *\r\n * A connector that throws on boot is recorded and the loop continues: one bad\r\n * connector must not hide the rest of the report, and \"which connector failed\r\n * to boot\" is itself a finding worth printing.\r\n */\r\nasync function bootPhase(\r\n phase: ConnectorLifecyclePhase,\r\n connectors: DoctorBootContext[\"connectors\"],\r\n): Promise<void> {\r\n for (const connector of connectorsManager.list()) {\r\n if (connector.lifecyclePhase !== phase) continue;\r\n\r\n const skipReason = shouldSkipBoot(connector.name);\r\n\r\n if (skipReason) {\r\n connectors.skipped.push({ name: connector.name, reason: skipReason });\r\n continue;\r\n }\r\n\r\n try {\r\n await connector.boot();\r\n connectors.booted.push(connector.name);\r\n } catch (error) {\r\n connectors.failures.push({ name: connector.name, message: messageOf(error) });\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Import every app module the dev server imports at boot, collecting failures\r\n * instead of letting the first one abort the pass.\r\n *\r\n * `loadAll()` is deliberately reused rather than reimplemented: it owns the\r\n * canonical order (locale → event → main → route) and the `withSourceFile`\r\n * scoping that gives each route its origin file. It aggregates its failures and\r\n * throws them at the end, which is exactly the shape needed here — every module\r\n * is attempted, and one broken route file cannot mask another.\r\n */\r\nasync function loadAppModules(): Promise<ModuleFailure[]> {\r\n try {\r\n await filesOrchestrator.moduleLoader.loadAll();\r\n return [];\r\n } catch (error) {\r\n if (error instanceof AggregateError) {\r\n return (error.errors as unknown[]).map((failure) => {\r\n return failure instanceof ModuleLoadError\r\n ? { file: failure.file.relativePath, message: messageOf(failure.cause) }\r\n : { file: \"unknown\", message: messageOf(failure) };\r\n });\r\n }\r\n\r\n return [{ file: \"unknown\", message: messageOf(error) }];\r\n }\r\n}\r\n\r\n/**\r\n * Eagerly import model files so the decorator-driven registries the route\r\n * modules resolve names against are populated first — the same reason\r\n * `DevelopmentServer.autoDiscoverFiles()` exists, and it must keep the same\r\n * position in the sequence.\r\n */\r\nasync function discoverModels(): Promise<ModuleFailure[]> {\r\n const failures: ModuleFailure[] = [];\r\n\r\n for (const file of filesOrchestrator.files.values()) {\r\n if (file.type !== \"model\") continue;\r\n\r\n try {\r\n await filesOrchestrator.moduleLoader.loadModule(file, \"model\");\r\n } catch (error) {\r\n failures.push({\r\n file: file.relativePath,\r\n message: error instanceof ModuleLoadError ? messageOf(error.cause) : messageOf(error),\r\n });\r\n }\r\n }\r\n\r\n return failures;\r\n}\r\n\r\n/**\r\n * Run the boot pass and report what it found.\r\n *\r\n * Never throws. Every failure mode — a connector array the config manager\r\n * refuses, a connector that throws on boot, a route module that fails to\r\n * import — becomes a field on the returned context, which the checks turn into\r\n * a printed verdict. A diagnostic command that crashes while diagnosing tells\r\n * the user nothing.\r\n */\r\nexport async function bootForDiagnostics(): Promise<DoctorBootContext> {\r\n const connectors: DoctorBootContext[\"connectors\"] = {\r\n registered: [],\r\n configured: [],\r\n booted: [],\r\n skipped: [],\r\n failures: [],\r\n };\r\n\r\n const context: DoctorBootContext = {\r\n booted: false,\r\n routeModules: 0,\r\n appRoutes: 0,\r\n totalRoutes: 0,\r\n moduleFailures: [],\r\n connectors,\r\n };\r\n\r\n try {\r\n // `init()` is idempotent and the preloader already ran it; calling it again\r\n // costs nothing and keeps this function honest on its own.\r\n await filesOrchestrator.init();\r\n await filesOrchestrator.initializeAll();\r\n\r\n filesOrchestrator.specialFilesCollector.collect(filesOrchestrator.getFiles());\r\n\r\n context.routeModules = filesOrchestrator.specialFilesCollector.getFilesByType(\"route\").length;\r\n } catch (error) {\r\n context.moduleFailures.push({ file: \"src/\", message: messageOf(error) });\r\n\r\n return context;\r\n }\r\n\r\n // The app's own connectors (`warlock.config.ts > connectors`) must be\r\n // registered before any phase boots, exactly as the dev preloader does it —\r\n // otherwise doctor would report on the eleven built-ins alone and miss the\r\n // connector the app actually configured. This is also the call that refuses a\r\n // duplicate or reserved connector name, so a config that would crash the next\r\n // real boot is caught here instead.\r\n try {\r\n registerConfiguredConnectors();\r\n } catch (error) {\r\n connectors.registrationError = messageOf(error);\r\n }\r\n\r\n connectors.registered = connectorsManager.list().map((connector) => connector.name);\r\n connectors.configured = connectors.registered.filter((name) => {\r\n return !connectorsManager.isBuiltInName(name);\r\n });\r\n\r\n await bootPhase(ConnectorLifecyclePhase.Early, connectors);\r\n\r\n context.moduleFailures.push(...(await discoverModels()));\r\n context.moduleFailures.push(...(await loadAppModules()));\r\n\r\n // Snapshot BEFORE the late phase: everything the app's own route modules\r\n // registered. The difference between this and the total is what connectors\r\n // contributed (pages, for `web`), and reporting the two separately is what\r\n // lets a user reconcile doctor's number with the dev server's.\r\n context.appRoutes = router.routeCount();\r\n\r\n await bootPhase(ConnectorLifecyclePhase.Late, connectors);\r\n\r\n context.totalRoutes = router.routeCount();\r\n\r\n context.booted = true;\r\n\r\n return context;\r\n}\r\n\r\n/**\r\n * Whether the app declares an HTTP surface at all. Checks that only make sense\r\n * for a served app use this to opt out instead of inventing a verdict.\r\n */\r\nexport function hasHttpConfig(): boolean {\r\n return Boolean(config.get(\"http\"));\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"boot-for-diagnostics.mjs","names":[],"sources":["../../../../../../../../../core/src/cli/commands/doctor/boot-for-diagnostics.ts"],"sourcesContent":["import { config } from \"../../../config/config-getter\";\nimport { connectorsManager } from \"../../../connectors/connectors-manager\";\nimport { registerConfiguredConnectors } from \"../../../connectors/register-configured-connectors\";\nimport { ConnectorLifecyclePhase } from \"../../../connectors/types\";\nimport { container } from \"../../../container\";\nimport { filesOrchestrator } from \"../../../dev-server/files-orchestrator\";\nimport { ModuleLoadError } from \"../../../dev-server/module-loader\";\nimport { router } from \"../../../router/router\";\nimport type { DoctorBootContext, ModuleFailure } from \"./check.types\";\n\n/**\n * @fileoverview The boot pass `warlock doctor` runs before its checks.\n *\n * THE RULE: **doctor boots, it never starts.**\n *\n * The connector contract already splits the two things doctor needs to keep\n * apart. `boot()` is where a connector REGISTERS — the HTTP connector builds a\n * Fastify instance and adds `/health` + `/ready` to it, the web connector\n * installs one route per page on the app router — and `start()` is where it\n * CONNECTS: opens the database, dials the cache, binds the port. Doctor runs\n * every `boot()` and no `start()`, so it sees the exact surface a served app\n * would have while opening no connection and binding no port.\n *\n * That seam is what makes the checks true. Before it existed here, doctor\n * preloaded config and framework bootstrap only: no app module was ever\n * imported, so no route module ever ran, so `routes` reported `0` on an app\n * `warlock dev` served 14 routes for — while `health` reported the endpoints as\n * \"registered\" from a config flag alone. Two checks, one app, contradictory\n * answers, and the wrong one was the one users acted on.\n *\n * ORDERING mirrors `DevelopmentServer.start()` exactly, because the route\n * surface is order-dependent: early connectors boot, then models (decorator\n * registries the route modules resolve against), then locales/events/mains/\n * routes, then late connectors — which is where `web` turns pages into routes.\n * Get the order wrong and the count is wrong, which is the defect this replaces.\n *\n * NOT read-only in one narrow sense: it imports the app's own modules, so\n * whatever a module does at import time happens. That is unavoidable — routes\n * only exist as the side effect of importing the file that declares them — and\n * it is the same import the dev server performs. It also refreshes\n * `.warlock/manifest.json`, the framework's own file cache. Nothing in the\n * app's data, and no network, is touched.\n */\n\n/**\n * The one connector whose `boot()` can bind a port. See {@link shouldSkipBoot}.\n */\nconst SOCKET_CONNECTOR = \"socket\";\n\n/**\n * Normalize anything thrown into a one-line message.\n */\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Whether doctor must refuse to boot this connector, and why.\n *\n * Exactly one case, and it is not a taste call: `SocketConnector.boot()` shares\n * the HTTP connector's raw server when there is one, and CREATES its own —\n * `server.listen(socketConfig.port)` — when there is not. The HTTP connector\n * boots first (lower priority number), so the shared path is what normally\n * happens; an app that configures `socket` without `http` is the exception, and\n * for that app booting socket would make a diagnostic command open a listening\n * port. Detected by asking the container what HTTP actually left behind rather\n * than by re-deriving the condition from config.\n */\nfunction shouldSkipBoot(name: string): string | undefined {\n if (name === SOCKET_CONNECTOR && !container.has(\"http.server\")) {\n return \"no http connector to share a server with — booting it would bind a port\";\n }\n\n return undefined;\n}\n\n/**\n * Boot every registered connector in one lifecycle phase, isolating failures.\n *\n * A connector that throws on boot is recorded and the loop continues: one bad\n * connector must not hide the rest of the report, and \"which connector failed\n * to boot\" is itself a finding worth printing.\n */\nasync function bootPhase(\n phase: ConnectorLifecyclePhase,\n connectors: DoctorBootContext[\"connectors\"],\n): Promise<void> {\n for (const connector of connectorsManager.list()) {\n if (connector.lifecyclePhase !== phase) continue;\n\n const skipReason = shouldSkipBoot(connector.name);\n\n if (skipReason) {\n connectors.skipped.push({ name: connector.name, reason: skipReason });\n continue;\n }\n\n try {\n await connector.boot();\n connectors.booted.push(connector.name);\n } catch (error) {\n connectors.failures.push({ name: connector.name, message: messageOf(error) });\n }\n }\n}\n\n/**\n * Import every app module the dev server imports at boot, collecting failures\n * instead of letting the first one abort the pass.\n *\n * `loadAll()` is deliberately reused rather than reimplemented: it owns the\n * canonical order (locale → event → main → route) and the `withSourceFile`\n * scoping that gives each route its origin file. It aggregates its failures and\n * throws them at the end, which is exactly the shape needed here — every module\n * is attempted, and one broken route file cannot mask another.\n */\nasync function loadAppModules(): Promise<ModuleFailure[]> {\n try {\n await filesOrchestrator.moduleLoader.loadAll();\n return [];\n } catch (error) {\n if (error instanceof AggregateError) {\n return (error.errors as unknown[]).map((failure) => {\n return failure instanceof ModuleLoadError\n ? { file: failure.file.relativePath, message: messageOf(failure.cause) }\n : { file: \"unknown\", message: messageOf(failure) };\n });\n }\n\n return [{ file: \"unknown\", message: messageOf(error) }];\n }\n}\n\n/**\n * Eagerly import model files so the decorator-driven registries the route\n * modules resolve names against are populated first — the same reason\n * `DevelopmentServer.autoDiscoverFiles()` exists, and it must keep the same\n * position in the sequence.\n */\nasync function discoverModels(): Promise<ModuleFailure[]> {\n const failures: ModuleFailure[] = [];\n\n for (const file of filesOrchestrator.files.values()) {\n if (file.type !== \"model\") continue;\n\n try {\n await filesOrchestrator.moduleLoader.loadModule(file, \"model\");\n } catch (error) {\n failures.push({\n file: file.relativePath,\n message: error instanceof ModuleLoadError ? messageOf(error.cause) : messageOf(error),\n });\n }\n }\n\n return failures;\n}\n\n/**\n * Run the boot pass and report what it found.\n *\n * Never throws. Every failure mode — a connector array the config manager\n * refuses, a connector that throws on boot, a route module that fails to\n * import — becomes a field on the returned context, which the checks turn into\n * a printed verdict. A diagnostic command that crashes while diagnosing tells\n * the user nothing.\n */\nexport async function bootForDiagnostics(): Promise<DoctorBootContext> {\n const connectors: DoctorBootContext[\"connectors\"] = {\n registered: [],\n configured: [],\n booted: [],\n skipped: [],\n failures: [],\n };\n\n const context: DoctorBootContext = {\n booted: false,\n routeModules: 0,\n appRoutes: 0,\n totalRoutes: 0,\n moduleFailures: [],\n connectors,\n };\n\n try {\n // `init()` is idempotent and the preloader already ran it; calling it again\n // costs nothing and keeps this function honest on its own.\n await filesOrchestrator.init();\n await filesOrchestrator.initializeAll();\n\n filesOrchestrator.specialFilesCollector.collect(filesOrchestrator.getFiles());\n\n context.routeModules = filesOrchestrator.specialFilesCollector.getFilesByType(\"route\").length;\n } catch (error) {\n context.moduleFailures.push({ file: \"src/\", message: messageOf(error) });\n\n return context;\n }\n\n // The app's own connectors (`warlock.config.ts > connectors`) must be\n // registered before any phase boots, exactly as the dev preloader does it —\n // otherwise doctor would report on the eleven built-ins alone and miss the\n // connector the app actually configured. This is also the call that refuses a\n // duplicate or reserved connector name, so a config that would crash the next\n // real boot is caught here instead.\n try {\n registerConfiguredConnectors();\n } catch (error) {\n connectors.registrationError = messageOf(error);\n }\n\n connectors.registered = connectorsManager.list().map((connector) => connector.name);\n connectors.configured = connectors.registered.filter((name) => {\n return !connectorsManager.isBuiltInName(name);\n });\n\n await bootPhase(ConnectorLifecyclePhase.Early, connectors);\n\n context.moduleFailures.push(...(await discoverModels()));\n context.moduleFailures.push(...(await loadAppModules()));\n\n // Snapshot BEFORE the late phase: everything the app's own route modules\n // registered. The difference between this and the total is what connectors\n // contributed (pages, for `web`), and reporting the two separately is what\n // lets a user reconcile doctor's number with the dev server's.\n context.appRoutes = router.routeCount();\n\n await bootPhase(ConnectorLifecyclePhase.Late, connectors);\n\n context.totalRoutes = router.routeCount();\n\n context.booted = true;\n\n return context;\n}\n\n/**\n * Whether the app declares an HTTP surface at all. Checks that only make sense\n * for a served app use this to opt out instead of inventing a verdict.\n */\nexport function hasHttpConfig(): boolean {\n return Boolean(config.get(\"http\"));\n}\n"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { connectorsCheck } from "./connectors.check.mjs";
|
|
|
3
3
|
import { handlerSignatureCheck } from "./handler-signature.check.mjs";
|
|
4
4
|
import { healthCheck } from "./health.check.mjs";
|
|
5
5
|
import { jwtSecretCheck } from "./jwt-secret.check.mjs";
|
|
6
|
+
import { localesTsDeprecatedCheck } from "./locales-ts-deprecated.check.mjs";
|
|
6
7
|
import { optionalPeersCheck } from "./optional-peers.check.mjs";
|
|
7
8
|
import { releaseHygieneCheck } from "./release-hygiene.check.mjs";
|
|
8
9
|
import { routesCheck } from "./routes.check.mjs";
|
|
@@ -26,6 +27,7 @@ const defaultDoctorChecks = [
|
|
|
26
27
|
optionalPeersCheck,
|
|
27
28
|
jwtSecretCheck,
|
|
28
29
|
healthCheck,
|
|
30
|
+
localesTsDeprecatedCheck,
|
|
29
31
|
releaseHygieneCheck
|
|
30
32
|
];
|
|
31
33
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/index.ts"],"sourcesContent":["import type { DoctorCheck } from \"../check.types\";\nimport { configCheck } from \"./config.check\";\nimport { connectorsCheck } from \"./connectors.check\";\nimport { handlerSignatureCheck } from \"./handler-signature.check\";\nimport { healthCheck } from \"./health.check\";\nimport { jwtSecretCheck } from \"./jwt-secret.check\";\nimport { optionalPeersCheck } from \"./optional-peers.check\";\nimport { releaseHygieneCheck } from \"./release-hygiene.check\";\nimport { routesCheck } from \"./routes.check\";\n\n/**\n * The default, ordered set of checks `warlock doctor` runs. Ordering controls\n * the report layout: runtime-surface checks first (routes, handler signatures,\n * config, connectors, peers, health), release hygiene last.\n *\n * Every check is handed the same boot context, and any of them may return\n * `undefined` to opt out of a project it does not apply to — so this list is\n * the set of checks that COULD run, not the set of lines a given project will\n * print.\n */\nexport const defaultDoctorChecks: DoctorCheck[] = [\n routesCheck,\n handlerSignatureCheck,\n configCheck,\n connectorsCheck,\n optionalPeersCheck,\n jwtSecretCheck,\n healthCheck,\n releaseHygieneCheck,\n];\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/index.ts"],"sourcesContent":["import type { DoctorCheck } from \"../check.types\";\nimport { configCheck } from \"./config.check\";\nimport { connectorsCheck } from \"./connectors.check\";\nimport { handlerSignatureCheck } from \"./handler-signature.check\";\nimport { healthCheck } from \"./health.check\";\nimport { jwtSecretCheck } from \"./jwt-secret.check\";\nimport { localesTsDeprecatedCheck } from \"./locales-ts-deprecated.check\";\nimport { optionalPeersCheck } from \"./optional-peers.check\";\nimport { releaseHygieneCheck } from \"./release-hygiene.check\";\nimport { routesCheck } from \"./routes.check\";\n\n/**\n * The default, ordered set of checks `warlock doctor` runs. Ordering controls\n * the report layout: runtime-surface checks first (routes, handler signatures,\n * config, connectors, peers, health), release hygiene last.\n *\n * Every check is handed the same boot context, and any of them may return\n * `undefined` to opt out of a project it does not apply to — so this list is\n * the set of checks that COULD run, not the set of lines a given project will\n * print.\n */\nexport const defaultDoctorChecks: DoctorCheck[] = [\n routesCheck,\n handlerSignatureCheck,\n configCheck,\n connectorsCheck,\n optionalPeersCheck,\n jwtSecretCheck,\n healthCheck,\n localesTsDeprecatedCheck,\n releaseHygieneCheck,\n];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,sBAAqC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { config } from "../../../../config/config-getter.mjs";
|
|
2
|
+
import { appPath, rootPath } from "../../../../utils/paths.mjs";
|
|
3
|
+
import { parseLocaleDictionary } from "../../../../localization/parse-locale-dictionary.mjs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
|
|
8
|
+
//#region ../core/src/cli/commands/doctor/checks/locales-ts-deprecated.check.ts
|
|
9
|
+
function localeSources() {
|
|
10
|
+
const appDirectory = appPath();
|
|
11
|
+
if (!existsSync(appDirectory)) return [];
|
|
12
|
+
return readdirSync(appDirectory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => ({
|
|
13
|
+
file: path.join(appDirectory, entry.name, "utils", "locales.ts"),
|
|
14
|
+
module: entry.name
|
|
15
|
+
})).filter(({ file }) => existsSync(file));
|
|
16
|
+
}
|
|
17
|
+
function propertyName(property) {
|
|
18
|
+
if (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) return property.name.text;
|
|
19
|
+
}
|
|
20
|
+
function literalObject(node) {
|
|
21
|
+
if (!ts.isObjectLiteralExpression(node)) return void 0;
|
|
22
|
+
const value = Object.create(null);
|
|
23
|
+
for (const member of node.properties) {
|
|
24
|
+
if (!ts.isPropertyAssignment(member)) return void 0;
|
|
25
|
+
const key = propertyName(member);
|
|
26
|
+
if (key === void 0) return void 0;
|
|
27
|
+
if (ts.isStringLiteral(member.initializer) || ts.isNumericLiteral(member.initializer)) {
|
|
28
|
+
value[key] = ts.isNumericLiteral(member.initializer) ? Number(member.initializer.text) : member.initializer.text;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const nested = literalObject(member.initializer);
|
|
32
|
+
if (nested === void 0) return void 0;
|
|
33
|
+
value[key] = nested;
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function flattenLiteral(value, group, entries) {
|
|
38
|
+
const values = Object.values(value);
|
|
39
|
+
if (values.every((item) => typeof item === "string" || typeof item === "number")) {
|
|
40
|
+
if (group === "") return false;
|
|
41
|
+
if (!values.every((item) => typeof item === "string")) return false;
|
|
42
|
+
entries[group] = value;
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (!values.every((item) => item !== null && typeof item === "object" && !Array.isArray(item))) return false;
|
|
46
|
+
return Object.entries(value).every(([key, nested]) => flattenLiteral(nested, group === "" ? key : `${group}.${key}`, entries));
|
|
47
|
+
}
|
|
48
|
+
function inspect(source) {
|
|
49
|
+
if (existsSync(path.join(path.dirname(source.file), "locales.json"))) return {
|
|
50
|
+
...source,
|
|
51
|
+
convertible: false,
|
|
52
|
+
reason: "locales.json already exists"
|
|
53
|
+
};
|
|
54
|
+
const text = readFileSync(source.file, "utf8");
|
|
55
|
+
const program = ts.createSourceFile(source.file, text, ts.ScriptTarget.Latest, true);
|
|
56
|
+
const syntaxDiagnostics = ts.transpileModule(text, {
|
|
57
|
+
fileName: source.file,
|
|
58
|
+
reportDiagnostics: true
|
|
59
|
+
}).diagnostics;
|
|
60
|
+
if (syntaxDiagnostics !== void 0 && syntaxDiagnostics.length > 0) return {
|
|
61
|
+
...source,
|
|
62
|
+
convertible: false,
|
|
63
|
+
reason: "TypeScript syntax is invalid"
|
|
64
|
+
};
|
|
65
|
+
const [importStatement, callStatement] = program.statements;
|
|
66
|
+
if (!importStatement || !ts.isImportDeclaration(importStatement) || !ts.isStringLiteral(importStatement.moduleSpecifier) || importStatement.moduleSpecifier.text !== "@warlock.js/core" || !importStatement.importClause || importStatement.importClause.isTypeOnly || importStatement.importClause.name || !importStatement.importClause.namedBindings || !ts.isNamedImports(importStatement.importClause.namedBindings) || importStatement.importClause.namedBindings.elements.length !== 1 || importStatement.importClause.namedBindings.elements[0]?.name.text !== "groupedTranslations" || importStatement.importClause.namedBindings.elements[0]?.propertyName) return {
|
|
67
|
+
...source,
|
|
68
|
+
convertible: false,
|
|
69
|
+
reason: "must import only groupedTranslations from @warlock.js/core"
|
|
70
|
+
};
|
|
71
|
+
if (program.statements.length !== 2) return {
|
|
72
|
+
...source,
|
|
73
|
+
convertible: false,
|
|
74
|
+
reason: "must contain only the groupedTranslations import and one call"
|
|
75
|
+
};
|
|
76
|
+
if (!callStatement || !ts.isExpressionStatement(callStatement) || !ts.isCallExpression(callStatement.expression)) return {
|
|
77
|
+
...source,
|
|
78
|
+
convertible: false,
|
|
79
|
+
reason: "must contain one top-level groupedTranslations call"
|
|
80
|
+
};
|
|
81
|
+
const call = callStatement.expression;
|
|
82
|
+
if (!ts.isIdentifier(call.expression) || call.expression.text !== "groupedTranslations" || call.arguments.length !== 2 || !ts.isStringLiteral(call.arguments[0])) return {
|
|
83
|
+
...source,
|
|
84
|
+
convertible: false,
|
|
85
|
+
reason: "must call groupedTranslations with a string group and object"
|
|
86
|
+
};
|
|
87
|
+
const dictionary = literalObject(call.arguments[1]);
|
|
88
|
+
if (!dictionary) return {
|
|
89
|
+
...source,
|
|
90
|
+
convertible: false,
|
|
91
|
+
reason: "translation object must contain only literal keys, objects, strings, or numbers"
|
|
92
|
+
};
|
|
93
|
+
const group = call.arguments[0].text;
|
|
94
|
+
const entries = Object.create(null);
|
|
95
|
+
if (!flattenLiteral(dictionary, group, entries)) return {
|
|
96
|
+
...source,
|
|
97
|
+
convertible: false,
|
|
98
|
+
reason: "translation object has no valid literal locale entries"
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
...source,
|
|
102
|
+
convertible: true,
|
|
103
|
+
reason: "pure groupedTranslations literal",
|
|
104
|
+
group,
|
|
105
|
+
dictionary,
|
|
106
|
+
entries
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function inspectLocaleSources() {
|
|
110
|
+
return localeSources().map(inspect);
|
|
111
|
+
}
|
|
112
|
+
function localeCodes() {
|
|
113
|
+
const configured = config.key("app.localeCodes");
|
|
114
|
+
return Array.isArray(configured) && configured.every((code) => typeof code === "string") ? configured : void 0;
|
|
115
|
+
}
|
|
116
|
+
function sameEntries(left, right) {
|
|
117
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
118
|
+
}
|
|
119
|
+
/** Convert safe legacy locale sources, retaining a TS file whenever verification cannot prove equivalence. */
|
|
120
|
+
function fixLocaleSources() {
|
|
121
|
+
return inspectLocaleSources().map((result) => {
|
|
122
|
+
if (!result.convertible || !result.group || !result.dictionary || !result.entries) return result;
|
|
123
|
+
const jsonFile = path.join(path.dirname(result.file), "locales.json");
|
|
124
|
+
const document = result.group === result.module ? result.dictionary : {
|
|
125
|
+
$group: result.group,
|
|
126
|
+
...result.dictionary
|
|
127
|
+
};
|
|
128
|
+
const json = `${JSON.stringify(document, null, 2)}\n`;
|
|
129
|
+
writeFileSync(jsonFile, json);
|
|
130
|
+
try {
|
|
131
|
+
const parsed = parseLocaleDictionary({
|
|
132
|
+
sourceFile: rootPath(path.relative(process.cwd(), jsonFile)),
|
|
133
|
+
source: json,
|
|
134
|
+
defaultNamespace: result.module,
|
|
135
|
+
localeCodes: localeCodes()
|
|
136
|
+
});
|
|
137
|
+
if (!sameEntries(result.entries, parsed.entries)) throw new Error("flattened entries differ");
|
|
138
|
+
} catch (error) {
|
|
139
|
+
unlinkSync(jsonFile);
|
|
140
|
+
return {
|
|
141
|
+
...result,
|
|
142
|
+
convertible: false,
|
|
143
|
+
reason: `verification failed: ${error instanceof Error ? error.message : String(error)}`
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
unlinkSync(result.file);
|
|
147
|
+
return {
|
|
148
|
+
...result,
|
|
149
|
+
reason: "converted (comments in the TypeScript source are not retained)"
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const localesTsDeprecatedCheck = {
|
|
154
|
+
name: "locales-ts-deprecated",
|
|
155
|
+
run: () => {
|
|
156
|
+
const sources = inspectLocaleSources();
|
|
157
|
+
if (sources.length === 0) return void 0;
|
|
158
|
+
return {
|
|
159
|
+
name: "locales-ts-deprecated",
|
|
160
|
+
status: "warn",
|
|
161
|
+
detail: `${sources.map((source) => {
|
|
162
|
+
return `${path.relative(process.cwd(), source.file).replaceAll("\\", "/")}: ${source.convertible ? "auto-convertible" : "manual"} (${source.reason})`;
|
|
163
|
+
}).join("; ")}. Auto-loaded locales.ts goes away in v6; run \`warlock doctor --fix\` to convert auto-convertible files.`
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
//#endregion
|
|
169
|
+
export { fixLocaleSources, localesTsDeprecatedCheck };
|
|
170
|
+
//# sourceMappingURL=locales-ts-deprecated.check.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locales-ts-deprecated.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/locales-ts-deprecated.check.ts"],"sourcesContent":["import { existsSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport ts from \"typescript\";\nimport { config } from \"../../../../config/config-getter\";\nimport {\n parseLocaleDictionary,\n type LocaleDictionaryEntries,\n} from \"../../../../localization/parse-locale-dictionary\";\nimport { appPath, rootPath } from \"../../../../utils/paths\";\nimport type { DoctorCheck } from \"../check.types\";\n\ntype LocaleSource = {\n file: string;\n module: string;\n};\n\nexport type LocaleInspection = LocaleSource & {\n convertible: boolean;\n reason: string;\n group?: string;\n dictionary?: Record<string, unknown>;\n entries?: LocaleDictionaryEntries;\n};\n\nfunction localeSources(): LocaleSource[] {\n const appDirectory = appPath();\n if (!existsSync(appDirectory)) return [];\n\n return readdirSync(appDirectory, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => ({\n file: path.join(appDirectory, entry.name, \"utils\", \"locales.ts\"),\n module: entry.name,\n }))\n .filter(({ file }) => existsSync(file));\n}\n\nfunction propertyName(property: ts.PropertyAssignment): string | undefined {\n if (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name))\n return property.name.text;\n return undefined;\n}\n\nfunction literalObject(node: ts.Expression): Record<string, unknown> | undefined {\n if (!ts.isObjectLiteralExpression(node)) return undefined;\n const value: Record<string, unknown> = Object.create(null);\n for (const member of node.properties) {\n if (!ts.isPropertyAssignment(member)) return undefined;\n const key = propertyName(member);\n if (key === undefined) return undefined;\n if (ts.isStringLiteral(member.initializer) || ts.isNumericLiteral(member.initializer)) {\n value[key] = ts.isNumericLiteral(member.initializer)\n ? Number(member.initializer.text)\n : member.initializer.text;\n continue;\n }\n const nested = literalObject(member.initializer);\n if (nested === undefined) return undefined;\n value[key] = nested;\n }\n return value;\n}\n\nfunction flattenLiteral(\n value: Record<string, unknown>,\n group: string,\n entries: LocaleDictionaryEntries,\n): boolean {\n const values = Object.values(value);\n if (values.every((item) => typeof item === \"string\" || typeof item === \"number\")) {\n if (group === \"\") return false;\n if (!values.every((item) => typeof item === \"string\")) return false;\n entries[group] = value as Record<string, string>;\n return true;\n }\n if (!values.every((item) => item !== null && typeof item === \"object\" && !Array.isArray(item)))\n return false;\n return Object.entries(value).every(([key, nested]) =>\n flattenLiteral(\n nested as Record<string, unknown>,\n group === \"\" ? key : `${group}.${key}`,\n entries,\n ),\n );\n}\n\nfunction inspect(source: LocaleSource): LocaleInspection {\n const jsonFile = path.join(path.dirname(source.file), \"locales.json\");\n if (existsSync(jsonFile))\n return { ...source, convertible: false, reason: \"locales.json already exists\" };\n\n const text = readFileSync(source.file, \"utf8\");\n const program = ts.createSourceFile(source.file, text, ts.ScriptTarget.Latest, true);\n const syntaxDiagnostics = ts.transpileModule(text, {\n fileName: source.file,\n reportDiagnostics: true,\n }).diagnostics;\n if (syntaxDiagnostics !== undefined && syntaxDiagnostics.length > 0)\n return { ...source, convertible: false, reason: \"TypeScript syntax is invalid\" };\n const [importStatement, callStatement] = program.statements;\n if (\n !importStatement ||\n !ts.isImportDeclaration(importStatement) ||\n !ts.isStringLiteral(importStatement.moduleSpecifier) ||\n importStatement.moduleSpecifier.text !== \"@warlock.js/core\" ||\n !importStatement.importClause ||\n importStatement.importClause.isTypeOnly ||\n importStatement.importClause.name ||\n !importStatement.importClause.namedBindings ||\n !ts.isNamedImports(importStatement.importClause.namedBindings) ||\n importStatement.importClause.namedBindings.elements.length !== 1 ||\n importStatement.importClause.namedBindings.elements[0]?.name.text !== \"groupedTranslations\" ||\n importStatement.importClause.namedBindings.elements[0]?.propertyName\n )\n return {\n ...source,\n convertible: false,\n reason: \"must import only groupedTranslations from @warlock.js/core\",\n };\n\n if (program.statements.length !== 2)\n return {\n ...source,\n convertible: false,\n reason: \"must contain only the groupedTranslations import and one call\",\n };\n\n if (\n !callStatement ||\n !ts.isExpressionStatement(callStatement) ||\n !ts.isCallExpression(callStatement.expression)\n )\n return {\n ...source,\n convertible: false,\n reason: \"must contain one top-level groupedTranslations call\",\n };\n const call = callStatement.expression;\n if (\n !ts.isIdentifier(call.expression) ||\n call.expression.text !== \"groupedTranslations\" ||\n call.arguments.length !== 2 ||\n !ts.isStringLiteral(call.arguments[0]!)\n )\n return {\n ...source,\n convertible: false,\n reason: \"must call groupedTranslations with a string group and object\",\n };\n const dictionary = literalObject(call.arguments[1]!);\n if (!dictionary)\n return {\n ...source,\n convertible: false,\n reason: \"translation object must contain only literal keys, objects, strings, or numbers\",\n };\n const group = call.arguments[0].text;\n const entries: LocaleDictionaryEntries = Object.create(null);\n if (!flattenLiteral(dictionary, group, entries))\n return {\n ...source,\n convertible: false,\n reason: \"translation object has no valid literal locale entries\",\n };\n return {\n ...source,\n convertible: true,\n reason: \"pure groupedTranslations literal\",\n group,\n dictionary,\n entries,\n };\n}\n\nexport function inspectLocaleSources(): LocaleInspection[] {\n return localeSources().map(inspect);\n}\n\nfunction localeCodes(): readonly string[] | undefined {\n const configured = config.key<unknown>(\"app.localeCodes\");\n return Array.isArray(configured) && configured.every((code) => typeof code === \"string\")\n ? configured\n : undefined;\n}\n\nfunction sameEntries(left: LocaleDictionaryEntries, right: LocaleDictionaryEntries): boolean {\n return JSON.stringify(left) === JSON.stringify(right);\n}\n\n/** Convert safe legacy locale sources, retaining a TS file whenever verification cannot prove equivalence. */\nexport function fixLocaleSources(): LocaleInspection[] {\n return inspectLocaleSources().map((result) => {\n if (!result.convertible || !result.group || !result.dictionary || !result.entries)\n return result;\n const jsonFile = path.join(path.dirname(result.file), \"locales.json\");\n const document: Record<string, unknown> =\n result.group === result.module\n ? result.dictionary\n : { $group: result.group, ...result.dictionary };\n const json = `${JSON.stringify(document, null, 2)}\\n`;\n writeFileSync(jsonFile, json);\n try {\n const parsed = parseLocaleDictionary({\n sourceFile: rootPath(path.relative(process.cwd(), jsonFile)),\n source: json,\n defaultNamespace: result.module,\n localeCodes: localeCodes(),\n });\n if (!sameEntries(result.entries, parsed.entries)) throw new Error(\"flattened entries differ\");\n } catch (error) {\n unlinkSync(jsonFile);\n return {\n ...result,\n convertible: false,\n reason: `verification failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n unlinkSync(result.file);\n return {\n ...result,\n reason: \"converted (comments in the TypeScript source are not retained)\",\n };\n });\n}\n\nexport const localesTsDeprecatedCheck: DoctorCheck = {\n name: \"locales-ts-deprecated\",\n run: () => {\n const sources = inspectLocaleSources();\n if (sources.length === 0) return undefined;\n const details = sources.map((source) => {\n const label = path.relative(process.cwd(), source.file).replaceAll(\"\\\\\", \"/\");\n return `${label}: ${source.convertible ? \"auto-convertible\" : \"manual\"} (${source.reason})`;\n });\n return {\n name: \"locales-ts-deprecated\",\n status: \"warn\",\n detail:\n `${details.join(\"; \")}. Auto-loaded locales.ts goes away in v6; ` +\n \"run `warlock doctor --fix` to convert auto-convertible files.\",\n };\n },\n};\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { bootForDiagnostics } from "./boot-for-diagnostics.mjs";
|
|
2
|
+
import { fixLocaleSources } from "./checks/locales-ts-deprecated.check.mjs";
|
|
2
3
|
import { defaultDoctorChecks } from "./checks/index.mjs";
|
|
3
4
|
import { printReport } from "./format-report.mjs";
|
|
4
5
|
import { runChecks } from "./run-checks.mjs";
|
|
@@ -25,9 +26,13 @@ import { runChecks } from "./run-checks.mjs";
|
|
|
25
26
|
* banner and exits zero — which also disposes of the handles the boot pass
|
|
26
27
|
* left open (a Fastify instance, a Vite dev server for `web` apps).
|
|
27
28
|
*
|
|
28
|
-
* @param
|
|
29
|
+
* @param data Parsed CLI args, including the optional safe migration flag.
|
|
29
30
|
*/
|
|
30
|
-
async function doctorCommandAction(
|
|
31
|
+
async function doctorCommandAction(data) {
|
|
32
|
+
if (data.options.fix === true) for (const result of fixLocaleSources()) {
|
|
33
|
+
const file = result.file.replace(process.cwd(), ".").replaceAll("\\", "/");
|
|
34
|
+
console.log(`${result.convertible ? "converted" : "manual"} ${file}: ${result.reason}`);
|
|
35
|
+
}
|
|
31
36
|
const report = await runChecks(defaultDoctorChecks, await bootForDiagnostics());
|
|
32
37
|
printReport(report);
|
|
33
38
|
if (report.hasFailures) process.exit(report.exitCode);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-command.action.mjs","names":[],"sources":["../../../../../../../../../core/src/cli/commands/doctor/doctor-command.action.ts"],"sourcesContent":["import type { CommandActionData } from \"../../../commands/types\";\nimport { bootForDiagnostics } from \"./boot-for-diagnostics\";\nimport { defaultDoctorChecks } from \"./checks\";\nimport { printReport } from \"./format-report\";\nimport { runChecks } from \"./run-checks\";\n\n/**\n * Action behind `warlock doctor`.\n *\n * Two steps, in this order and for a reason: ONE boot pass, then every check\n * against what that pass observed. The checks never boot anything themselves,\n * so the report cannot contain two lines that inspected the app at different\n * moments and contradicted each other — which is exactly what it used to\n * contain (`routes: 0 registered` beside `health: /health + /ready\n * registered`, in the same run, on an app serving fourteen routes).\n *\n * The pass boots every connector and starts none, so no database, cache or\n * socket connection is opened and no port is bound. See\n * `boot-for-diagnostics.ts`.\n *\n * A failing check is REPORTED (a line in the report + a non-zero exit), never\n * thrown: the runner converts any thrown check into a `fail` result first.\n *\n * On success this simply returns; the CLI manager then prints its own success\n * banner and exits zero — which also disposes of the handles the boot pass\n * left open (a Fastify instance, a Vite dev server for `web` apps).\n *\n * @param
|
|
1
|
+
{"version":3,"file":"doctor-command.action.mjs","names":[],"sources":["../../../../../../../../../core/src/cli/commands/doctor/doctor-command.action.ts"],"sourcesContent":["import type { CommandActionData } from \"../../../commands/types\";\nimport { bootForDiagnostics } from \"./boot-for-diagnostics\";\nimport { defaultDoctorChecks } from \"./checks\";\nimport { fixLocaleSources } from \"./checks/locales-ts-deprecated.check\";\nimport { printReport } from \"./format-report\";\nimport { runChecks } from \"./run-checks\";\n\n/**\n * Action behind `warlock doctor`.\n *\n * Two steps, in this order and for a reason: ONE boot pass, then every check\n * against what that pass observed. The checks never boot anything themselves,\n * so the report cannot contain two lines that inspected the app at different\n * moments and contradicted each other — which is exactly what it used to\n * contain (`routes: 0 registered` beside `health: /health + /ready\n * registered`, in the same run, on an app serving fourteen routes).\n *\n * The pass boots every connector and starts none, so no database, cache or\n * socket connection is opened and no port is bound. See\n * `boot-for-diagnostics.ts`.\n *\n * A failing check is REPORTED (a line in the report + a non-zero exit), never\n * thrown: the runner converts any thrown check into a `fail` result first.\n *\n * On success this simply returns; the CLI manager then prints its own success\n * banner and exits zero — which also disposes of the handles the boot pass\n * left open (a Fastify instance, a Vite dev server for `web` apps).\n *\n * @param data Parsed CLI args, including the optional safe migration flag.\n */\nexport async function doctorCommandAction(data: CommandActionData): Promise<void> {\n if (data.options.fix === true) {\n for (const result of fixLocaleSources()) {\n const file = result.file.replace(process.cwd(), \".\").replaceAll(\"\\\\\", \"/\");\n console.log(`${result.convertible ? \"converted\" : \"manual\"} ${file}: ${result.reason}`);\n }\n }\n const context = await bootForDiagnostics();\n\n const report = await runChecks(defaultDoctorChecks, context);\n\n printReport(report);\n\n if (report.hasFailures) {\n process.exit(report.exitCode);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,eAAsB,oBAAoB,MAAwC;CAChF,IAAI,KAAK,QAAQ,QAAQ,MACvB,KAAK,MAAM,UAAU,iBAAiB,GAAG;EACvC,MAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ,IAAI,GAAG,GAAG,CAAC,CAAC,WAAW,MAAM,GAAG;EACzE,QAAQ,IAAI,GAAG,OAAO,cAAc,cAAc,SAAS,GAAG,KAAK,IAAI,OAAO,QAAQ;CACxF;CAIF,MAAM,SAAS,MAAM,UAAU,qBAAqB,MAF9B,mBAAmB,CAEkB;CAE3D,YAAY,MAAM;CAElB,IAAI,OAAO,aACT,QAAQ,KAAK,OAAO,QAAQ;AAEhC"}
|
|
@@ -24,6 +24,11 @@ const doctorCommand = command({
|
|
|
24
24
|
name: "doctor",
|
|
25
25
|
description: "Boot the app read-only (no connections, no port) and report on its routes, config, connectors, drivers and health endpoints",
|
|
26
26
|
action: doctorCommandAction,
|
|
27
|
+
options: [{
|
|
28
|
+
text: "--fix",
|
|
29
|
+
description: "Convert safely auto-convertible legacy utils/locales.ts files to locales.json",
|
|
30
|
+
type: "boolean"
|
|
31
|
+
}],
|
|
27
32
|
preload: {
|
|
28
33
|
runtimeStrategy: "development",
|
|
29
34
|
config: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/doctor.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\nimport { doctorCommandAction } from \"./doctor/doctor-command.action\";\n\n/**\n * `warlock doctor` — run a set of diagnostic checks against the real app and\n * print a grouped pass/warn/fail report. Exits non-zero if any check fails.\n *\n * Preload plan: config + env + framework bootstrap, and NO connectors. The\n * `connectors` preloader would START them (open the database, dial the cache,\n * bind the port); doctor's own boot pass BOOTS them instead — the phase where\n * routes and endpoints register — which is what gives the checks a real app to\n * read without opening a single connection. See\n * `doctor/boot-for-diagnostics.ts` for that seam.\n *\n * `runtimeStrategy: \"development\"` is declared, not defaulted: connectors branch\n * on it (`web` serves pages from a manifest in production and from Vite in\n * development, `http` picks its router scan the same way), and a diagnostic\n * command that left the strategy unset would take the production branch by\n * accident — refusing to boot a perfectly healthy app because no build manifest\n * exists. Doctor reads a working tree, so it says so.\n */\nexport const doctorCommand = command({\n name: \"doctor\",\n description:\n \"Boot the app read-only (no connections, no port) and report on its routes, config, connectors, drivers and health endpoints\",\n action: doctorCommandAction,\n preload: {\n runtimeStrategy: \"development\",\n config: true,\n env: true,\n bootstrap: true,\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,aACE;CACF,QAAQ;CACR,SAAS;EACP,iBAAiB;EACjB,QAAQ;EACR,KAAK;EACL,WAAW;CACb;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"doctor.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/doctor.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\nimport { doctorCommandAction } from \"./doctor/doctor-command.action\";\n\n/**\n * `warlock doctor` — run a set of diagnostic checks against the real app and\n * print a grouped pass/warn/fail report. Exits non-zero if any check fails.\n *\n * Preload plan: config + env + framework bootstrap, and NO connectors. The\n * `connectors` preloader would START them (open the database, dial the cache,\n * bind the port); doctor's own boot pass BOOTS them instead — the phase where\n * routes and endpoints register — which is what gives the checks a real app to\n * read without opening a single connection. See\n * `doctor/boot-for-diagnostics.ts` for that seam.\n *\n * `runtimeStrategy: \"development\"` is declared, not defaulted: connectors branch\n * on it (`web` serves pages from a manifest in production and from Vite in\n * development, `http` picks its router scan the same way), and a diagnostic\n * command that left the strategy unset would take the production branch by\n * accident — refusing to boot a perfectly healthy app because no build manifest\n * exists. Doctor reads a working tree, so it says so.\n */\nexport const doctorCommand = command({\n name: \"doctor\",\n description:\n \"Boot the app read-only (no connections, no port) and report on its routes, config, connectors, drivers and health endpoints\",\n action: doctorCommandAction,\n options: [\n {\n text: \"--fix\",\n description: \"Convert safely auto-convertible legacy utils/locales.ts files to locales.json\",\n type: \"boolean\",\n },\n ],\n preload: {\n runtimeStrategy: \"development\",\n config: true,\n env: true,\n bootstrap: true,\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,aACE;CACF,QAAQ;CACR,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;CACA,SAAS;EACP,iBAAiB;EACjB,QAAQ;EACR,KAAK;EACL,WAAW;CACb;AACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drop-tables.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/drop-tables.command.ts"],"sourcesContent":["import { dropTablesAction } from \"../../database/drop-tables-action\";\
|
|
1
|
+
{"version":3,"file":"drop-tables.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/drop-tables.command.ts"],"sourcesContent":["import { dropTablesAction } from \"../../database/drop-tables-action\";\nimport { command } from \"../../commands/cli-command\";\n\nexport const dropTablesCommand = command({\n name: \"drop.tables\",\n description: \"Drop all tables in the database\",\n action: dropTablesAction,\n preload: {\n config: [\"database\", \"log\"],\n env: true,\n connectors: [\"database\", \"logger\"],\n },\n options: [\n {\n text: \"--force, -f\",\n description: \"Drop tables without confirmation\",\n type: \"boolean\",\n },\n ],\n});\n"],"mappings":";;;;AAGA,MAAa,oBAAoB,QAAQ;CACvC,MAAM;CACN,aAAa;CACb,QAAQ;CACR,SAAS;EACP,QAAQ,CAAC,YAAY,KAAK;EAC1B,KAAK;EACL,YAAY,CAAC,YAAY,QAAQ;CACnC;CACA,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;AACF,CAAC"}
|
|
@@ -52,18 +52,7 @@ import { guarded } from "app/shared/utils/router";
|
|
|
52
52
|
// router.get("/${name.kebab}", listController);
|
|
53
53
|
`;
|
|
54
54
|
await putFileAsync(path.join(modulePath, "routes.ts"), routesContent);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
groupedTranslations("${name.camel}", {
|
|
58
|
-
// Add your translations here
|
|
59
|
-
// Example:
|
|
60
|
-
// welcome: {
|
|
61
|
-
// en: "Welcome",
|
|
62
|
-
// ar: "مرحبا",
|
|
63
|
-
// },
|
|
64
|
-
});
|
|
65
|
-
`;
|
|
66
|
-
await putFileAsync(path.join(modulePath, "utils", "locales.ts"), localesContent);
|
|
55
|
+
await putFileAsync(path.join(modulePath, "utils", "locales.json"), "{}\n");
|
|
67
56
|
if (withCrud) {
|
|
68
57
|
const entity = singularName(moduleName);
|
|
69
58
|
await putFileAsync(path.join(modulePath, "controllers", `create-${entity.kebab}.controller.ts`), crudCreateControllerStub(entity));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/module.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport path from \"node:path\";\nimport { appPath } from \"../../../../utils\";\nimport type { CommandActionData } from \"../../../../commands/types\";\nimport {\n crudCreateControllerStub,\n crudCreateSchemaStub,\n crudCreateServiceStub,\n crudDeleteControllerStub,\n crudDeleteServiceStub,\n crudGetServiceStub,\n crudListControllerStub,\n crudListServiceStub,\n crudModelStub,\n crudRepositoryStub,\n crudResourceStub,\n crudRoutesStub,\n crudSeedStub,\n crudShowControllerStub,\n crudUpdateControllerStub,\n crudUpdateSchemaStub,\n crudUpdateServiceStub,\n} from \"../templates/stubs\";\nimport { pluralName, singularName } from \"../utils/name-parser\";\nimport { moduleExists } from \"../utils/path-resolver\";\nimport {\n ensureDirectoryAsync,\n putFileAsync,\n reportSkippedFiles,\n setDryRun,\n setSkipExisting,\n} from \"../utils/writer\";\nimport { createMigrationFile } from \"./migration.generator\";\n\nexport async function generateModule(data: CommandActionData): Promise<void> {\n const moduleName = data.args[0];\n\n if (!moduleName) {\n console.log(colors.red(\"Error: Module name is required\"));\n console.log(colors.yellow(\"Usage: warlock generate.module <name> [options]\"));\n console.log(colors.yellow(\"Example: warlock generate.module products\"));\n console.log(colors.yellow(\" warlock generate.module products --minimal\"));\n process.exit(1);\n }\n\n const name = pluralName(moduleName);\n const force = data.options.force || data.options.f;\n setDryRun(Boolean(data.options.dryRun));\n // --force only creates what is missing; replacing edited files needs --overwrite too\n setSkipExisting(!data.options.overwrite);\n\n // Full CRUD is the default; --minimal (-m) opts down to a bare skeleton.\n const minimal = data.options.minimal || data.options.m;\n const withCrud = !minimal;\n\n // Check if module already exists\n if ((await moduleExists(name.kebab)) && !force) {\n console.log(colors.red(`Error: Module \"${name.kebab}\" already exists`));\n console.log(colors.yellow(\"Use --force to overwrite\"));\n process.exit(1);\n }\n\n const modulePath = appPath(name.kebab);\n\n // Create module directory structure\n const directories = [\n \"controllers\",\n \"services\",\n \"models\",\n \"repositories\",\n \"schema\",\n \"resources\",\n \"events\",\n \"types\",\n \"utils\",\n ];\n\n for (const dir of directories) {\n await ensureDirectoryAsync(path.join(modulePath, dir));\n }\n\n // Create main.ts\n const mainContent = `// You may use this file as a custom entry point to be executed when the app starts.\n// it runs only once, and you don't need to import the routes.ts file, it will be imported automatically.\n// Use it to regsiter or boot up any custom logic for this module.\n`;\n await putFileAsync(path.join(modulePath, \"main.ts\"), mainContent);\n\n // Create routes.ts\n const routesContent = withCrud\n ? crudRoutesStub(name)\n : `import { router } from \"@warlock.js/core\";\nimport { guarded } from \"app/shared/utils/router\";\n\n// Define your routes here\n// Example:\n// router.get(\"/${name.kebab}\", listController);\n`;\n await putFileAsync(path.join(modulePath, \"routes.ts\"), routesContent);\n\n // Create utils/locales.ts\n const localesContent = `import { groupedTranslations } from \"@warlock.js/core\";\n\ngroupedTranslations(\"${name.camel}\", {\n // Add your translations here\n // Example:\n // welcome: {\n // en: \"Welcome\",\n // ar: \"مرحبا\",\n // },\n});\n`;\n await putFileAsync(path.join(modulePath, \"utils\", \"locales.ts\"), localesContent);\n\n // Full CRUD scaffold (the default) unless --minimal was passed\n if (withCrud) {\n const entity = singularName(moduleName);\n\n // Create controllers\n await putFileAsync(\n path.join(modulePath, \"controllers\", `create-${entity.kebab}.controller.ts`),\n crudCreateControllerStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `update-${entity.kebab}.controller.ts`),\n crudUpdateControllerStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `list-${name.kebab}.controller.ts`),\n crudListControllerStub(name),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `get-${entity.kebab}.controller.ts`),\n crudShowControllerStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `delete-${entity.kebab}.controller.ts`),\n crudDeleteControllerStub(entity),\n );\n\n // Create schema files — each exports both the schema value and its\n // inferred type; controllers import them directly (no requests/ alias).\n await putFileAsync(\n path.join(modulePath, \"schema\", `create-${entity.kebab}.schema.ts`),\n crudCreateSchemaStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"schema\", `update-${entity.kebab}.schema.ts`),\n crudUpdateSchemaStub(entity),\n );\n\n // Create model\n await ensureDirectoryAsync(path.join(modulePath, \"models\", entity.kebab));\n await putFileAsync(\n path.join(modulePath, \"models\", entity.kebab, `${entity.kebab}.model.ts`),\n crudModelStub(entity),\n );\n\n // Create model index\n await putFileAsync(\n path.join(modulePath, \"models\", entity.kebab, \"index.ts\"),\n `export * from \"./${entity.kebab}.model\";\\n`,\n );\n\n // Create migrations folder\n await ensureDirectoryAsync(path.join(modulePath, \"models\", entity.kebab, \"migrations\"));\n\n // Create resource\n await putFileAsync(\n path.join(modulePath, \"resources\", `${entity.kebab}.resource.ts`),\n crudResourceStub(name),\n );\n\n // Create repository\n await putFileAsync(\n path.join(modulePath, \"repositories\", `${name.kebab}.repository.ts`),\n crudRepositoryStub(name),\n );\n\n // Create services\n await putFileAsync(\n path.join(modulePath, \"services\", `create-${entity.kebab}.service.ts`),\n crudCreateServiceStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `update-${entity.kebab}.service.ts`),\n crudUpdateServiceStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `list-${name.kebab}.service.ts`),\n crudListServiceStub(name),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `get-${entity.kebab}.service.ts`),\n crudGetServiceStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `delete-${entity.kebab}.service.ts`),\n crudDeleteServiceStub(entity),\n );\n\n // Create seeds\n await ensureDirectoryAsync(path.join(modulePath, \"seeds\"));\n await putFileAsync(path.join(modulePath, \"seeds\", `${name.kebab}.seed.ts`), crudSeedStub(name));\n\n // Create migration\n await createMigrationFile(name.kebab, entity.kebab);\n }\n\n reportSkippedFiles();\n\n console.log(colors.cyan(`\\n✨ Module \"${name.kebab}\" generated successfully!`));\n\n if (withCrud) {\n console.log(colors.gray(`\\n📦 CRUD scaffold created with:`));\n console.log(colors.gray(` - List, Get, Create, Update & Delete controllers`));\n console.log(colors.gray(` - Repository`));\n console.log(colors.gray(` - Validation schemas in schema/`));\n console.log(colors.gray(` - Model with resource`));\n console.log(colors.gray(` - Routes configured`));\n console.log(colors.gray(`\\nNext steps:`));\n const entity = singularName(moduleName);\n console.log(\n colors.gray(\n ` 1. Update model schema in models/${entity.kebab}/${entity.kebab}.model.ts`,\n ),\n );\n console.log(colors.gray(` 2. Update schema rules in schema/*.schema.ts`));\n } else {\n console.log(colors.gray(`\\nNext steps:`));\n console.log(colors.gray(` 1. Define routes in ${name.kebab}/routes.ts`));\n console.log(\n colors.gray(` 2. Create controllers: warlock generate.controller ${name.kebab}/<name>`),\n );\n console.log(colors.gray(` 3. Create models: warlock generate.model ${name.kebab}/<name>`));\n console.log(colors.gray(`\\nTip: omit --minimal to generate a full CRUD scaffold`));\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"module.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/module.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport path from \"node:path\";\nimport { appPath } from \"../../../../utils\";\nimport type { CommandActionData } from \"../../../../commands/types\";\nimport {\n crudCreateControllerStub,\n crudCreateSchemaStub,\n crudCreateServiceStub,\n crudDeleteControllerStub,\n crudDeleteServiceStub,\n crudGetServiceStub,\n crudListControllerStub,\n crudListServiceStub,\n crudModelStub,\n crudRepositoryStub,\n crudResourceStub,\n crudRoutesStub,\n crudSeedStub,\n crudShowControllerStub,\n crudUpdateControllerStub,\n crudUpdateSchemaStub,\n crudUpdateServiceStub,\n} from \"../templates/stubs\";\nimport { pluralName, singularName } from \"../utils/name-parser\";\nimport { moduleExists } from \"../utils/path-resolver\";\nimport {\n ensureDirectoryAsync,\n putFileAsync,\n reportSkippedFiles,\n setDryRun,\n setSkipExisting,\n} from \"../utils/writer\";\nimport { createMigrationFile } from \"./migration.generator\";\n\nexport async function generateModule(data: CommandActionData): Promise<void> {\n const moduleName = data.args[0];\n\n if (!moduleName) {\n console.log(colors.red(\"Error: Module name is required\"));\n console.log(colors.yellow(\"Usage: warlock generate.module <name> [options]\"));\n console.log(colors.yellow(\"Example: warlock generate.module products\"));\n console.log(colors.yellow(\" warlock generate.module products --minimal\"));\n process.exit(1);\n }\n\n const name = pluralName(moduleName);\n const force = data.options.force || data.options.f;\n setDryRun(Boolean(data.options.dryRun));\n // --force only creates what is missing; replacing edited files needs --overwrite too\n setSkipExisting(!data.options.overwrite);\n\n // Full CRUD is the default; --minimal (-m) opts down to a bare skeleton.\n const minimal = data.options.minimal || data.options.m;\n const withCrud = !minimal;\n\n // Check if module already exists\n if ((await moduleExists(name.kebab)) && !force) {\n console.log(colors.red(`Error: Module \"${name.kebab}\" already exists`));\n console.log(colors.yellow(\"Use --force to overwrite\"));\n process.exit(1);\n }\n\n const modulePath = appPath(name.kebab);\n\n // Create module directory structure\n const directories = [\n \"controllers\",\n \"services\",\n \"models\",\n \"repositories\",\n \"schema\",\n \"resources\",\n \"events\",\n \"types\",\n \"utils\",\n ];\n\n for (const dir of directories) {\n await ensureDirectoryAsync(path.join(modulePath, dir));\n }\n\n // Create main.ts\n const mainContent = `// You may use this file as a custom entry point to be executed when the app starts.\n// it runs only once, and you don't need to import the routes.ts file, it will be imported automatically.\n// Use it to regsiter or boot up any custom logic for this module.\n`;\n await putFileAsync(path.join(modulePath, \"main.ts\"), mainContent);\n\n // Create routes.ts\n const routesContent = withCrud\n ? crudRoutesStub(name)\n : `import { router } from \"@warlock.js/core\";\nimport { guarded } from \"app/shared/utils/router\";\n\n// Define your routes here\n// Example:\n// router.get(\"/${name.kebab}\", listController);\n`;\n await putFileAsync(path.join(modulePath, \"routes.ts\"), routesContent);\n\n /* Legacy TypeScript locale scaffold, retained here only until its removal can\n * be made in a dedicated cleanup. New modules use the JSON dictionary below.\n const localesContent = `import { groupedTranslations } from \"@warlock.js/core\";\n\ngroupedTranslations(\"${name.camel}\", {\n // Add your translations here\n // Example:\n // welcome: {\n // en: \"Welcome\",\n // ar: \"مرحبا\",\n // },\n});\n`;\n await putFileAsync(path.join(modulePath, \"utils\", \"locales.ts\"), localesContent); */\n\n // The generator cannot infer configured locale codes for a first dictionary.\n await putFileAsync(path.join(modulePath, \"utils\", \"locales.json\"), \"{}\\n\");\n\n // Full CRUD scaffold (the default) unless --minimal was passed\n if (withCrud) {\n const entity = singularName(moduleName);\n\n // Create controllers\n await putFileAsync(\n path.join(modulePath, \"controllers\", `create-${entity.kebab}.controller.ts`),\n crudCreateControllerStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `update-${entity.kebab}.controller.ts`),\n crudUpdateControllerStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `list-${name.kebab}.controller.ts`),\n crudListControllerStub(name),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `get-${entity.kebab}.controller.ts`),\n crudShowControllerStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"controllers\", `delete-${entity.kebab}.controller.ts`),\n crudDeleteControllerStub(entity),\n );\n\n // Create schema files — each exports both the schema value and its\n // inferred type; controllers import them directly (no requests/ alias).\n await putFileAsync(\n path.join(modulePath, \"schema\", `create-${entity.kebab}.schema.ts`),\n crudCreateSchemaStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"schema\", `update-${entity.kebab}.schema.ts`),\n crudUpdateSchemaStub(entity),\n );\n\n // Create model\n await ensureDirectoryAsync(path.join(modulePath, \"models\", entity.kebab));\n await putFileAsync(\n path.join(modulePath, \"models\", entity.kebab, `${entity.kebab}.model.ts`),\n crudModelStub(entity),\n );\n\n // Create model index\n await putFileAsync(\n path.join(modulePath, \"models\", entity.kebab, \"index.ts\"),\n `export * from \"./${entity.kebab}.model\";\\n`,\n );\n\n // Create migrations folder\n await ensureDirectoryAsync(path.join(modulePath, \"models\", entity.kebab, \"migrations\"));\n\n // Create resource\n await putFileAsync(\n path.join(modulePath, \"resources\", `${entity.kebab}.resource.ts`),\n crudResourceStub(name),\n );\n\n // Create repository\n await putFileAsync(\n path.join(modulePath, \"repositories\", `${name.kebab}.repository.ts`),\n crudRepositoryStub(name),\n );\n\n // Create services\n await putFileAsync(\n path.join(modulePath, \"services\", `create-${entity.kebab}.service.ts`),\n crudCreateServiceStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `update-${entity.kebab}.service.ts`),\n crudUpdateServiceStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `list-${name.kebab}.service.ts`),\n crudListServiceStub(name),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `get-${entity.kebab}.service.ts`),\n crudGetServiceStub(entity),\n );\n\n await putFileAsync(\n path.join(modulePath, \"services\", `delete-${entity.kebab}.service.ts`),\n crudDeleteServiceStub(entity),\n );\n\n // Create seeds\n await ensureDirectoryAsync(path.join(modulePath, \"seeds\"));\n await putFileAsync(path.join(modulePath, \"seeds\", `${name.kebab}.seed.ts`), crudSeedStub(name));\n\n // Create migration\n await createMigrationFile(name.kebab, entity.kebab);\n }\n\n reportSkippedFiles();\n\n console.log(colors.cyan(`\\n✨ Module \"${name.kebab}\" generated successfully!`));\n\n if (withCrud) {\n console.log(colors.gray(`\\n📦 CRUD scaffold created with:`));\n console.log(colors.gray(` - List, Get, Create, Update & Delete controllers`));\n console.log(colors.gray(` - Repository`));\n console.log(colors.gray(` - Validation schemas in schema/`));\n console.log(colors.gray(` - Model with resource`));\n console.log(colors.gray(` - Routes configured`));\n console.log(colors.gray(`\\nNext steps:`));\n const entity = singularName(moduleName);\n console.log(\n colors.gray(` 1. Update model schema in models/${entity.kebab}/${entity.kebab}.model.ts`),\n );\n console.log(colors.gray(` 2. Update schema rules in schema/*.schema.ts`));\n } else {\n console.log(colors.gray(`\\nNext steps:`));\n console.log(colors.gray(` 1. Define routes in ${name.kebab}/routes.ts`));\n console.log(\n colors.gray(` 2. Create controllers: warlock generate.controller ${name.kebab}/<name>`),\n );\n console.log(colors.gray(` 3. Create models: warlock generate.model ${name.kebab}/<name>`));\n console.log(colors.gray(`\\nTip: omit --minimal to generate a full CRUD scaffold`));\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-resolver.mjs","names":["ensureDirectoryAsync"],"sources":["../../../../../../../../../../core/src/cli/commands/generate/utils/path-resolver.ts"],"sourcesContent":["import { directoryExistsAsync, fileExistsAsync } from \"@warlock.js/fs\";\
|
|
1
|
+
{"version":3,"file":"path-resolver.mjs","names":["ensureDirectoryAsync"],"sources":["../../../../../../../../../../core/src/cli/commands/generate/utils/path-resolver.ts"],"sourcesContent":["import { directoryExistsAsync, fileExistsAsync } from \"@warlock.js/fs\";\nimport path from \"node:path\";\nimport { appPath } from \"../../../../utils\";\nimport { ensureDirectoryAsync } from \"./writer\";\n\n/**\n * Resolve module path\n * Returns absolute path to module directory\n */\nexport function resolveModulePath(module: string): string {\n return appPath(module);\n}\n\n/**\n * Resolve component path within a module\n * @param module - Module name (e.g., \"users\")\n * @param type - Component type (e.g., \"controllers\", \"services\")\n * @param name - Component name (e.g., \"create-user\")\n * @param extension - File extension (default: \".ts\")\n */\nexport function resolveComponentPath(\n module: string,\n type: string,\n name: string,\n extension = \".ts\",\n): string {\n return path.join(resolveModulePath(module), type, `${name}${extension}`);\n}\n\n/**\n * Check if module exists\n *\n * A module is a DIRECTORY under the app path, so this uses\n * `directoryExistsAsync` — `fileExistsAsync` would resolve `false` for a folder\n * and wrongly gate every module-scoped generator behind \"module does not exist\".\n */\nexport async function moduleExists(module: string): Promise<boolean> {\n return await directoryExistsAsync(resolveModulePath(module));\n}\n\n/**\n * Check if file exists\n */\nexport async function componentExists(\n module: string,\n type: string,\n name: string,\n extension = \".ts\",\n): Promise<boolean> {\n return (await fileExistsAsync(resolveComponentPath(module, type, name, extension))) as boolean;\n}\n\n/**\n * Ensure directory exists, create if missing\n */\nexport async function ensureDirectory(dirPath: string): Promise<void> {\n await ensureDirectoryAsync(dirPath);\n}\n\n/**\n * Ensure component directory exists\n */\nexport async function ensureComponentDirectory(module: string, type: string): Promise<void> {\n const dirPath = path.join(resolveModulePath(module), type);\n await ensureDirectory(dirPath);\n}\n"],"mappings":";;;;;;;;;;;AASA,SAAgB,kBAAkB,QAAwB;CACxD,OAAO,QAAQ,MAAM;AACvB;;;;;;;;AASA,SAAgB,qBACd,QACA,MACA,MACA,YAAY,OACJ;CACR,OAAO,KAAK,KAAK,kBAAkB,MAAM,GAAG,MAAM,GAAG,OAAO,WAAW;AACzE;;;;;;;;AASA,eAAsB,aAAa,QAAkC;CACnE,OAAO,MAAM,qBAAqB,kBAAkB,MAAM,CAAC;AAC7D;;;;AAKA,eAAsB,gBACpB,QACA,MACA,MACA,YAAY,OACM;CAClB,OAAQ,MAAM,gBAAgB,qBAAqB,QAAQ,MAAM,MAAM,SAAS,CAAC;AACnF;;;;AAKA,eAAsB,gBAAgB,SAAgC;CACpE,MAAMA,uBAAqB,OAAO;AACpC;;;;AAKA,eAAsB,yBAAyB,QAAgB,MAA6B;CAE1F,MAAM,gBADU,KAAK,KAAK,kBAAkB,MAAM,GAAG,IACzB,CAAC;AAC/B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/migrate.command.ts"],"sourcesContent":["import { migrateAction } from \"../../database/migrate-action\";\
|
|
1
|
+
{"version":3,"file":"migrate.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/migrate.command.ts"],"sourcesContent":["import { migrateAction } from \"../../database/migrate-action\";\nimport { command } from \"../../commands/cli-command\";\n\nexport const migrateCommand = command({\n action: migrateAction,\n name: \"migrate\",\n description: \"Run database migrations\",\n preload: {\n config: [\"database\", \"log\"],\n connectors: [\"database\", \"logger\", \"storage\"],\n },\n options: [\n {\n text: \"--fresh, -f\",\n description: \"Drop all tables and re-run migrations\",\n type: \"boolean\",\n },\n {\n text: \"--rollback, -r\",\n description: \"Rollback migrations, drop all tables\",\n type: \"boolean\",\n },\n {\n text: \"--path, -p\",\n description: \"Migration file path, if not provided, all migrations will be wroking\",\n type: \"string\",\n },\n {\n text: \"--list, -l\",\n description: \"Show migration state: executed migrations, then what will run next\",\n type: \"boolean\",\n },\n {\n text: \"--pending\",\n description:\n \"List only what will run next, in execution order. Exits 0 if nothing is pending, 1 if something is, 2 if it could not be determined\",\n type: \"boolean\",\n },\n {\n text: \"--all, -a\",\n description: \"List all migrations files in the app\",\n type: \"boolean\",\n },\n {\n text: \"--sql, -s\",\n description: \"Export all migrations as phase-ordered SQL files instead of executing them\",\n type: \"boolean\",\n },\n {\n text: \"--pending-only\",\n description: \"When used with --sql, exports only pending migrations instead of all\",\n type: \"boolean\",\n },\n {\n text: \"--compact, -c\",\n description:\n \"When used with --sql, strips out generated comments and blank lines to reduce file size\",\n type: \"boolean\",\n },\n ],\n});\n"],"mappings":";;;;AAGA,MAAa,iBAAiB,QAAQ;CACpC,QAAQ;CACR,MAAM;CACN,aAAa;CACb,SAAS;EACP,QAAQ,CAAC,YAAY,KAAK;EAC1B,YAAY;GAAC;GAAY;GAAU;EAAS;CAC9C;CACA,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;CACF;AACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/routes.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\
|
|
1
|
+
{"version":3,"file":"routes.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/routes.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\nimport { routesCommandAction } from \"./routes/routes-command.action\";\nimport { routesDiffCommandAction } from \"./routes/routes-diff.action\";\n\n/**\n * `warlock routes` — list every registered HTTP route as a table (a read-only\n * sibling of `warlock doctor`). Filter with `--method` / `--path` / `--name`,\n * or emit the normalized rows as JSON with `--json`.\n *\n * The preload is config-only and imports no route modules; the action loads them\n * through `bootForDiagnostics()`, the same loader the server uses at boot. A\n * route file that throws on import is reported on stderr instead of being\n * silently omitted from the table.\n */\nexport const routesCommand = command({\n name: \"routes\",\n description:\n \"List the registered HTTP routes as a table (read-only); filter with --method / --path / --name, or emit JSON with --json\",\n action: routesCommandAction,\n preload: {\n config: true,\n env: true,\n bootstrap: true,\n },\n options: [\n {\n text: \"--method, -m\",\n description: \"Filter by HTTP method (case-insensitive, e.g. GET, POST)\",\n type: \"string\",\n },\n {\n text: \"--path, -p\",\n description: \"Filter by a case-insensitive path substring\",\n type: \"string\",\n },\n {\n text: \"--name, -n\",\n description: \"Filter by a case-insensitive route-name substring\",\n type: \"string\",\n },\n {\n text: \"--json, -j\",\n description: \"Output the routes as JSON instead of a table\",\n type: \"boolean\",\n },\n ],\n});\n\n/** Compare live development page routes with the last successful build. */\nexport const routesDiffCommand = command({\n name: \"routes:diff\",\n description: \"Compare live page routes with the last successful production build\",\n action: routesDiffCommandAction,\n preload: {\n runtimeStrategy: \"development\",\n warlockConfig: true,\n config: true,\n env: true,\n bootstrap: true,\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;AAcA,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,aACE;CACF,QAAQ;CACR,SAAS;EACP,QAAQ;EACR,KAAK;EACL,WAAW;CACb;CACA,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;CACF;AACF,CAAC;;AAGD,MAAa,oBAAoB,QAAQ;CACvC,MAAM;CACN,aAAa;CACb,QAAQ;CACR,SAAS;EACP,iBAAiB;EACjB,eAAe;EACf,QAAQ;EACR,KAAK;EACL,WAAW;CACb;AACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/seed.command.ts"],"sourcesContent":["import { seedCommandAction } from \"../../database/seed-command-action\";\
|
|
1
|
+
{"version":3,"file":"seed.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/seed.command.ts"],"sourcesContent":["import { seedCommandAction } from \"../../database/seed-command-action\";\nimport { command } from \"../../commands/cli-command\";\n\nexport const seedCommand = command({\n action: seedCommandAction,\n name: \"seed\",\n description: \"Run database seeds\",\n preload: {\n config: true,\n env: true,\n bootstrap: true,\n connectors: [\"database\", \"cache\", \"logger\", \"storage\"],\n },\n options: [\n {\n text: \"--fresh, -f\",\n description: \"Drop all tables records and run seeds\",\n type: \"boolean\",\n },\n {\n text: \"--list, -l\",\n description: \"Display the seeds list in order without execution\",\n type: \"boolean\",\n },\n {\n text: \"--transaction, -t\",\n description: \"Run seeds in a transaction\",\n type: \"boolean\",\n defaultValue: true,\n },\n {\n text: \"--drop, -d\",\n description:\n 'Undo seeded data — delete tracked records (reverse order) and reset the seed log; scope to one seeder with --drop=\"Seed Name\"',\n type: \"string\",\n },\n ],\n});\n"],"mappings":";;;;AAGA,MAAa,cAAc,QAAQ;CACjC,QAAQ;CACR,MAAM;CACN,aAAa;CACb,SAAS;EACP,QAAQ;EACR,KAAK;EACL,WAAW;EACX,YAAY;GAAC;GAAY;GAAS;GAAU;EAAS;CACvD;CACA,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;GACN,cAAc;EAChB;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;CACF;AACF,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertSitesRequireWebRole, parseRoles, parseSites } from "../../application/roles.mjs";
|
|
1
2
|
import { command } from "../../commands/cli-command.mjs";
|
|
2
3
|
import { resolveBuildConfig } from "../../production/resolve-build-config.mjs";
|
|
3
4
|
import { checkDistReadyToStartAsync } from "../../production/assert-dist-ready-to-start.mjs";
|
|
@@ -5,12 +6,29 @@ import { superviseProductionProcess } from "../../production/production-supervis
|
|
|
5
6
|
|
|
6
7
|
//#region ../core/src/cli/commands/start-production.command.ts
|
|
7
8
|
const NODE_FLAG_PATTERN = /^--(inspect(-brk|-wait)?(=.*)?|max-old-space-size=.*|enable-source-maps|trace-.*)$/;
|
|
9
|
+
/** `--role`/`--sites`, in both `--role=x` and `--role x` forms — recognised
|
|
10
|
+
* here so they never leak into the child's app argv (they become
|
|
11
|
+
* `WARLOCK_ROLES`/`WARLOCK_SITES` env vars instead). */
|
|
12
|
+
const ROLE_OPTION_PATTERN = /^--(role|sites)(=.*)?$/;
|
|
8
13
|
const startProductionCommand = command({
|
|
9
14
|
name: "start",
|
|
10
15
|
description: "Start production server",
|
|
11
16
|
persistent: true,
|
|
12
17
|
preload: { warlockConfig: true },
|
|
13
|
-
action: async () => {
|
|
18
|
+
action: async (data) => {
|
|
19
|
+
const roleOption = typeof data.options.role === "string" ? data.options.role : void 0;
|
|
20
|
+
const sitesOption = typeof data.options.sites === "string" ? data.options.sites : void 0;
|
|
21
|
+
let roles;
|
|
22
|
+
let sites;
|
|
23
|
+
try {
|
|
24
|
+
roles = parseRoles(roleOption);
|
|
25
|
+
sites = parseSites(sitesOption);
|
|
26
|
+
assertSitesRequireWebRole(roles, sites);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(`✖ ${error.message}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
14
32
|
const { entryPath, sourcemap, outdir } = resolveBuildConfig();
|
|
15
33
|
const readiness = await checkDistReadyToStartAsync(outdir);
|
|
16
34
|
if (!readiness.ready) {
|
|
@@ -22,13 +40,44 @@ const startProductionCommand = command({
|
|
|
22
40
|
if (sourcemap !== false) nodeArgs.push("--enable-source-maps");
|
|
23
41
|
const appArgs = [];
|
|
24
42
|
const startIndex = process.argv.findIndex((arg) => arg === "start");
|
|
25
|
-
if (startIndex !== -1 && startIndex < process.argv.length - 1)
|
|
26
|
-
|
|
43
|
+
if (startIndex !== -1 && startIndex < process.argv.length - 1) {
|
|
44
|
+
const rest = process.argv.slice(startIndex + 1);
|
|
45
|
+
for (let i = 0; i < rest.length; i++) {
|
|
46
|
+
const arg = rest[i];
|
|
47
|
+
if (NODE_FLAG_PATTERN.test(arg)) {
|
|
48
|
+
nodeArgs.push(arg);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (ROLE_OPTION_PATTERN.test(arg)) {
|
|
52
|
+
const next = rest[i + 1];
|
|
53
|
+
if (!arg.includes("=") && next !== void 0 && !next.startsWith("-")) i++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
appArgs.push(arg);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
27
59
|
nodeArgs.push(entryPath, ...appArgs);
|
|
28
60
|
console.error(`🚀 Starting production server...\n`);
|
|
29
|
-
const
|
|
61
|
+
const env = {
|
|
62
|
+
...process.env,
|
|
63
|
+
WARLOCK_ROLES: [...roles].join(",")
|
|
64
|
+
};
|
|
65
|
+
if (sites) env.WARLOCK_SITES = [...sites].join(",");
|
|
66
|
+
const { exitCode } = await superviseProductionProcess({
|
|
67
|
+
nodeArgs,
|
|
68
|
+
env
|
|
69
|
+
});
|
|
30
70
|
process.exit(exitCode);
|
|
31
|
-
}
|
|
71
|
+
},
|
|
72
|
+
options: [{
|
|
73
|
+
text: "--role",
|
|
74
|
+
description: "Comma-separated roles to serve: api, web, worker. Defaults to every role.",
|
|
75
|
+
type: "string"
|
|
76
|
+
}, {
|
|
77
|
+
text: "--sites",
|
|
78
|
+
description: "Comma-separated site keys to install pages for. Requires --role=web.",
|
|
79
|
+
type: "string"
|
|
80
|
+
}]
|
|
32
81
|
});
|
|
33
82
|
|
|
34
83
|
//#endregion
|