@warlock.js/core 5.2.3 → 5.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs.map +1 -1
- package/esm/benchmark/benchmark-snapshots.d.mts.map +1 -1
- package/esm/benchmark/benchmark.d.mts.map +1 -1
- package/esm/benchmark/channels/console.channel.mjs.map +1 -1
- package/esm/benchmark/channels/noop.channel.d.mts.map +1 -1
- package/esm/benchmark/profiler.d.mts.map +1 -1
- package/esm/benchmark/profiler.mjs.map +1 -1
- package/esm/bootstrap/setup.d.mts.map +1 -1
- package/esm/bootstrap/setup.mjs.map +1 -1
- package/esm/bootstrap.d.mts.map +1 -1
- package/esm/cache/database-cache-driver.d.mts.map +1 -1
- package/esm/cache/database-cache-driver.mjs.map +1 -1
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/cli-commands.utils.mjs.map +1 -1
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -1
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/name-parser.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/prompt.mjs.map +1 -1
- package/esm/cli/commands/routes/diff-page-routes.mjs.map +1 -1
- package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -1
- package/esm/cli/commands/routes/route-row.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-diff.action.mjs.map +1 -1
- package/esm/cli/commands/storage-put.action.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/commands-loader.mjs.map +1 -1
- package/esm/cli/start.mjs.map +1 -1
- package/esm/cli/string-similarity.mjs.map +1 -1
- package/esm/commands/cli-command.mjs.map +1 -1
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/config/config-setter.d.mts.map +1 -1
- package/esm/config/load-config-files.mjs.map +1 -1
- package/esm/connectors/access-connector.d.mts.map +1 -1
- package/esm/connectors/ai-connector.d.mts.map +1 -1
- package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -1
- package/esm/connectors/assert-unique-connector-names.mjs.map +1 -1
- package/esm/connectors/base-connector.d.mts.map +1 -1
- package/esm/connectors/base-connector.mjs.map +1 -1
- package/esm/connectors/cache-connector.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/database-connector.d.mts.map +1 -1
- package/esm/connectors/describe-server-address.mjs.map +1 -1
- package/esm/connectors/herald-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.d.mts.map +1 -1
- package/esm/connectors/mail-connector.d.mts.map +1 -1
- package/esm/connectors/notifications-connector.d.mts.map +1 -1
- package/esm/connectors/register-configured-connectors.mjs.map +1 -1
- package/esm/connectors/socket-connector.d.mts.map +1 -1
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/connectors/storage.connector.d.mts.map +1 -1
- package/esm/container/index.d.mts.map +1 -1
- package/esm/database/create-database-action.mjs.map +1 -1
- package/esm/database/drop-tables-action.mjs.map +1 -1
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/database/seeds/seed-records-table-migration.mjs.map +1 -1
- package/esm/database/seeds/seeders.manager.mjs.map +1 -1
- package/esm/database/seeds/seeds-table-migration.mjs.map +1 -1
- package/esm/dev-server/check-for-updates.mjs.map +1 -1
- package/esm/dev-server/dependency-graph.d.mts.map +1 -1
- package/esm/dev-server/dependency-graph.mjs.map +1 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/file-manager.d.mts.map +1 -1
- package/esm/dev-server/file-manager.mjs.map +1 -1
- package/esm/dev-server/file-operations.d.mts.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/typescript-health-checker.mjs.map +1 -1
- package/esm/dev-server/health-checker/file-health-result.d.mts.map +1 -1
- package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
- package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
- package/esm/dev-server/layer-executor.mjs.map +1 -1
- package/esm/dev-server/loader/hook-thread.d.mts.map +1 -1
- package/esm/dev-server/loader/own-resolver.mjs.map +1 -1
- package/esm/dev-server/loader/register-loader.mjs.map +1 -1
- package/esm/dev-server/loader/resolve-hook.mjs.map +1 -1
- package/esm/dev-server/loader/source-slug.mjs.map +1 -1
- package/esm/dev-server/loader/transpile-cache.mjs.map +1 -1
- package/esm/dev-server/loader/version-registry.mjs.map +1 -1
- package/esm/dev-server/manifest-manager.d.mts.map +1 -1
- package/esm/dev-server/manifest-manager.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/package-json-manager.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/ready-block.mjs.map +1 -1
- package/esm/dev-server/special-files-collector.d.mts.map +1 -1
- package/esm/dev-server/special-files-collector.mjs.map +1 -1
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/dev-server/utils.mjs.map +1 -1
- package/esm/encryption/encrypt.mjs.map +1 -1
- package/esm/encryption/hash.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/features/shadcn.feature.mjs.map +1 -1
- package/esm/generations/features/shared/migration-timestamp.mjs.map +1 -1
- package/esm/generations/features/tailwind.feature.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs +2 -1
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/generations/stubs.mjs +40 -29
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/boot-port-preflight.d.mts.map +1 -1
- package/esm/http/context/request-context.d.mts.map +1 -1
- package/esm/http/context/request-context.mjs.map +1 -1
- package/esm/http/context/request-memo.d.mts.map +1 -1
- package/esm/http/context/request-memo.mjs.map +1 -1
- package/esm/http/createHttpApplication.d.mts.map +1 -1
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/health.d.mts.map +1 -1
- package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
- package/esm/http/ready-report.d.mts.map +1 -1
- package/esm/http/request-controller.d.mts.map +1 -1
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs.map +1 -1
- package/esm/http/uploaded-file.d.mts.map +1 -1
- package/esm/http/uploaded-file.mjs.map +1 -1
- package/esm/http/uploads-config.d.mts.map +1 -1
- package/esm/image/image.d.mts.map +1 -1
- package/esm/image/image.mjs.map +1 -1
- package/esm/logger/logger.mjs.map +1 -1
- package/esm/mail/config.d.mts.map +1 -1
- package/esm/mail/events.d.mts.map +1 -1
- package/esm/mail/mail.d.mts.map +1 -1
- package/esm/mail/mail.mjs.map +1 -1
- package/esm/mail/mailer-pool.d.mts.map +1 -1
- package/esm/mail/mailer-pool.mjs.map +1 -1
- package/esm/mail/send-mail.mjs.map +1 -1
- package/esm/mail/test-mailbox.d.mts.map +1 -1
- package/esm/mail/test-mailbox.mjs.map +1 -1
- package/esm/production/assert-generated-imports.mjs.map +1 -1
- package/esm/production/build-app-production.mjs.map +1 -1
- package/esm/production/dist-build-manifest.mjs.map +1 -1
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs.map +1 -1
- package/esm/production/promote-dist.mjs.map +1 -1
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-adapter.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-adapter.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
- package/esm/repositories/repository.manager.d.mts.map +1 -1
- package/esm/repositories/repository.manager.mjs.map +1 -1
- package/esm/resource/register-resource.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.d.mts.map +1 -1
- package/esm/resource/resource-field-builder.mjs.map +1 -1
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/restful/restful.d.mts.map +1 -1
- package/esm/restful/restful.mjs.map +1 -1
- package/esm/router/log-request-lifecycle.mjs.map +1 -1
- package/esm/router/normalize-route-path.d.mts.map +1 -1
- package/esm/router/positional-handler-diagnostics.d.mts.map +1 -1
- package/esm/router/positional-handler-diagnostics.mjs.map +1 -1
- package/esm/router/route-registry.d.mts.map +1 -1
- package/esm/router/route-registry.mjs.map +1 -1
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs.map +1 -1
- package/esm/socket/utils.d.mts.map +1 -1
- package/esm/storage/config.d.mts.map +1 -1
- package/esm/storage/context/storage-driver-context.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
- package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
- package/esm/storage/drivers/do-spaces-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.mjs.map +1 -1
- package/esm/storage/drivers/r2-driver.d.mts.map +1 -1
- package/esm/storage/scoped-storage.d.mts.map +1 -1
- package/esm/storage/scoped-storage.mjs.map +1 -1
- package/esm/storage/storage-file.d.mts.map +1 -1
- package/esm/storage/storage-file.mjs.map +1 -1
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/utils/safe-fetch.mjs.map +1 -1
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/test-connectors-selection.mjs.map +1 -1
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-setup-timeout.mjs.map +1 -1
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/updater/update-warlock-packages.mjs.map +1 -1
- package/esm/use-cases/use-case-broadcast.d.mts.map +1 -1
- package/esm/use-cases/use-case-events.d.mts.map +1 -1
- package/esm/use-cases/use-case-pipeline.d.mts.map +1 -1
- package/esm/use-cases/use-case.d.mts.map +1 -1
- package/esm/use-cases/use-cases-registry.d.mts.map +1 -1
- package/esm/use-cases/use-cases-registry.mjs.map +1 -1
- package/esm/utils/database-log.d.mts.map +1 -1
- package/esm/utils/database-log.mjs.map +1 -1
- package/esm/utils/environment.d.mts.map +1 -1
- package/esm/utils/framework-vesion.mjs.map +1 -1
- package/esm/utils/get-localized.mjs.map +1 -1
- package/esm/utils/load-environment.mjs.map +1 -1
- package/esm/utils/normalized-path.d.mts.map +1 -1
- package/esm/utils/paths.d.mts.map +1 -1
- package/esm/utils/promise-all-object.d.mts.map +1 -1
- package/esm/utils/promise-all-object.mjs.map +1 -1
- package/esm/utils/sluggable.mjs.map +1 -1
- package/esm/utils/version-compare.mjs.map +1 -1
- package/esm/validation/database/exists-except-current-id.mjs.map +1 -1
- package/esm/validation/database/exists-except-current-user.mjs.map +1 -1
- package/esm/validation/database/unique-except-current-id.mjs.map +1 -1
- package/esm/validation/plugins/localized-plugin.mjs.map +1 -1
- package/esm/validation/validators/file-validator.mjs.map +1 -1
- package/esm/vite/lower-stage3-decorators.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/llms-full.txt +4 -1
- package/llms.txt +1 -1
- package/package.json +12 -12
- package/skills/README.md +1 -1
- package/skills/write-cli-command/SKILL.md +4 -1
|
@@ -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\";\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectors.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/connectors.check.ts"],"sourcesContent":["import type { DoctorCheck } from \"../check.types\";\n\n/**\n * Reports whether the app's connectors survive boot.\n *\n * NEEDS A BOOTED APP: the boot pass runs every connector's `boot()` and no\n * `start()`, and this check reads the outcome.\n *\n * WHAT THIS REPLACED, AND WHY: the previous version printed\n * `11 registered, none active`. Both halves were tautologies. Eleven is the\n * number of built-in connectors the manager constructs when its module is\n * imported — it is the same on every project that has ever existed and says\n * nothing about the app. And \"none active\" was guaranteed, because `active` is\n * set by `start()` and doctor deliberately never starts anything: the warning\n * could not be absent from a healthy app, so it could not mean anything on a\n * broken one.\n *\n * What is reported instead is a fact that varies with the project and can be\n * wrong: the connectors the app itself configured, and whether each of them\n * boots. A connector that throws on boot is a boot-time crash the user has not\n * hit yet.\n *\n * Verdicts:\n * - `warlock.config.ts > connectors` refused (duplicate or reserved name) →\n * `fail`. The next real boot would throw the same error;\n * - a connector's `boot()` threw → `fail`, named, with the message;\n * - otherwise → `ok`, listing what booted.\n */\nexport const connectorsCheck: DoctorCheck = {\n name: \"connectors\",\n run: (context) => {\n const { connectors } = context;\n\n if (connectors.registrationError) {\n return {\n name: \"connectors\",\n status: \"fail\",\n detail: `warlock.config.ts > connectors was refused: ${connectors.registrationError}`,\n };\n }\n\n if (connectors.failures.length > 0) {\n return {\n name: \"connectors\",\n status: \"fail\",\n detail:\n `${connectors.failures.length} connector(s) failed to boot:\\n` +\n connectors.failures\n .map((failure) => ` - ${failure.name}: ${failure.message}`)\n .join(\"\\n\"),\n };\n }\n\n if (!context.booted) {\n return undefined;\n }\n\n const skipped = connectors.skipped\n .map((entry) => `; skipped ${entry.name} (${entry.reason})`)\n .join(\"\");\n\n const configured =\n connectors.configured.length > 0\n ? `${connectors.configured.join(\", \")} from warlock.config.ts + ` +\n `${connectors.registered.length - connectors.configured.length} built-in`\n : `${connectors.registered.length} built-in, none configured in warlock.config.ts`;\n\n return {\n name: \"connectors\",\n status: \"ok\",\n detail: `all booted without error (${configured})${skipped}`,\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAa,kBAA+B;CAC1C,MAAM;CACN,MAAM,YAAY;EAChB,MAAM,EAAE,eAAe;EAEvB,IAAI,WAAW,mBACb,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ,+CAA+C,WAAW;EACpE;EAGF,IAAI,WAAW,SAAS,SAAS,GAC/B,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QACE,GAAG,WAAW,SAAS,OAAO,mCAC9B,WAAW,SACR,KAAK,YAAY,OAAO,QAAQ,KAAK,IAAI,QAAQ,SAAS,
|
|
1
|
+
{"version":3,"file":"connectors.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/connectors.check.ts"],"sourcesContent":["import type { DoctorCheck } from \"../check.types\";\n\n/**\n * Reports whether the app's connectors survive boot.\n *\n * NEEDS A BOOTED APP: the boot pass runs every connector's `boot()` and no\n * `start()`, and this check reads the outcome.\n *\n * WHAT THIS REPLACED, AND WHY: the previous version printed\n * `11 registered, none active`. Both halves were tautologies. Eleven is the\n * number of built-in connectors the manager constructs when its module is\n * imported — it is the same on every project that has ever existed and says\n * nothing about the app. And \"none active\" was guaranteed, because `active` is\n * set by `start()` and doctor deliberately never starts anything: the warning\n * could not be absent from a healthy app, so it could not mean anything on a\n * broken one.\n *\n * What is reported instead is a fact that varies with the project and can be\n * wrong: the connectors the app itself configured, and whether each of them\n * boots. A connector that throws on boot is a boot-time crash the user has not\n * hit yet.\n *\n * Verdicts:\n * - `warlock.config.ts > connectors` refused (duplicate or reserved name) →\n * `fail`. The next real boot would throw the same error;\n * - a connector's `boot()` threw → `fail`, named, with the message;\n * - otherwise → `ok`, listing what booted.\n */\nexport const connectorsCheck: DoctorCheck = {\n name: \"connectors\",\n run: (context) => {\n const { connectors } = context;\n\n if (connectors.registrationError) {\n return {\n name: \"connectors\",\n status: \"fail\",\n detail: `warlock.config.ts > connectors was refused: ${connectors.registrationError}`,\n };\n }\n\n if (connectors.failures.length > 0) {\n return {\n name: \"connectors\",\n status: \"fail\",\n detail:\n `${connectors.failures.length} connector(s) failed to boot:\\n` +\n connectors.failures\n .map((failure) => ` - ${failure.name}: ${failure.message}`)\n .join(\"\\n\"),\n };\n }\n\n if (!context.booted) {\n return undefined;\n }\n\n const skipped = connectors.skipped\n .map((entry) => `; skipped ${entry.name} (${entry.reason})`)\n .join(\"\");\n\n const configured =\n connectors.configured.length > 0\n ? `${connectors.configured.join(\", \")} from warlock.config.ts + ` +\n `${connectors.registered.length - connectors.configured.length} built-in`\n : `${connectors.registered.length} built-in, none configured in warlock.config.ts`;\n\n return {\n name: \"connectors\",\n status: \"ok\",\n detail: `all booted without error (${configured})${skipped}`,\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAa,kBAA+B;CAC1C,MAAM;CACN,MAAM,YAAY;EAChB,MAAM,EAAE,eAAe;EAEvB,IAAI,WAAW,mBACb,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ,+CAA+C,WAAW;EACpE;EAGF,IAAI,WAAW,SAAS,SAAS,GAC/B,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QACE,GAAG,WAAW,SAAS,OAAO,mCAC9B,WAAW,SACR,KAAK,YAAY,OAAO,QAAQ,KAAK,IAAI,QAAQ,SAAS,CAAC,CAC3D,KAAK,IAAI;EAChB;EAGF,IAAI,CAAC,QAAQ,QACX;EAGF,MAAM,UAAU,WAAW,QACxB,KAAK,UAAU,aAAa,MAAM,KAAK,IAAI,MAAM,OAAO,EAAE,CAAC,CAC3D,KAAK,EAAE;EAQV,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ,6BARR,WAAW,WAAW,SAAS,IAC3B,GAAG,WAAW,WAAW,KAAK,IAAI,EAAE,4BACjC,WAAW,WAAW,SAAS,WAAW,WAAW,OAAO,aAC/D,GAAG,WAAW,WAAW,OAAO,iDAKY,GAAG;EACrD;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler-signature.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/handler-signature.check.ts"],"sourcesContent":["import {\n describePositionalHandlerSuspect,\n listPositionalHandlerSuspects,\n} from \"../../../../router/positional-handler-diagnostics\";\nimport type { DoctorCheck } from \"../check.types\";\n\n/**\n * Surfaces route handlers still written against the v4 positional signature,\n * `(request, response)`. v5 calls a handler with a single context object, so\n * the second parameter is always `undefined` and the route fails on its first\n * request with a `TypeError` that names nothing.\n *\n * Verdicts:\n * - one or more suspects → `warn`;\n * - otherwise → `ok`.\n *\n * `warn`, never `fail`: the detection is a heuristic on the handler's declared\n * parameters, so a false positive is possible and must not break a build.\n *\n * NEEDS A BOOTED APP: the router collects suspects as routes register, so this\n * reads a list the boot pass filled. Before that pass existed the list was\n * always empty and this check always passed — it was green because it had\n * nothing to look at, which is indistinguishable from green because the app is\n * fine. It stays silent when there are no routes for the same reason: a check\n * with no input reports nothing rather than a clean bill of health.\n */\nexport const handlerSignatureCheck: DoctorCheck = {\n name: \"handler-signature\",\n run: (context) => {\n if (context.totalRoutes === 0) {\n return undefined;\n }\n\n const suspects = listPositionalHandlerSuspects();\n\n if (suspects.length === 0) {\n return {\n name: \"handler-signature\",\n status: \"ok\",\n detail: \"no handlers look like the v4 positional signature\",\n };\n }\n\n const headline =\n suspects.length === 1\n ? \"1 handler looks like the v4 positional signature\"\n : `${suspects.length} handlers look like the v4 positional signature`;\n\n return {\n name: \"handler-signature\",\n status: \"warn\",\n detail:\n `${headline}:\\n` +\n suspects.map((suspect) => ` - ${describePositionalHandlerSuspect(suspect)}`).join(\"\\n\"),\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,wBAAqC;CAChD,MAAM;CACN,MAAM,YAAY;EAChB,IAAI,QAAQ,gBAAgB,GAC1B;EAGF,MAAM,WAAW,8BAA8B;EAE/C,IAAI,SAAS,WAAW,GACtB,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;EACV;EAQF,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QACE,GARF,SAAS,WAAW,IAChB,qDACA,GAAG,SAAS,OAAO,iDAMT,OACZ,SAAS,KAAK,YAAY,OAAO,iCAAiC,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"handler-signature.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/handler-signature.check.ts"],"sourcesContent":["import {\n describePositionalHandlerSuspect,\n listPositionalHandlerSuspects,\n} from \"../../../../router/positional-handler-diagnostics\";\nimport type { DoctorCheck } from \"../check.types\";\n\n/**\n * Surfaces route handlers still written against the v4 positional signature,\n * `(request, response)`. v5 calls a handler with a single context object, so\n * the second parameter is always `undefined` and the route fails on its first\n * request with a `TypeError` that names nothing.\n *\n * Verdicts:\n * - one or more suspects → `warn`;\n * - otherwise → `ok`.\n *\n * `warn`, never `fail`: the detection is a heuristic on the handler's declared\n * parameters, so a false positive is possible and must not break a build.\n *\n * NEEDS A BOOTED APP: the router collects suspects as routes register, so this\n * reads a list the boot pass filled. Before that pass existed the list was\n * always empty and this check always passed — it was green because it had\n * nothing to look at, which is indistinguishable from green because the app is\n * fine. It stays silent when there are no routes for the same reason: a check\n * with no input reports nothing rather than a clean bill of health.\n */\nexport const handlerSignatureCheck: DoctorCheck = {\n name: \"handler-signature\",\n run: (context) => {\n if (context.totalRoutes === 0) {\n return undefined;\n }\n\n const suspects = listPositionalHandlerSuspects();\n\n if (suspects.length === 0) {\n return {\n name: \"handler-signature\",\n status: \"ok\",\n detail: \"no handlers look like the v4 positional signature\",\n };\n }\n\n const headline =\n suspects.length === 1\n ? \"1 handler looks like the v4 positional signature\"\n : `${suspects.length} handlers look like the v4 positional signature`;\n\n return {\n name: \"handler-signature\",\n status: \"warn\",\n detail:\n `${headline}:\\n` +\n suspects.map((suspect) => ` - ${describePositionalHandlerSuspect(suspect)}`).join(\"\\n\"),\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,wBAAqC;CAChD,MAAM;CACN,MAAM,YAAY;EAChB,IAAI,QAAQ,gBAAgB,GAC1B;EAGF,MAAM,WAAW,8BAA8B;EAE/C,IAAI,SAAS,WAAW,GACtB,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;EACV;EAQF,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QACE,GARF,SAAS,WAAW,IAChB,qDACA,GAAG,SAAS,OAAO,iDAMT,OACZ,SAAS,KAAK,YAAY,OAAO,iCAAiC,OAAO,GAAG,CAAC,CAAC,KAAK,IAAI;EAC3F;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/health.check.ts"],"sourcesContent":["import { config } from \"../../../../config/config-getter\";\nimport { container } from \"../../../../container\";\nimport { router } from \"../../../../router/router\";\nimport type { DoctorCheck } from \"../check.types\";\n\n/**\n * Ask the booted Fastify instance whether a GET route exists at this path.\n *\n * `hasRoute` is the instance's own answer, not a re-derivation of the config\n * that was supposed to produce it — which is the entire point of asking it.\n * Guarded because it is the one part of this check that depends on a Fastify\n * API: if the instance cannot answer, the check says so instead of inventing a\n * verdict from the config flag it is meant to be verifying.\n */\nfunction fastifyHasGet(path: string): boolean | undefined {\n const http = container.has(\"http.server\") ? container.get<any>(\"http.server\") : undefined;\n\n if (!http || typeof http.hasRoute !== \"function\") return undefined;\n\n try {\n return Boolean(http.hasRoute({ method: \"GET\", url: path }));\n } catch {\n return undefined;\n }\n}\n\n/**\n * Whether an app route already claims this path. The HTTP connector puts the\n * probes on the Fastify instance directly and the router scans app routes onto\n * the same instance at `start()`, so a collision is a duplicate registration:\n * Fastify refuses it and the server does not start.\n */\nfunction appRouteClaims(path: string): boolean {\n return router.list().some((route) => {\n const method = String(route.method).toUpperCase();\n\n return route.path === path && (method === \"GET\" || method === \"ALL\");\n });\n}\n\n/**\n * Confirms the liveness (`/health`) and readiness (`/ready`) endpoints are\n * really there.\n *\n * NEEDS A BOOTED APP: the HTTP connector registers the probes during `boot()`,\n * which the boot pass runs. Previously this check read `http.health.enabled`\n * and reported the endpoints as \"registered\" on that basis alone — in the same\n * report where `routes` said zero, because nothing had been booted at all. It\n * was reporting an intention as a fact, and the two lines contradicted each\n * other in the owner's own run.\n *\n * Verdicts:\n * - no `http` config → not applicable, no line;\n * - `http.health.enabled = false` → `ok`. Turning the probes off is a supported\n * choice, and a warning that fires on a deliberate setting every single run\n * is not read by the time it matters;\n * - an app route already claims a probe's path → `fail`: Fastify will refuse\n * the duplicate and the server will not start;\n * - the instance says the route is absent → `fail`;\n * - otherwise → `ok`, with the paths that are actually registered.\n */\nexport const healthCheck: DoctorCheck = {\n name: \"health\",\n run: (context) => {\n if (!config.get(\"http\")) {\n return undefined;\n }\n\n const healthConfig = config.get(\"http.health\");\n\n if (healthConfig?.enabled === false) {\n return {\n name: \"health\",\n status: \"ok\",\n detail: \"probes disabled by config (http.health.enabled = false)\",\n };\n }\n\n const livenessPath = healthConfig?.path ?? \"/health\";\n const readinessPath = healthConfig?.readinessPath ?? \"/ready\";\n\n const collisions = [livenessPath, readinessPath].filter(appRouteClaims);\n\n if (collisions.length > 0) {\n return {\n name: \"health\",\n status: \"fail\",\n detail:\n `an app route already claims ${collisions.join(\" and \")} — Fastify refuses the ` +\n \"duplicate registration and the server will not start. Move the app route, or \" +\n \"rename the probe via http.health.path / http.health.readinessPath\",\n };\n }\n\n // The HTTP connector is what registers the probes. If it did not boot, the\n // connectors check already says so with the reason — reporting the same\n // defect a second time here as a health failure would inflate one problem\n // into two.\n if (!context.connectors.booted.includes(\"http\")) {\n return undefined;\n }\n\n const registered = [livenessPath, readinessPath].map(fastifyHasGet);\n\n if (registered.some((answer) => answer === undefined)) {\n return {\n name: \"health\",\n status: \"warn\",\n detail:\n \"could not read the http server's route table — \" +\n `${livenessPath} + ${readinessPath} unverified`,\n };\n }\n\n const missing = [livenessPath, readinessPath].filter((_path, index) => !registered[index]);\n\n if (missing.length > 0) {\n return {\n name: \"health\",\n status: \"fail\",\n detail: `enabled in config but not registered on the http server: ${missing.join(\", \")}`,\n };\n }\n\n return {\n name: \"health\",\n status: \"ok\",\n detail: `liveness ${livenessPath} + readiness ${readinessPath} registered`,\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAS,cAAc,MAAmC;CACxD,MAAM,OAAO,UAAU,IAAI,aAAa,IAAI,UAAU,IAAS,aAAa,IAAI;CAEhF,IAAI,CAAC,QAAQ,OAAO,KAAK,aAAa,YAAY,OAAO;CAEzD,IAAI;EACF,OAAO,QAAQ,KAAK,SAAS;GAAE,QAAQ;GAAO,KAAK;EAAK,CAAC,CAAC;CAC5D,QAAQ;EACN;CACF;AACF;;;;;;;AAQA,SAAS,eAAe,MAAuB;CAC7C,OAAO,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"health.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/health.check.ts"],"sourcesContent":["import { config } from \"../../../../config/config-getter\";\nimport { container } from \"../../../../container\";\nimport { router } from \"../../../../router/router\";\nimport type { DoctorCheck } from \"../check.types\";\n\n/**\n * Ask the booted Fastify instance whether a GET route exists at this path.\n *\n * `hasRoute` is the instance's own answer, not a re-derivation of the config\n * that was supposed to produce it — which is the entire point of asking it.\n * Guarded because it is the one part of this check that depends on a Fastify\n * API: if the instance cannot answer, the check says so instead of inventing a\n * verdict from the config flag it is meant to be verifying.\n */\nfunction fastifyHasGet(path: string): boolean | undefined {\n const http = container.has(\"http.server\") ? container.get<any>(\"http.server\") : undefined;\n\n if (!http || typeof http.hasRoute !== \"function\") return undefined;\n\n try {\n return Boolean(http.hasRoute({ method: \"GET\", url: path }));\n } catch {\n return undefined;\n }\n}\n\n/**\n * Whether an app route already claims this path. The HTTP connector puts the\n * probes on the Fastify instance directly and the router scans app routes onto\n * the same instance at `start()`, so a collision is a duplicate registration:\n * Fastify refuses it and the server does not start.\n */\nfunction appRouteClaims(path: string): boolean {\n return router.list().some((route) => {\n const method = String(route.method).toUpperCase();\n\n return route.path === path && (method === \"GET\" || method === \"ALL\");\n });\n}\n\n/**\n * Confirms the liveness (`/health`) and readiness (`/ready`) endpoints are\n * really there.\n *\n * NEEDS A BOOTED APP: the HTTP connector registers the probes during `boot()`,\n * which the boot pass runs. Previously this check read `http.health.enabled`\n * and reported the endpoints as \"registered\" on that basis alone — in the same\n * report where `routes` said zero, because nothing had been booted at all. It\n * was reporting an intention as a fact, and the two lines contradicted each\n * other in the owner's own run.\n *\n * Verdicts:\n * - no `http` config → not applicable, no line;\n * - `http.health.enabled = false` → `ok`. Turning the probes off is a supported\n * choice, and a warning that fires on a deliberate setting every single run\n * is not read by the time it matters;\n * - an app route already claims a probe's path → `fail`: Fastify will refuse\n * the duplicate and the server will not start;\n * - the instance says the route is absent → `fail`;\n * - otherwise → `ok`, with the paths that are actually registered.\n */\nexport const healthCheck: DoctorCheck = {\n name: \"health\",\n run: (context) => {\n if (!config.get(\"http\")) {\n return undefined;\n }\n\n const healthConfig = config.get(\"http.health\");\n\n if (healthConfig?.enabled === false) {\n return {\n name: \"health\",\n status: \"ok\",\n detail: \"probes disabled by config (http.health.enabled = false)\",\n };\n }\n\n const livenessPath = healthConfig?.path ?? \"/health\";\n const readinessPath = healthConfig?.readinessPath ?? \"/ready\";\n\n const collisions = [livenessPath, readinessPath].filter(appRouteClaims);\n\n if (collisions.length > 0) {\n return {\n name: \"health\",\n status: \"fail\",\n detail:\n `an app route already claims ${collisions.join(\" and \")} — Fastify refuses the ` +\n \"duplicate registration and the server will not start. Move the app route, or \" +\n \"rename the probe via http.health.path / http.health.readinessPath\",\n };\n }\n\n // The HTTP connector is what registers the probes. If it did not boot, the\n // connectors check already says so with the reason — reporting the same\n // defect a second time here as a health failure would inflate one problem\n // into two.\n if (!context.connectors.booted.includes(\"http\")) {\n return undefined;\n }\n\n const registered = [livenessPath, readinessPath].map(fastifyHasGet);\n\n if (registered.some((answer) => answer === undefined)) {\n return {\n name: \"health\",\n status: \"warn\",\n detail:\n \"could not read the http server's route table — \" +\n `${livenessPath} + ${readinessPath} unverified`,\n };\n }\n\n const missing = [livenessPath, readinessPath].filter((_path, index) => !registered[index]);\n\n if (missing.length > 0) {\n return {\n name: \"health\",\n status: \"fail\",\n detail: `enabled in config but not registered on the http server: ${missing.join(\", \")}`,\n };\n }\n\n return {\n name: \"health\",\n status: \"ok\",\n detail: `liveness ${livenessPath} + readiness ${readinessPath} registered`,\n };\n },\n};\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAS,cAAc,MAAmC;CACxD,MAAM,OAAO,UAAU,IAAI,aAAa,IAAI,UAAU,IAAS,aAAa,IAAI;CAEhF,IAAI,CAAC,QAAQ,OAAO,KAAK,aAAa,YAAY,OAAO;CAEzD,IAAI;EACF,OAAO,QAAQ,KAAK,SAAS;GAAE,QAAQ;GAAO,KAAK;EAAK,CAAC,CAAC;CAC5D,QAAQ;EACN;CACF;AACF;;;;;;;AAQA,SAAS,eAAe,MAAuB;CAC7C,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,UAAU;EACnC,MAAM,SAAS,OAAO,MAAM,MAAM,CAAC,CAAC,YAAY;EAEhD,OAAO,MAAM,SAAS,SAAS,WAAW,SAAS,WAAW;CAChE,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,cAA2B;CACtC,MAAM;CACN,MAAM,YAAY;EAChB,IAAI,CAAC,OAAO,IAAI,MAAM,GACpB;EAGF,MAAM,eAAe,OAAO,IAAI,aAAa;EAE7C,IAAI,cAAc,YAAY,OAC5B,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;EACV;EAGF,MAAM,eAAe,cAAc,QAAQ;EAC3C,MAAM,gBAAgB,cAAc,iBAAiB;EAErD,MAAM,aAAa,CAAC,cAAc,aAAa,CAAC,CAAC,OAAO,cAAc;EAEtE,IAAI,WAAW,SAAS,GACtB,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QACE,+BAA+B,WAAW,KAAK,OAAO,EAAE;EAG5D;EAOF,IAAI,CAAC,QAAQ,WAAW,OAAO,SAAS,MAAM,GAC5C;EAGF,MAAM,aAAa,CAAC,cAAc,aAAa,CAAC,CAAC,IAAI,aAAa;EAElE,IAAI,WAAW,MAAM,WAAW,WAAW,MAAS,GAClD,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QACE,kDACG,aAAa,KAAK,cAAc;EACvC;EAGF,MAAM,UAAU,CAAC,cAAc,aAAa,CAAC,CAAC,QAAQ,OAAO,UAAU,CAAC,WAAW,MAAM;EAEzF,IAAI,QAAQ,SAAS,GACnB,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ,4DAA4D,QAAQ,KAAK,IAAI;EACvF;EAGF,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ,YAAY,aAAa,eAAe,cAAc;EAChE;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optional-peers.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/optional-peers.check.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { config } from \"../../../../config/config-getter\";\nimport type { CheckStatus, DoctorCheck } from \"../check.types\";\n\n/**\n * A package this project's own configuration requires, and what asked for it.\n */\ntype RequiredPeer = {\n /** The npm package name to probe via `require.resolve`. */\n package: string;\n\n /** The config that asked for it, quoted back to the user verbatim. */\n because: string;\n};\n\n/**\n * Resolve from the project's working directory, not from core's own\n * `node_modules`, so the probe reflects what the *consuming app* has installed.\n */\nconst projectRequire = createRequire(`${process.cwd()}/package.json`);\n\n/**\n * Whether a package can be resolved from the project. A failed resolve (the\n * normal \"not installed\" case) is swallowed and reported as missing.\n */\nfunction isInstalled(packageName: string): boolean {\n try {\n projectRequire.resolve(packageName);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * A non-empty string, or `undefined`. Config values arrive from `env(...)` and\n * an unset variable yields `undefined` — but a `.env` line with nothing after\n * the `=` yields `\"\"`, which is \"not configured\" just as much.\n */\nfunction text(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : undefined;\n}\n\n/**\n * The database driver's package. `driver` is a closed union in cascade\n * (`mongodb | postgres | mysql`), so an unrecognised value means the config is\n * wrong in a way this check is not the right one to report.\n */\nfunction databasePeer(): RequiredPeer | undefined {\n const driver = text(config.get(\"database.driver\"));\n\n const packageName =\n driver === \"mongodb\"\n ? \"mongodb\"\n : driver === \"postgres\"\n ? \"pg\"\n : driver === \"mysql\"\n ? \"mysql2\"\n : undefined;\n\n return packageName ? { package: packageName, because: `database.driver = \"${driver}\"` } : undefined;\n}\n\n/**\n * The selected cache driver. Only the DEFAULT matters: `cache.drivers` is a\n * menu the scaffold fills in for every driver the framework ships, and warning\n * about a menu entry nobody selected is the false positive this whole check was\n * rewritten to remove.\n */\nfunction cachePeer(): RequiredPeer | undefined {\n const selected = text(config.get(\"cache.default\"));\n\n if (!selected || !/redis/i.test(selected)) return undefined;\n\n return { package: \"redis\", because: `cache.default = \"${selected}\"` };\n}\n\n/**\n * The selected storage driver, read through its canonical `driver` field\n * (`local` / `s3` / `r2` / `spaces`) rather than the key the app happened to\n * name it, so a driver called \"cdn\" is still recognised as S3.\n */\nfunction storagePeer(): RequiredPeer | undefined {\n const selected = text(config.get(\"storage.default\"));\n\n if (!selected) return undefined;\n\n const driver = text(config.get(`storage.drivers.${selected}.driver`));\n\n if (!driver || driver === \"local\") return undefined;\n\n return {\n package: \"@aws-sdk/client-s3\",\n because: `storage.default = \"${selected}\" (driver \"${driver}\")`,\n };\n}\n\n/**\n * The mail transport. A `mail` config with no host and no driver is the\n * scaffold's placeholder — every field is an unset env var — so it asks for\n * nothing and this stays quiet.\n */\nfunction mailPeer(): RequiredPeer | undefined {\n const driver = text(config.get(\"mail.driver\"));\n\n if (driver === \"ses\") {\n return { package: \"@aws-sdk/client-sesv2\", because: `mail.driver = \"ses\"` };\n }\n\n const host = text(config.get(\"mail.host\"));\n\n if (!host) return undefined;\n\n return { package: \"nodemailer\", because: `mail.host = \"${host}\"` };\n}\n\n/**\n * Realtime sockets. The connector no-ops without a `socket` config, so the\n * presence of that config is exactly the request for the peer.\n */\nfunction socketPeer(): RequiredPeer | undefined {\n if (!config.get(\"socket\")) return undefined;\n\n return { package: \"socket.io\", because: \"a socket config is present\" };\n}\n\n/**\n * Every peer THIS project's configuration asks for.\n */\nfunction requiredPeers(): RequiredPeer[] {\n return [databasePeer(), cachePeer(), storagePeer(), mailPeer(), socketPeer()].filter(\n (peer): peer is RequiredPeer => Boolean(peer),\n );\n}\n\n/**\n * Checks that every optional peer the project's own configuration selects is\n * actually installed.\n *\n * NEEDS NO BOOTED APP — it reads config and resolves module paths. It runs after\n * the boot pass only because config is loaded by then.\n *\n * WHAT THIS REPLACED, AND WHY: the previous version probed nine packages and\n * warned about every one that was absent. Every one of them is an OPTIONAL\n * peer — a fresh app installs none of them by design — so the check warned, at\n * length, about the framework's own default state. \"No MySQL driver installed\"\n * is not a diagnosis for someone who never wanted MySQL; it is noise that\n * teaches the reader to skim past the line where a real finding will one day\n * appear.\n *\n * Inverted, it becomes a genuine one: a project whose config selects a driver\n * whose package is missing is broken — it will throw the first time that\n * feature is used — so a missing peer here is a `fail`, not a warning.\n */\nexport const optionalPeersCheck: DoctorCheck = {\n name: \"optional-peers\",\n run: () => {\n const required = requiredPeers();\n\n if (required.length === 0) {\n return undefined;\n }\n\n const missing = required.filter((peer) => !isInstalled(peer.package));\n\n const status: CheckStatus = missing.length > 0 ? \"fail\" : \"ok\";\n\n if (missing.length > 0) {\n return {\n name: \"optional-peers\",\n status,\n detail:\n `${missing.length} configured feature(s) have no driver installed:\\n` +\n missing.map((peer) => ` - ${peer.package} — required by ${peer.because}`).join(\"\\n\"),\n };\n }\n\n return {\n name: \"optional-peers\",\n status,\n detail: `every configured driver is installed (${required\n .map((peer) => peer.package)\n .join(\", \")})`,\n };\n },\n};\n"],"mappings":";;;;;;;;AAmBA,MAAM,iBAAiB,cAAc,GAAG,QAAQ,IAAI,EAAE,cAAc;;;;;AAMpE,SAAS,YAAY,aAA8B;CACjD,IAAI;EACF,eAAe,QAAQ,WAAW;EAClC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;AAOA,SAAS,KAAK,OAAoC;CAChD,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"optional-peers.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/optional-peers.check.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport { config } from \"../../../../config/config-getter\";\nimport type { CheckStatus, DoctorCheck } from \"../check.types\";\n\n/**\n * A package this project's own configuration requires, and what asked for it.\n */\ntype RequiredPeer = {\n /** The npm package name to probe via `require.resolve`. */\n package: string;\n\n /** The config that asked for it, quoted back to the user verbatim. */\n because: string;\n};\n\n/**\n * Resolve from the project's working directory, not from core's own\n * `node_modules`, so the probe reflects what the *consuming app* has installed.\n */\nconst projectRequire = createRequire(`${process.cwd()}/package.json`);\n\n/**\n * Whether a package can be resolved from the project. A failed resolve (the\n * normal \"not installed\" case) is swallowed and reported as missing.\n */\nfunction isInstalled(packageName: string): boolean {\n try {\n projectRequire.resolve(packageName);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * A non-empty string, or `undefined`. Config values arrive from `env(...)` and\n * an unset variable yields `undefined` — but a `.env` line with nothing after\n * the `=` yields `\"\"`, which is \"not configured\" just as much.\n */\nfunction text(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : undefined;\n}\n\n/**\n * The database driver's package. `driver` is a closed union in cascade\n * (`mongodb | postgres | mysql`), so an unrecognised value means the config is\n * wrong in a way this check is not the right one to report.\n */\nfunction databasePeer(): RequiredPeer | undefined {\n const driver = text(config.get(\"database.driver\"));\n\n const packageName =\n driver === \"mongodb\"\n ? \"mongodb\"\n : driver === \"postgres\"\n ? \"pg\"\n : driver === \"mysql\"\n ? \"mysql2\"\n : undefined;\n\n return packageName ? { package: packageName, because: `database.driver = \"${driver}\"` } : undefined;\n}\n\n/**\n * The selected cache driver. Only the DEFAULT matters: `cache.drivers` is a\n * menu the scaffold fills in for every driver the framework ships, and warning\n * about a menu entry nobody selected is the false positive this whole check was\n * rewritten to remove.\n */\nfunction cachePeer(): RequiredPeer | undefined {\n const selected = text(config.get(\"cache.default\"));\n\n if (!selected || !/redis/i.test(selected)) return undefined;\n\n return { package: \"redis\", because: `cache.default = \"${selected}\"` };\n}\n\n/**\n * The selected storage driver, read through its canonical `driver` field\n * (`local` / `s3` / `r2` / `spaces`) rather than the key the app happened to\n * name it, so a driver called \"cdn\" is still recognised as S3.\n */\nfunction storagePeer(): RequiredPeer | undefined {\n const selected = text(config.get(\"storage.default\"));\n\n if (!selected) return undefined;\n\n const driver = text(config.get(`storage.drivers.${selected}.driver`));\n\n if (!driver || driver === \"local\") return undefined;\n\n return {\n package: \"@aws-sdk/client-s3\",\n because: `storage.default = \"${selected}\" (driver \"${driver}\")`,\n };\n}\n\n/**\n * The mail transport. A `mail` config with no host and no driver is the\n * scaffold's placeholder — every field is an unset env var — so it asks for\n * nothing and this stays quiet.\n */\nfunction mailPeer(): RequiredPeer | undefined {\n const driver = text(config.get(\"mail.driver\"));\n\n if (driver === \"ses\") {\n return { package: \"@aws-sdk/client-sesv2\", because: `mail.driver = \"ses\"` };\n }\n\n const host = text(config.get(\"mail.host\"));\n\n if (!host) return undefined;\n\n return { package: \"nodemailer\", because: `mail.host = \"${host}\"` };\n}\n\n/**\n * Realtime sockets. The connector no-ops without a `socket` config, so the\n * presence of that config is exactly the request for the peer.\n */\nfunction socketPeer(): RequiredPeer | undefined {\n if (!config.get(\"socket\")) return undefined;\n\n return { package: \"socket.io\", because: \"a socket config is present\" };\n}\n\n/**\n * Every peer THIS project's configuration asks for.\n */\nfunction requiredPeers(): RequiredPeer[] {\n return [databasePeer(), cachePeer(), storagePeer(), mailPeer(), socketPeer()].filter(\n (peer): peer is RequiredPeer => Boolean(peer),\n );\n}\n\n/**\n * Checks that every optional peer the project's own configuration selects is\n * actually installed.\n *\n * NEEDS NO BOOTED APP — it reads config and resolves module paths. It runs after\n * the boot pass only because config is loaded by then.\n *\n * WHAT THIS REPLACED, AND WHY: the previous version probed nine packages and\n * warned about every one that was absent. Every one of them is an OPTIONAL\n * peer — a fresh app installs none of them by design — so the check warned, at\n * length, about the framework's own default state. \"No MySQL driver installed\"\n * is not a diagnosis for someone who never wanted MySQL; it is noise that\n * teaches the reader to skim past the line where a real finding will one day\n * appear.\n *\n * Inverted, it becomes a genuine one: a project whose config selects a driver\n * whose package is missing is broken — it will throw the first time that\n * feature is used — so a missing peer here is a `fail`, not a warning.\n */\nexport const optionalPeersCheck: DoctorCheck = {\n name: \"optional-peers\",\n run: () => {\n const required = requiredPeers();\n\n if (required.length === 0) {\n return undefined;\n }\n\n const missing = required.filter((peer) => !isInstalled(peer.package));\n\n const status: CheckStatus = missing.length > 0 ? \"fail\" : \"ok\";\n\n if (missing.length > 0) {\n return {\n name: \"optional-peers\",\n status,\n detail:\n `${missing.length} configured feature(s) have no driver installed:\\n` +\n missing.map((peer) => ` - ${peer.package} — required by ${peer.because}`).join(\"\\n\"),\n };\n }\n\n return {\n name: \"optional-peers\",\n status,\n detail: `every configured driver is installed (${required\n .map((peer) => peer.package)\n .join(\", \")})`,\n };\n },\n};\n"],"mappings":";;;;;;;;AAmBA,MAAM,iBAAiB,cAAc,GAAG,QAAQ,IAAI,EAAE,cAAc;;;;;AAMpE,SAAS,YAAY,aAA8B;CACjD,IAAI;EACF,eAAe,QAAQ,WAAW;EAClC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;AAOA,SAAS,KAAK,OAAoC;CAChD,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS,IAAI,MAAM,KAAK,IAAI;AAC/E;;;;;;AAOA,SAAS,eAAyC;CAChD,MAAM,SAAS,KAAK,OAAO,IAAI,iBAAiB,CAAC;CAEjD,MAAM,cACJ,WAAW,YACP,YACA,WAAW,aACT,OACA,WAAW,UACT,WACA;CAEV,OAAO,cAAc;EAAE,SAAS;EAAa,SAAS,sBAAsB,OAAO;CAAG,IAAI;AAC5F;;;;;;;AAQA,SAAS,YAAsC;CAC7C,MAAM,WAAW,KAAK,OAAO,IAAI,eAAe,CAAC;CAEjD,IAAI,CAAC,YAAY,CAAC,SAAS,KAAK,QAAQ,GAAG,OAAO;CAElD,OAAO;EAAE,SAAS;EAAS,SAAS,oBAAoB,SAAS;CAAG;AACtE;;;;;;AAOA,SAAS,cAAwC;CAC/C,MAAM,WAAW,KAAK,OAAO,IAAI,iBAAiB,CAAC;CAEnD,IAAI,CAAC,UAAU,OAAO;CAEtB,MAAM,SAAS,KAAK,OAAO,IAAI,mBAAmB,SAAS,QAAQ,CAAC;CAEpE,IAAI,CAAC,UAAU,WAAW,SAAS,OAAO;CAE1C,OAAO;EACL,SAAS;EACT,SAAS,sBAAsB,SAAS,aAAa,OAAO;CAC9D;AACF;;;;;;AAOA,SAAS,WAAqC;CAG5C,IAFe,KAAK,OAAO,IAAI,aAAa,CAEnC,MAAM,OACb,OAAO;EAAE,SAAS;EAAyB,SAAS;CAAsB;CAG5E,MAAM,OAAO,KAAK,OAAO,IAAI,WAAW,CAAC;CAEzC,IAAI,CAAC,MAAM,OAAO;CAElB,OAAO;EAAE,SAAS;EAAc,SAAS,gBAAgB,KAAK;CAAG;AACnE;;;;;AAMA,SAAS,aAAuC;CAC9C,IAAI,CAAC,OAAO,IAAI,QAAQ,GAAG,OAAO;CAElC,OAAO;EAAE,SAAS;EAAa,SAAS;CAA6B;AACvE;;;;AAKA,SAAS,gBAAgC;CACvC,OAAO;EAAC,aAAa;EAAG,UAAU;EAAG,YAAY;EAAG,SAAS;EAAG,WAAW;CAAC,CAAC,CAAC,QAC3E,SAA+B,QAAQ,IAAI,CAC9C;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,qBAAkC;CAC7C,MAAM;CACN,WAAW;EACT,MAAM,WAAW,cAAc;EAE/B,IAAI,SAAS,WAAW,GACtB;EAGF,MAAM,UAAU,SAAS,QAAQ,SAAS,CAAC,YAAY,KAAK,OAAO,CAAC;EAEpE,MAAM,SAAsB,QAAQ,SAAS,IAAI,SAAS;EAE1D,IAAI,QAAQ,SAAS,GACnB,OAAO;GACL,MAAM;GACN;GACA,QACE,GAAG,QAAQ,OAAO,sDAClB,QAAQ,KAAK,SAAS,OAAO,KAAK,QAAQ,iBAAiB,KAAK,SAAS,CAAC,CAAC,KAAK,IAAI;EACxF;EAGF,OAAO;GACL,MAAM;GACN;GACA,QAAQ,yCAAyC,SAC9C,KAAK,SAAS,KAAK,OAAO,CAAC,CAC3B,KAAK,IAAI,EAAE;EAChB;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/routes.check.ts"],"sourcesContent":["import type { DoctorBootContext, DoctorCheck } from \"../check.types\";\n\n/**\n * Describe the failed modules as an indented block, one per line, cause first.\n * The file that failed is the only thing that makes this actionable, so it is\n * never summarised away.\n */\nfunction describeFailures(context: DoctorBootContext): string {\n return context.moduleFailures\n .map((failure) => ` - ${failure.file}: ${failure.message}`)\n .join(\"\\n\");\n}\n\n/**\n * Reports the route surface the app will actually serve.\n *\n * NEEDS A BOOTED APP — and now gets one. Routes exist only as the side effect of\n * importing the module that declares them, so a doctor that did not import app\n * code reported `0 routes` for an app the dev server served fourteen routes\n * for. The boot pass imports the same modules, in the same order, and boots\n * (never starts) the same connectors, so the number printed here is the number\n * `warlock dev` prints for the same project — the two are read off the same\n * router after the same registrations.\n *\n * Verdicts:\n * - a module failed to import → `fail`, naming every file and its cause. This\n * is the finding doctor exists for: a route file that throws on import is\n * invisible at runtime, it just 404s;\n * - route modules on disk but nothing registered → `fail` (they loaded and\n * registered nothing, which is not a state a working app is in);\n * - no route modules at all → `ok`, stated plainly. A worker or CLI-only app is\n * a legitimate shape and must not be nagged;\n * - otherwise → `ok` with the count, split into what app code registered and\n * what connectors added.\n */\nexport const routesCheck: DoctorCheck = {\n name: \"routes\",\n run: (context) => {\n if (context.moduleFailures.length > 0) {\n const count = context.moduleFailures.length;\n\n return {\n name: \"routes\",\n status: \"fail\",\n detail:\n `${count} app module(s) failed to load — the routes they declare will 404:\\n` +\n describeFailures(context),\n };\n }\n\n if (!context.booted) {\n return {\n name: \"routes\",\n status: \"fail\",\n detail: \"app modules could not be discovered — nothing to report on\",\n };\n }\n\n if (context.totalRoutes === 0) {\n if (context.routeModules === 0) {\n return {\n name: \"routes\",\n status: \"ok\",\n detail: \"no route modules found (src/app/*/routes.ts) — this app serves no HTTP routes\",\n };\n }\n\n return {\n name: \"routes\",\n status: \"fail\",\n detail:\n `${context.routeModules} route module(s) loaded without error but registered 0 routes — ` +\n \"every request will 404\",\n };\n }\n\n const fromConnectors = context.totalRoutes - context.appRoutes;\n const breakdown =\n fromConnectors > 0\n ? `${context.appRoutes} from ${context.routeModules} route module(s), ` +\n `${fromConnectors} from connectors`\n : `from ${context.routeModules} route module(s)`;\n\n return {\n name: \"routes\",\n status: \"ok\",\n detail: `${context.totalRoutes} registered (${breakdown})`,\n };\n },\n};\n"],"mappings":";;;;;;AAOA,SAAS,iBAAiB,SAAoC;CAC5D,OAAO,QAAQ,eACZ,KAAK,YAAY,OAAO,QAAQ,KAAK,IAAI,QAAQ,SAAS,
|
|
1
|
+
{"version":3,"file":"routes.check.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/doctor/checks/routes.check.ts"],"sourcesContent":["import type { DoctorBootContext, DoctorCheck } from \"../check.types\";\n\n/**\n * Describe the failed modules as an indented block, one per line, cause first.\n * The file that failed is the only thing that makes this actionable, so it is\n * never summarised away.\n */\nfunction describeFailures(context: DoctorBootContext): string {\n return context.moduleFailures\n .map((failure) => ` - ${failure.file}: ${failure.message}`)\n .join(\"\\n\");\n}\n\n/**\n * Reports the route surface the app will actually serve.\n *\n * NEEDS A BOOTED APP — and now gets one. Routes exist only as the side effect of\n * importing the module that declares them, so a doctor that did not import app\n * code reported `0 routes` for an app the dev server served fourteen routes\n * for. The boot pass imports the same modules, in the same order, and boots\n * (never starts) the same connectors, so the number printed here is the number\n * `warlock dev` prints for the same project — the two are read off the same\n * router after the same registrations.\n *\n * Verdicts:\n * - a module failed to import → `fail`, naming every file and its cause. This\n * is the finding doctor exists for: a route file that throws on import is\n * invisible at runtime, it just 404s;\n * - route modules on disk but nothing registered → `fail` (they loaded and\n * registered nothing, which is not a state a working app is in);\n * - no route modules at all → `ok`, stated plainly. A worker or CLI-only app is\n * a legitimate shape and must not be nagged;\n * - otherwise → `ok` with the count, split into what app code registered and\n * what connectors added.\n */\nexport const routesCheck: DoctorCheck = {\n name: \"routes\",\n run: (context) => {\n if (context.moduleFailures.length > 0) {\n const count = context.moduleFailures.length;\n\n return {\n name: \"routes\",\n status: \"fail\",\n detail:\n `${count} app module(s) failed to load — the routes they declare will 404:\\n` +\n describeFailures(context),\n };\n }\n\n if (!context.booted) {\n return {\n name: \"routes\",\n status: \"fail\",\n detail: \"app modules could not be discovered — nothing to report on\",\n };\n }\n\n if (context.totalRoutes === 0) {\n if (context.routeModules === 0) {\n return {\n name: \"routes\",\n status: \"ok\",\n detail: \"no route modules found (src/app/*/routes.ts) — this app serves no HTTP routes\",\n };\n }\n\n return {\n name: \"routes\",\n status: \"fail\",\n detail:\n `${context.routeModules} route module(s) loaded without error but registered 0 routes — ` +\n \"every request will 404\",\n };\n }\n\n const fromConnectors = context.totalRoutes - context.appRoutes;\n const breakdown =\n fromConnectors > 0\n ? `${context.appRoutes} from ${context.routeModules} route module(s), ` +\n `${fromConnectors} from connectors`\n : `from ${context.routeModules} route module(s)`;\n\n return {\n name: \"routes\",\n status: \"ok\",\n detail: `${context.totalRoutes} registered (${breakdown})`,\n };\n },\n};\n"],"mappings":";;;;;;AAOA,SAAS,iBAAiB,SAAoC;CAC5D,OAAO,QAAQ,eACZ,KAAK,YAAY,OAAO,QAAQ,KAAK,IAAI,QAAQ,SAAS,CAAC,CAC3D,KAAK,IAAI;AACd;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,cAA2B;CACtC,MAAM;CACN,MAAM,YAAY;EAChB,IAAI,QAAQ,eAAe,SAAS,GAGlC,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QACE,GANU,QAAQ,eAAe,OAMxB,uEACT,iBAAiB,OAAO;EAC5B;EAGF,IAAI,CAAC,QAAQ,QACX,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ;EACV;EAGF,IAAI,QAAQ,gBAAgB,GAAG;GAC7B,IAAI,QAAQ,iBAAiB,GAC3B,OAAO;IACL,MAAM;IACN,QAAQ;IACR,QAAQ;GACV;GAGF,OAAO;IACL,MAAM;IACN,QAAQ;IACR,QACE,GAAG,QAAQ,aAAa;GAE5B;EACF;EAEA,MAAM,iBAAiB,QAAQ,cAAc,QAAQ;EACrD,MAAM,YACJ,iBAAiB,IACb,GAAG,QAAQ,UAAU,QAAQ,QAAQ,aAAa,oBAC/C,eAAe,oBAClB,QAAQ,QAAQ,aAAa;EAEnC,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ,GAAG,QAAQ,YAAY,eAAe,UAAU;EAC1D;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.command.mjs","names":[],"sources":["../../../../../../../../../core/src/cli/commands/generate/generate.command.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { command } from \"../../../commands/cli-command\";\r\nimport type { CommandActionData } from \"../../../commands/types\";\r\nimport { generateController } from \"./generators/controller.generator\";\r\nimport { generateMigration } from \"./generators/migration.generator\";\r\nimport { generateModel } from \"./generators/model.generator\";\r\nimport { generateModule } from \"./generators/module.generator\";\r\nimport { generateRepository } from \"./generators/repository.generator\";\r\nimport { generateResource } from \"./generators/resource.generator\";\r\nimport { generateService } from \"./generators/service.generator\";\r\n\r\nconst generators: Record<string, (data: CommandActionData) => Promise<void>> = {\r\n module: generateModule,\r\n controller: generateController,\r\n service: generateService,\r\n model: generateModel,\r\n migration: generateMigration,\r\n repository: generateRepository,\r\n resource: generateResource,\r\n};\r\n\r\nasync function generateCommandAction(data: CommandActionData) {\r\n const commandName = data.args[0];\r\n\r\n if (!commandName) {\r\n console.log(colors.red(\"Error: Generator name is required\"));\r\n console.log(colors.yellow(\"\\nAvailable generators:\"));\r\n Object.keys(generators).forEach((cmd) => {\r\n console.log(colors.cyan(` - ${cmd}`));\r\n });\r\n console.log(colors.yellow(\"\\nUsage: warlock generate <generator> <name> [options]\"));\r\n process.exit(1);\r\n }\r\n\r\n const generator = generators[commandName];\r\n\r\n if (!generator) {\r\n console.log(colors.red(`Error: Unknown generator \"${commandName}\"`));\r\n console.log(colors.yellow(\"\\nAvailable generators:\"));\r\n Object.keys(generators).forEach((cmd) => {\r\n console.log(colors.cyan(` - ${cmd}`));\r\n });\r\n process.exit(1);\r\n }\r\n\r\n // Remove the command name from args for the generator\r\n const generatorData: CommandActionData = {\r\n ...data,\r\n args: data.args.slice(1),\r\n };\r\n\r\n await generator(generatorData);\r\n}\r\n\r\n// Master generate command\r\nexport const generateCommand = command({\r\n name: \"generate <generator> [args...]\",\r\n alias: \"g\", // Allows 'warlock g module ...'\r\n description: \"Scaffold a new component\",\r\n action: generateCommandAction,\r\n options: [\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\n// Individual commands\r\nexport const generateModuleCommand = command({\r\n name: \"generate.module <name>\",\r\n alias: \"gen.m\",\r\n description: \"Generate a new module\",\r\n action: generateModule,\r\n options: [\r\n {\r\n text: \"--minimal, -m\",\r\n description:\r\n \"Generate a minimal skeleton (routes, main, locales, empty subfolders) instead of the full CRUD scaffold\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateControllerCommand = command({\r\n name: \"generate.controller <module>/<name>\",\r\n alias: \"gen.c\",\r\n description: \"Generate a new controller\",\r\n action: generateController,\r\n options: [\r\n {\r\n text: \"--with-validation, -v\",\r\n description: \"Generate a validation schema and bind it to the controller\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateServiceCommand = command({\r\n name: \"generate.service <module>/<name>\",\r\n alias: \"gen.s\",\r\n description: \"Generate a new service\",\r\n action: generateService,\r\n options: [\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateModelCommand = command({\r\n name: \"generate.model <module>/<name>\",\r\n alias: \"gen.md\",\r\n description: \"Generate a new model with migration\",\r\n action: generateModel,\r\n options: [\r\n {\r\n text: \"--with-resource, -rs\",\r\n description: \"Generate resource\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--table <name>\",\r\n description: \"Specify table name\",\r\n },\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--timestamps [bool]\",\r\n description: \"Include timestamps in migration (default: true)\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateRepositoryCommand = command({\r\n name: \"generate.repository <module>/<name>\",\r\n alias: \"gen.r\",\r\n description: \"Generate a new repository\",\r\n action: generateRepository,\r\n options: [\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateResourceCommand = command({\r\n name: \"generate.resource <module>/<name>\",\r\n alias: \"gen.rs\",\r\n description: \"Generate a new resource\",\r\n action: generateResource,\r\n options: [\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateMigrationCommand = command({\r\n name: \"generate.migration <model-path>\",\r\n alias: \"gen.mig\",\r\n description: \"Generate a new migration for a model\",\r\n action: generateMigration,\r\n options: [\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--add <columns>\",\r\n description: \"Columns to add (DSL: name:type:modifier,...)\",\r\n },\r\n {\r\n text: \"--drop <columns>\",\r\n description: \"Columns to drop (comma-separated names)\",\r\n },\r\n {\r\n text: \"--rename <columns>\",\r\n description: \"Columns to rename (DSL: old:new,...)\",\r\n },\r\n {\r\n text: \"--timestamps [bool]\",\r\n description: \"Include timestamps (default: true)\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n"],"mappings":";;;;;;;;;;;AAWA,MAAM,aAAyE;CAC7E,QAAQ;CACR,YAAY;CACZ,SAAS;CACT,OAAO;CACP,WAAW;CACX,YAAY;CACZ,UAAU;AACZ;AAEA,eAAe,sBAAsB,MAAyB;CAC5D,MAAM,cAAc,KAAK,KAAK;CAE9B,IAAI,CAAC,aAAa;EAChB,QAAQ,IAAI,OAAO,IAAI,mCAAmC,CAAC;EAC3D,QAAQ,IAAI,OAAO,OAAO,yBAAyB,CAAC;EACpD,OAAO,KAAK,UAAU,EAAE,SAAS,QAAQ;GACvC,QAAQ,IAAI,OAAO,KAAK,OAAO,KAAK,CAAC;EACvC,CAAC;EACD,QAAQ,IAAI,OAAO,OAAO,wDAAwD,CAAC;EACnF,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,YAAY,WAAW;CAE7B,IAAI,CAAC,WAAW;EACd,QAAQ,IAAI,OAAO,IAAI,6BAA6B,YAAY,EAAE,CAAC;EACnE,QAAQ,IAAI,OAAO,OAAO,yBAAyB,CAAC;EACpD,OAAO,KAAK,UAAU,EAAE,SAAS,QAAQ;GACvC,QAAQ,IAAI,OAAO,KAAK,OAAO,KAAK,CAAC;EACvC,CAAC;EACD,QAAQ,KAAK,CAAC;CAChB;CAQA,MAAM,UAAU;EAJd,GAAG;EACH,MAAM,KAAK,KAAK,MAAM,CAAC;CAGG,CAAC;AAC/B;AAGA,MAAa,kBAAkB,QAAQ;CACrC,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;AACF,CAAC;AAGD,MAAa,wBAAwB,QAAQ;CAC3C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS;EACP;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;CACF;AACF,CAAC;AAED,MAAa,4BAA4B,QAAQ;CAC/C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,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;CACF;AACF,CAAC;AAED,MAAa,yBAAyB,QAAQ;CAC5C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;AACF,CAAC;AAED,MAAa,uBAAuB,QAAQ;CAC1C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;EACf;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;AAED,MAAa,4BAA4B,QAAQ;CAC/C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;AACF,CAAC;AAED,MAAa,0BAA0B,QAAQ;CAC7C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;AACF,CAAC;AAED,MAAa,2BAA2B,QAAQ;CAC9C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;CACF;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"generate.command.mjs","names":[],"sources":["../../../../../../../../../core/src/cli/commands/generate/generate.command.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport { command } from \"../../../commands/cli-command\";\r\nimport type { CommandActionData } from \"../../../commands/types\";\r\nimport { generateController } from \"./generators/controller.generator\";\r\nimport { generateMigration } from \"./generators/migration.generator\";\r\nimport { generateModel } from \"./generators/model.generator\";\r\nimport { generateModule } from \"./generators/module.generator\";\r\nimport { generateRepository } from \"./generators/repository.generator\";\r\nimport { generateResource } from \"./generators/resource.generator\";\r\nimport { generateService } from \"./generators/service.generator\";\r\n\r\nconst generators: Record<string, (data: CommandActionData) => Promise<void>> = {\r\n module: generateModule,\r\n controller: generateController,\r\n service: generateService,\r\n model: generateModel,\r\n migration: generateMigration,\r\n repository: generateRepository,\r\n resource: generateResource,\r\n};\r\n\r\nasync function generateCommandAction(data: CommandActionData) {\r\n const commandName = data.args[0];\r\n\r\n if (!commandName) {\r\n console.log(colors.red(\"Error: Generator name is required\"));\r\n console.log(colors.yellow(\"\\nAvailable generators:\"));\r\n Object.keys(generators).forEach((cmd) => {\r\n console.log(colors.cyan(` - ${cmd}`));\r\n });\r\n console.log(colors.yellow(\"\\nUsage: warlock generate <generator> <name> [options]\"));\r\n process.exit(1);\r\n }\r\n\r\n const generator = generators[commandName];\r\n\r\n if (!generator) {\r\n console.log(colors.red(`Error: Unknown generator \"${commandName}\"`));\r\n console.log(colors.yellow(\"\\nAvailable generators:\"));\r\n Object.keys(generators).forEach((cmd) => {\r\n console.log(colors.cyan(` - ${cmd}`));\r\n });\r\n process.exit(1);\r\n }\r\n\r\n // Remove the command name from args for the generator\r\n const generatorData: CommandActionData = {\r\n ...data,\r\n args: data.args.slice(1),\r\n };\r\n\r\n await generator(generatorData);\r\n}\r\n\r\n// Master generate command\r\nexport const generateCommand = command({\r\n name: \"generate <generator> [args...]\",\r\n alias: \"g\", // Allows 'warlock g module ...'\r\n description: \"Scaffold a new component\",\r\n action: generateCommandAction,\r\n options: [\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\n// Individual commands\r\nexport const generateModuleCommand = command({\r\n name: \"generate.module <name>\",\r\n alias: \"gen.m\",\r\n description: \"Generate a new module\",\r\n action: generateModule,\r\n options: [\r\n {\r\n text: \"--minimal, -m\",\r\n description:\r\n \"Generate a minimal skeleton (routes, main, locales, empty subfolders) instead of the full CRUD scaffold\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateControllerCommand = command({\r\n name: \"generate.controller <module>/<name>\",\r\n alias: \"gen.c\",\r\n description: \"Generate a new controller\",\r\n action: generateController,\r\n options: [\r\n {\r\n text: \"--with-validation, -v\",\r\n description: \"Generate a validation schema and bind it to the controller\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateServiceCommand = command({\r\n name: \"generate.service <module>/<name>\",\r\n alias: \"gen.s\",\r\n description: \"Generate a new service\",\r\n action: generateService,\r\n options: [\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateModelCommand = command({\r\n name: \"generate.model <module>/<name>\",\r\n alias: \"gen.md\",\r\n description: \"Generate a new model with migration\",\r\n action: generateModel,\r\n options: [\r\n {\r\n text: \"--with-resource, -rs\",\r\n description: \"Generate resource\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--table <name>\",\r\n description: \"Specify table name\",\r\n },\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--timestamps [bool]\",\r\n description: \"Include timestamps in migration (default: true)\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateRepositoryCommand = command({\r\n name: \"generate.repository <module>/<name>\",\r\n alias: \"gen.r\",\r\n description: \"Generate a new repository\",\r\n action: generateRepository,\r\n options: [\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateResourceCommand = command({\r\n name: \"generate.resource <module>/<name>\",\r\n alias: \"gen.rs\",\r\n description: \"Generate a new resource\",\r\n action: generateResource,\r\n options: [\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n\r\nexport const generateMigrationCommand = command({\r\n name: \"generate.migration <model-path>\",\r\n alias: \"gen.mig\",\r\n description: \"Generate a new migration for a model\",\r\n action: generateMigration,\r\n options: [\r\n {\r\n text: \"--force, -f\",\r\n description: \"Overwrite existing files\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--dry-run\",\r\n description: \"Preview the files that would be generated without writing anything\",\r\n type: \"boolean\",\r\n },\r\n {\r\n text: \"--add <columns>\",\r\n description: \"Columns to add (DSL: name:type:modifier,...)\",\r\n },\r\n {\r\n text: \"--drop <columns>\",\r\n description: \"Columns to drop (comma-separated names)\",\r\n },\r\n {\r\n text: \"--rename <columns>\",\r\n description: \"Columns to rename (DSL: old:new,...)\",\r\n },\r\n {\r\n text: \"--timestamps [bool]\",\r\n description: \"Include timestamps (default: true)\",\r\n type: \"boolean\",\r\n },\r\n ],\r\n});\r\n"],"mappings":";;;;;;;;;;;AAWA,MAAM,aAAyE;CAC7E,QAAQ;CACR,YAAY;CACZ,SAAS;CACT,OAAO;CACP,WAAW;CACX,YAAY;CACZ,UAAU;AACZ;AAEA,eAAe,sBAAsB,MAAyB;CAC5D,MAAM,cAAc,KAAK,KAAK;CAE9B,IAAI,CAAC,aAAa;EAChB,QAAQ,IAAI,OAAO,IAAI,mCAAmC,CAAC;EAC3D,QAAQ,IAAI,OAAO,OAAO,yBAAyB,CAAC;EACpD,OAAO,KAAK,UAAU,CAAC,CAAC,SAAS,QAAQ;GACvC,QAAQ,IAAI,OAAO,KAAK,OAAO,KAAK,CAAC;EACvC,CAAC;EACD,QAAQ,IAAI,OAAO,OAAO,wDAAwD,CAAC;EACnF,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,YAAY,WAAW;CAE7B,IAAI,CAAC,WAAW;EACd,QAAQ,IAAI,OAAO,IAAI,6BAA6B,YAAY,EAAE,CAAC;EACnE,QAAQ,IAAI,OAAO,OAAO,yBAAyB,CAAC;EACpD,OAAO,KAAK,UAAU,CAAC,CAAC,SAAS,QAAQ;GACvC,QAAQ,IAAI,OAAO,KAAK,OAAO,KAAK,CAAC;EACvC,CAAC;EACD,QAAQ,KAAK,CAAC;CAChB;CAQA,MAAM,UAAU;EAJd,GAAG;EACH,MAAM,KAAK,KAAK,MAAM,CAAC;CAGG,CAAC;AAC/B;AAGA,MAAa,kBAAkB,QAAQ;CACrC,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;AACF,CAAC;AAGD,MAAa,wBAAwB,QAAQ;CAC3C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS;EACP;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;CACF;AACF,CAAC;AAED,MAAa,4BAA4B,QAAQ;CAC/C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,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;CACF;AACF,CAAC;AAED,MAAa,yBAAyB,QAAQ;CAC5C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;AACF,CAAC;AAED,MAAa,uBAAuB,QAAQ;CAC1C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;EACf;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;AAED,MAAa,4BAA4B,QAAQ;CAC/C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;AACF,CAAC;AAED,MAAa,0BAA0B,QAAQ;CAC7C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS,CACP;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,GACA;EACE,MAAM;EACN,aAAa;EACb,MAAM;CACR,CACF;AACF,CAAC;AAED,MAAa,2BAA2B,QAAQ;CAC9C,MAAM;CACN,OAAO;CACP,aAAa;CACb,QAAQ;CACR,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;CACF;AACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column-dsl-parser.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/column-dsl-parser.ts"],"sourcesContent":["export type ParsedColumn = {\n name: string;\n helper: string; // e.g. \"text\", \"boolCol\", \"decimal\"\n modifiers: string[]; // e.g. [\".nullable()\", \".unsigned()\"]\n};\n\nconst typeMapping: Record<string, string> = {\n text: \"text\",\n string: \"string\",\n integer: \"integer\",\n int: \"integer\",\n bigInteger: \"bigInteger\",\n bigInt: \"bigInteger\",\n boolean: \"boolCol\",\n bool: \"boolCol\",\n uuid: \"uuid\",\n timestamp: \"timestamp\",\n date: \"date\",\n json: \"json\",\n object: \"objectCol\",\n decimal: \"decimal\",\n float: \"float\",\n enum: \"enumCol\",\n set: \"setCol\",\n blob: \"blobCol\",\n binary: \"blobCol\",\n};\n\n/**\n * Parses a subset DSL for column schemas:\n * \"phone:text:nullable,price:decimal:notNullable:unsigned\"\n */\nexport function parseColumnDsl(input: string): ParsedColumn[] {\n if (!input) return [];\n\n const columns = input.split(\",\").map((c) => c.trim()).filter(Boolean);\n \n return columns.map((colStr) => {\n const parts = colStr.split(\":\").map((p) => p.trim());\n const name = parts[0];\n const rawType = parts[1] || \"string\";\n const helper = typeMapping[rawType] || rawType; // fallback to raw string if not mapped\n\n const modifiers: string[] = [];\n for (let i = 2; i < parts.length; i++) {\n let modifier = parts[i];\n // Basic modifiers that translates to builder calls\n modifiers.push(`.${modifier}()`);\n }\n\n return {\n name,\n helper,\n modifiers,\n };\n });\n}\n"],"mappings":";AAMA,MAAM,cAAsC;CAC1C,MAAM;CACN,QAAQ;CACR,SAAS;CACT,KAAK;CACL,YAAY;CACZ,QAAQ;CACR,SAAS;CACT,MAAM;CACN,MAAM;CACN,WAAW;CACX,MAAM;CACN,MAAM;CACN,QAAQ;CACR,SAAS;CACT,OAAO;CACP,MAAM;CACN,KAAK;CACL,MAAM;CACN,QAAQ;AACV;;;;;AAMA,SAAgB,eAAe,OAA+B;CAC5D,IAAI,CAAC,OAAO,OAAO,CAAC;CAIpB,OAFgB,MAAM,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"column-dsl-parser.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/column-dsl-parser.ts"],"sourcesContent":["export type ParsedColumn = {\n name: string;\n helper: string; // e.g. \"text\", \"boolCol\", \"decimal\"\n modifiers: string[]; // e.g. [\".nullable()\", \".unsigned()\"]\n};\n\nconst typeMapping: Record<string, string> = {\n text: \"text\",\n string: \"string\",\n integer: \"integer\",\n int: \"integer\",\n bigInteger: \"bigInteger\",\n bigInt: \"bigInteger\",\n boolean: \"boolCol\",\n bool: \"boolCol\",\n uuid: \"uuid\",\n timestamp: \"timestamp\",\n date: \"date\",\n json: \"json\",\n object: \"objectCol\",\n decimal: \"decimal\",\n float: \"float\",\n enum: \"enumCol\",\n set: \"setCol\",\n blob: \"blobCol\",\n binary: \"blobCol\",\n};\n\n/**\n * Parses a subset DSL for column schemas:\n * \"phone:text:nullable,price:decimal:notNullable:unsigned\"\n */\nexport function parseColumnDsl(input: string): ParsedColumn[] {\n if (!input) return [];\n\n const columns = input.split(\",\").map((c) => c.trim()).filter(Boolean);\n \n return columns.map((colStr) => {\n const parts = colStr.split(\":\").map((p) => p.trim());\n const name = parts[0];\n const rawType = parts[1] || \"string\";\n const helper = typeMapping[rawType] || rawType; // fallback to raw string if not mapped\n\n const modifiers: string[] = [];\n for (let i = 2; i < parts.length; i++) {\n let modifier = parts[i];\n // Basic modifiers that translates to builder calls\n modifiers.push(`.${modifier}()`);\n }\n\n return {\n name,\n helper,\n modifiers,\n };\n });\n}\n"],"mappings":";AAMA,MAAM,cAAsC;CAC1C,MAAM;CACN,QAAQ;CACR,SAAS;CACT,KAAK;CACL,YAAY;CACZ,QAAQ;CACR,SAAS;CACT,MAAM;CACN,MAAM;CACN,WAAW;CACX,MAAM;CACN,MAAM;CACN,QAAQ;CACR,SAAS;CACT,OAAO;CACP,MAAM;CACN,KAAK;CACL,MAAM;CACN,QAAQ;AACV;;;;;AAMA,SAAgB,eAAe,OAA+B;CAC5D,IAAI,CAAC,OAAO,OAAO,CAAC;CAIpB,OAFgB,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,OAEhD,CAAC,CAAC,KAAK,WAAW;EAC7B,MAAM,QAAQ,OAAO,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK,CAAC;EACnD,MAAM,OAAO,MAAM;EACnB,MAAM,UAAU,MAAM,MAAM;EAC5B,MAAM,SAAS,YAAY,YAAY;EAEvC,MAAM,YAAsB,CAAC;EAC7B,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,IAAI,WAAW,MAAM;GAErB,UAAU,KAAK,IAAI,SAAS,GAAG;EACjC;EAEA,OAAO;GACL;GACA;GACA;EACF;CACF,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/migration.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport path from \"node:path\";\r\nimport { appPath } from \"../../../../utils\";\r\nimport type { CommandActionData } from \"../../../../commands/types\";\r\nimport { migrationAlterStub, migrationStub } from \"../templates/stubs\";\r\nimport { parseColumnDsl } from \"./column-dsl-parser\";\r\nimport { parseName } from \"../utils/name-parser\";\r\nimport { ensureDirectoryAsync, putFileAsync, setDryRun } from \"../utils/writer\";\r\n\r\n/**\r\n * Generate a migration file for a model\r\n */\r\n/**\r\n * Create a migration file for a model\r\n */\r\nexport async function createMigrationFile(moduleName: string, entityName: string, options: any = {}) {\r\n const entity = parseName(entityName);\r\n\r\n // Generate timestamp: MM-DD-YYYY_HH-MM-SS\r\n const now = new Date();\r\n const timestamp = `${String(now.getMonth() + 1).padStart(2, \"0\")}-${String(now.getDate()).padStart(2, \"0\")}-${now.getFullYear()}_${String(now.getHours()).padStart(2, \"0\")}-${String(now.getMinutes()).padStart(2, \"0\")}-${String(now.getSeconds()).padStart(2, \"0\")}`;\r\n\r\n const migrationFileName = `${timestamp}-${entity.kebab}.migration.ts`;\r\n const migrationsPath = path.join(appPath(), moduleName, \"models\", entity.kebab, \"migrations\");\r\n\r\n // Ensure migrations directory exists\r\n await ensureDirectoryAsync(migrationsPath);\r\n\r\n const addParams = options.add as string;\r\n const dropParams = options.drop as string;\r\n const renameParams = options.rename as string;\r\n const timestamps = options.timestamps !== \"false\" && options.timestamps !== false;\r\n\r\n let migrationContent = \"\";\r\n\r\n if (addParams || dropParams || renameParams) {\r\n // Generate alter stub\r\n const parsedAdd = parseColumnDsl(addParams || \"\");\r\n const helpersSet = new Set<string>();\r\n \r\n const addLines = parsedAdd.map((col) => {\r\n helpersSet.add(col.helper);\r\n return ` ${col.name}: ${col.helper}()${col.modifiers.join(\"\")},`;\r\n });\r\n\r\n let formattedDrop: string | undefined = undefined;\r\n if (dropParams) {\r\n formattedDrop = JSON.stringify(dropParams.split(\",\").map((s) => s.trim()));\r\n }\r\n\r\n let formattedRename: string | undefined = undefined;\r\n if (renameParams) {\r\n const obj: Record<string, string> = {};\r\n renameParams.split(\",\").map((s) => s.trim()).forEach((p) => {\r\n const [oldN, newN] = p.split(\":\").map((s) => s.trim());\r\n if (oldN && newN) obj[oldN] = newN;\r\n });\r\n formattedRename = JSON.stringify(obj, null, 2).replace(/\\n/g, '\\n ');\r\n }\r\n\r\n migrationContent = migrationAlterStub(entity, {\r\n add: addLines.length > 0 ? addLines.join(\"\\n\") : undefined,\r\n drop: formattedDrop,\r\n rename: formattedRename,\r\n imports: Array.from(helpersSet),\r\n });\r\n } else {\r\n // Generate create stub\r\n migrationContent = migrationStub(entity, {\r\n timestamps,\r\n });\r\n }\r\n\r\n // Create migration file\r\n const migrationFilePath = path.join(migrationsPath, migrationFileName);\r\n await putFileAsync(migrationFilePath, migrationContent);\r\n\r\n return migrationFilePath;\r\n}\r\n\r\n/**\r\n * Generate a migration file for a model\r\n */\r\nexport async function generateMigration(data: CommandActionData) {\r\n const modelPath = data.args[0] as string;\r\n\r\n if (!modelPath) {\r\n console.log(colors.red(\"Error: Model path is required\"));\r\n console.log(colors.gray(\"Usage: warlock gen.migration <model-path>\"));\r\n console.log(colors.gray(\"Example: warlock gen.migration products/product\"));\r\n return;\r\n }\r\n\r\n // Parse model path (e.g., \"products/product\")\r\n const [moduleName, entityName] = modelPath.split(\"/\");\r\n\r\n if (!moduleName || !entityName) {\r\n console.log(colors.red(\"Error: Invalid model path format. Expected: <module>/<entity>\"));\r\n console.log(colors.gray(\"Example: warlock gen.migration products/product\"));\r\n return;\r\n }\r\n\r\n setDryRun(Boolean(data.options.dryRun));\r\n\r\n const migrationFilePath = await createMigrationFile(moduleName, entityName, data.options);\r\n\r\n console.log(\r\n colors.cyan(`\\n✨ Migration file created at: ${path.relative(appPath(), migrationFilePath)}`),\r\n );\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;AAeA,eAAsB,oBAAoB,YAAoB,YAAoB,UAAe,CAAC,GAAG;CACnG,MAAM,SAAS,UAAU,UAAU;CAGnC,MAAM,sBAAM,IAAI,KAAK;CAGrB,MAAM,oBAAoB,GAAG,GAFR,OAAO,IAAI,SAAS,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"migration.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/migration.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport path from \"node:path\";\r\nimport { appPath } from \"../../../../utils\";\r\nimport type { CommandActionData } from \"../../../../commands/types\";\r\nimport { migrationAlterStub, migrationStub } from \"../templates/stubs\";\r\nimport { parseColumnDsl } from \"./column-dsl-parser\";\r\nimport { parseName } from \"../utils/name-parser\";\r\nimport { ensureDirectoryAsync, putFileAsync, setDryRun } from \"../utils/writer\";\r\n\r\n/**\r\n * Generate a migration file for a model\r\n */\r\n/**\r\n * Create a migration file for a model\r\n */\r\nexport async function createMigrationFile(moduleName: string, entityName: string, options: any = {}) {\r\n const entity = parseName(entityName);\r\n\r\n // Generate timestamp: MM-DD-YYYY_HH-MM-SS\r\n const now = new Date();\r\n const timestamp = `${String(now.getMonth() + 1).padStart(2, \"0\")}-${String(now.getDate()).padStart(2, \"0\")}-${now.getFullYear()}_${String(now.getHours()).padStart(2, \"0\")}-${String(now.getMinutes()).padStart(2, \"0\")}-${String(now.getSeconds()).padStart(2, \"0\")}`;\r\n\r\n const migrationFileName = `${timestamp}-${entity.kebab}.migration.ts`;\r\n const migrationsPath = path.join(appPath(), moduleName, \"models\", entity.kebab, \"migrations\");\r\n\r\n // Ensure migrations directory exists\r\n await ensureDirectoryAsync(migrationsPath);\r\n\r\n const addParams = options.add as string;\r\n const dropParams = options.drop as string;\r\n const renameParams = options.rename as string;\r\n const timestamps = options.timestamps !== \"false\" && options.timestamps !== false;\r\n\r\n let migrationContent = \"\";\r\n\r\n if (addParams || dropParams || renameParams) {\r\n // Generate alter stub\r\n const parsedAdd = parseColumnDsl(addParams || \"\");\r\n const helpersSet = new Set<string>();\r\n \r\n const addLines = parsedAdd.map((col) => {\r\n helpersSet.add(col.helper);\r\n return ` ${col.name}: ${col.helper}()${col.modifiers.join(\"\")},`;\r\n });\r\n\r\n let formattedDrop: string | undefined = undefined;\r\n if (dropParams) {\r\n formattedDrop = JSON.stringify(dropParams.split(\",\").map((s) => s.trim()));\r\n }\r\n\r\n let formattedRename: string | undefined = undefined;\r\n if (renameParams) {\r\n const obj: Record<string, string> = {};\r\n renameParams.split(\",\").map((s) => s.trim()).forEach((p) => {\r\n const [oldN, newN] = p.split(\":\").map((s) => s.trim());\r\n if (oldN && newN) obj[oldN] = newN;\r\n });\r\n formattedRename = JSON.stringify(obj, null, 2).replace(/\\n/g, '\\n ');\r\n }\r\n\r\n migrationContent = migrationAlterStub(entity, {\r\n add: addLines.length > 0 ? addLines.join(\"\\n\") : undefined,\r\n drop: formattedDrop,\r\n rename: formattedRename,\r\n imports: Array.from(helpersSet),\r\n });\r\n } else {\r\n // Generate create stub\r\n migrationContent = migrationStub(entity, {\r\n timestamps,\r\n });\r\n }\r\n\r\n // Create migration file\r\n const migrationFilePath = path.join(migrationsPath, migrationFileName);\r\n await putFileAsync(migrationFilePath, migrationContent);\r\n\r\n return migrationFilePath;\r\n}\r\n\r\n/**\r\n * Generate a migration file for a model\r\n */\r\nexport async function generateMigration(data: CommandActionData) {\r\n const modelPath = data.args[0] as string;\r\n\r\n if (!modelPath) {\r\n console.log(colors.red(\"Error: Model path is required\"));\r\n console.log(colors.gray(\"Usage: warlock gen.migration <model-path>\"));\r\n console.log(colors.gray(\"Example: warlock gen.migration products/product\"));\r\n return;\r\n }\r\n\r\n // Parse model path (e.g., \"products/product\")\r\n const [moduleName, entityName] = modelPath.split(\"/\");\r\n\r\n if (!moduleName || !entityName) {\r\n console.log(colors.red(\"Error: Invalid model path format. Expected: <module>/<entity>\"));\r\n console.log(colors.gray(\"Example: warlock gen.migration products/product\"));\r\n return;\r\n }\r\n\r\n setDryRun(Boolean(data.options.dryRun));\r\n\r\n const migrationFilePath = await createMigrationFile(moduleName, entityName, data.options);\r\n\r\n console.log(\r\n colors.cyan(`\\n✨ Migration file created at: ${path.relative(appPath(), migrationFilePath)}`),\r\n );\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/model.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport path from \"node:path\";\r\nimport type { CommandActionData } from \"../../../../commands/types\";\r\nimport { migrationStub, modelStub } from \"../templates/stubs\";\r\nimport { parseModulePath, singularName } from \"../utils/name-parser\";\r\nimport { componentExists, moduleExists, resolveModulePath } from \"../utils/path-resolver\";\r\nimport { ensureDirectoryAsync, putFileAsync, setDryRun } from \"../utils/writer\";\r\n\r\nexport async function generateModel(data: CommandActionData): Promise<void> {\r\n const input = data.args[0];\r\n\r\n if (!input) {\r\n console.log(colors.red(\"Error: Model name is required\"));\r\n console.log(colors.yellow(\"Usage: warlock create.model <module>/<name>\"));\r\n console.log(colors.yellow(\"Example: warlock create.model users/user\"));\r\n process.exit(1);\r\n }\r\n\r\n const { module, name: componentName } = parseModulePath(input);\r\n\r\n if (!module) {\r\n console.log(colors.red(\"Error: Module name is required\"));\r\n console.log(colors.yellow(\"Usage: warlock create.model <module>/<name>\"));\r\n process.exit(1);\r\n }\r\n\r\n // Check if module exists\r\n if (!(await moduleExists(module))) {\r\n console.log(colors.red(`Error: Module \"${module}\" does not exist`));\r\n console.log(colors.yellow(`Run: warlock create.module ${module}`));\r\n process.exit(1);\r\n }\r\n\r\n const name = singularName(componentName);\r\n const force = data.options.force || data.options.f;\r\n setDryRun(Boolean(data.options.dryRun));\r\n const withResource = data.options.withResource || data.options.rs;\r\n const tableName = (data.options.table as string) || name.plural.snake;\r\n\r\n // Check if model already exists\r\n const modelDir = path.join(resolveModulePath(module), \"models\", name.kebab);\r\n const modelPath = path.join(modelDir, `${name.kebab}.model.ts`);\r\n\r\n if ((await componentExists(module, `models/${name.kebab}`, `${name.kebab}.model`)) && !force) {\r\n console.log(colors.red(`Error: Model \"${name.kebab}\" already exists`));\r\n console.log(colors.yellow(\"Use --force to overwrite\"));\r\n process.exit(1);\r\n }\r\n\r\n // Ensure directories exist\r\n await ensureDirectoryAsync(modelDir);\r\n await ensureDirectoryAsync(path.join(modelDir, \"migrations\"));\r\n\r\n // Generate model\r\n const modelContent = modelStub(name, { tableName, withResource: !!withResource });\r\n await putFileAsync(modelPath, modelContent);\r\n\r\n // Generate index.ts\r\n const indexContent = `export * from \"./${name.kebab}.model\";\r\n`;\r\n await putFileAsync(path.join(modelDir, \"index.ts\"), indexContent);\r\n\r\n // Generate migration\r\n const timestamp = new Date().toISOString().replace(/[-:T]/g, \"_\").split(\".\")[0];\r\n const migrationPath = path.join(\r\n modelDir,\r\n \"migrations\",\r\n `${timestamp}_${name.kebab}.migration.ts`,\r\n );\r\n\r\n const migrationContent = migrationStub(name, {\r\n timestamps: data.options.timestamps !== \"false\" && data.options.timestamps !== false,\r\n });\r\n\r\n await putFileAsync(migrationPath, migrationContent);\r\n\r\n console.log(colors.cyan(`\\n✨ Model \"${name.pascal}\" generated successfully!`));\r\n console.log(colors.gray(`\\nNext steps:`));\r\n console.log(colors.gray(` 1. Update model schema in ${name.kebab}.model.ts`));\r\n console.log(\r\n colors.gray(` 2. Update migration in migrations/${timestamp}_${name.kebab}.migration.ts`),\r\n );\r\n console.log(colors.gray(` 3. Run migration: warlock migrate`));\r\n}\r\n"],"mappings":";;;;;;;;AAQA,eAAsB,cAAc,MAAwC;CAC1E,MAAM,QAAQ,KAAK,KAAK;CAExB,IAAI,CAAC,OAAO;EACV,QAAQ,IAAI,OAAO,IAAI,+BAA+B,CAAC;EACvD,QAAQ,IAAI,OAAO,OAAO,6CAA6C,CAAC;EACxE,QAAQ,IAAI,OAAO,OAAO,0CAA0C,CAAC;EACrE,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,EAAE,QAAQ,MAAM,kBAAkB,gBAAgB,KAAK;CAE7D,IAAI,CAAC,QAAQ;EACX,QAAQ,IAAI,OAAO,IAAI,gCAAgC,CAAC;EACxD,QAAQ,IAAI,OAAO,OAAO,6CAA6C,CAAC;EACxE,QAAQ,KAAK,CAAC;CAChB;CAGA,IAAI,CAAE,MAAM,aAAa,MAAM,GAAI;EACjC,QAAQ,IAAI,OAAO,IAAI,kBAAkB,OAAO,iBAAiB,CAAC;EAClE,QAAQ,IAAI,OAAO,OAAO,8BAA8B,QAAQ,CAAC;EACjE,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,OAAO,aAAa,aAAa;CACvC,MAAM,QAAQ,KAAK,QAAQ,SAAS,KAAK,QAAQ;CACjD,UAAU,QAAQ,KAAK,QAAQ,MAAM,CAAC;CACtC,MAAM,eAAe,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CAC/D,MAAM,YAAa,KAAK,QAAQ,SAAoB,KAAK,OAAO;CAGhE,MAAM,WAAW,KAAK,KAAK,kBAAkB,MAAM,GAAG,UAAU,KAAK,KAAK;CAC1E,MAAM,YAAY,KAAK,KAAK,UAAU,GAAG,KAAK,MAAM,UAAU;CAE9D,IAAK,MAAM,gBAAgB,QAAQ,UAAU,KAAK,SAAS,GAAG,KAAK,MAAM,OAAO,KAAM,CAAC,OAAO;EAC5F,QAAQ,IAAI,OAAO,IAAI,iBAAiB,KAAK,MAAM,iBAAiB,CAAC;EACrE,QAAQ,IAAI,OAAO,OAAO,0BAA0B,CAAC;EACrD,QAAQ,KAAK,CAAC;CAChB;CAGA,MAAM,qBAAqB,QAAQ;CACnC,MAAM,qBAAqB,KAAK,KAAK,UAAU,YAAY,CAAC;CAI5D,MAAM,aAAa,WADE,UAAU,MAAM;EAAE;EAAW,cAAc,CAAC,CAAC;CAAa,CACtC,CAAC;CAG1C,MAAM,eAAe,oBAAoB,KAAK,MAAM;;CAEpD,MAAM,aAAa,KAAK,KAAK,UAAU,UAAU,GAAG,YAAY;CAGhE,MAAM,6BAAY,IAAI,KAAK,
|
|
1
|
+
{"version":3,"file":"model.generator.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/generators/model.generator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\r\nimport path from \"node:path\";\r\nimport type { CommandActionData } from \"../../../../commands/types\";\r\nimport { migrationStub, modelStub } from \"../templates/stubs\";\r\nimport { parseModulePath, singularName } from \"../utils/name-parser\";\r\nimport { componentExists, moduleExists, resolveModulePath } from \"../utils/path-resolver\";\r\nimport { ensureDirectoryAsync, putFileAsync, setDryRun } from \"../utils/writer\";\r\n\r\nexport async function generateModel(data: CommandActionData): Promise<void> {\r\n const input = data.args[0];\r\n\r\n if (!input) {\r\n console.log(colors.red(\"Error: Model name is required\"));\r\n console.log(colors.yellow(\"Usage: warlock create.model <module>/<name>\"));\r\n console.log(colors.yellow(\"Example: warlock create.model users/user\"));\r\n process.exit(1);\r\n }\r\n\r\n const { module, name: componentName } = parseModulePath(input);\r\n\r\n if (!module) {\r\n console.log(colors.red(\"Error: Module name is required\"));\r\n console.log(colors.yellow(\"Usage: warlock create.model <module>/<name>\"));\r\n process.exit(1);\r\n }\r\n\r\n // Check if module exists\r\n if (!(await moduleExists(module))) {\r\n console.log(colors.red(`Error: Module \"${module}\" does not exist`));\r\n console.log(colors.yellow(`Run: warlock create.module ${module}`));\r\n process.exit(1);\r\n }\r\n\r\n const name = singularName(componentName);\r\n const force = data.options.force || data.options.f;\r\n setDryRun(Boolean(data.options.dryRun));\r\n const withResource = data.options.withResource || data.options.rs;\r\n const tableName = (data.options.table as string) || name.plural.snake;\r\n\r\n // Check if model already exists\r\n const modelDir = path.join(resolveModulePath(module), \"models\", name.kebab);\r\n const modelPath = path.join(modelDir, `${name.kebab}.model.ts`);\r\n\r\n if ((await componentExists(module, `models/${name.kebab}`, `${name.kebab}.model`)) && !force) {\r\n console.log(colors.red(`Error: Model \"${name.kebab}\" already exists`));\r\n console.log(colors.yellow(\"Use --force to overwrite\"));\r\n process.exit(1);\r\n }\r\n\r\n // Ensure directories exist\r\n await ensureDirectoryAsync(modelDir);\r\n await ensureDirectoryAsync(path.join(modelDir, \"migrations\"));\r\n\r\n // Generate model\r\n const modelContent = modelStub(name, { tableName, withResource: !!withResource });\r\n await putFileAsync(modelPath, modelContent);\r\n\r\n // Generate index.ts\r\n const indexContent = `export * from \"./${name.kebab}.model\";\r\n`;\r\n await putFileAsync(path.join(modelDir, \"index.ts\"), indexContent);\r\n\r\n // Generate migration\r\n const timestamp = new Date().toISOString().replace(/[-:T]/g, \"_\").split(\".\")[0];\r\n const migrationPath = path.join(\r\n modelDir,\r\n \"migrations\",\r\n `${timestamp}_${name.kebab}.migration.ts`,\r\n );\r\n\r\n const migrationContent = migrationStub(name, {\r\n timestamps: data.options.timestamps !== \"false\" && data.options.timestamps !== false,\r\n });\r\n\r\n await putFileAsync(migrationPath, migrationContent);\r\n\r\n console.log(colors.cyan(`\\n✨ Model \"${name.pascal}\" generated successfully!`));\r\n console.log(colors.gray(`\\nNext steps:`));\r\n console.log(colors.gray(` 1. Update model schema in ${name.kebab}.model.ts`));\r\n console.log(\r\n colors.gray(` 2. Update migration in migrations/${timestamp}_${name.kebab}.migration.ts`),\r\n );\r\n console.log(colors.gray(` 3. Run migration: warlock migrate`));\r\n}\r\n"],"mappings":";;;;;;;;AAQA,eAAsB,cAAc,MAAwC;CAC1E,MAAM,QAAQ,KAAK,KAAK;CAExB,IAAI,CAAC,OAAO;EACV,QAAQ,IAAI,OAAO,IAAI,+BAA+B,CAAC;EACvD,QAAQ,IAAI,OAAO,OAAO,6CAA6C,CAAC;EACxE,QAAQ,IAAI,OAAO,OAAO,0CAA0C,CAAC;EACrE,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,EAAE,QAAQ,MAAM,kBAAkB,gBAAgB,KAAK;CAE7D,IAAI,CAAC,QAAQ;EACX,QAAQ,IAAI,OAAO,IAAI,gCAAgC,CAAC;EACxD,QAAQ,IAAI,OAAO,OAAO,6CAA6C,CAAC;EACxE,QAAQ,KAAK,CAAC;CAChB;CAGA,IAAI,CAAE,MAAM,aAAa,MAAM,GAAI;EACjC,QAAQ,IAAI,OAAO,IAAI,kBAAkB,OAAO,iBAAiB,CAAC;EAClE,QAAQ,IAAI,OAAO,OAAO,8BAA8B,QAAQ,CAAC;EACjE,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,OAAO,aAAa,aAAa;CACvC,MAAM,QAAQ,KAAK,QAAQ,SAAS,KAAK,QAAQ;CACjD,UAAU,QAAQ,KAAK,QAAQ,MAAM,CAAC;CACtC,MAAM,eAAe,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CAC/D,MAAM,YAAa,KAAK,QAAQ,SAAoB,KAAK,OAAO;CAGhE,MAAM,WAAW,KAAK,KAAK,kBAAkB,MAAM,GAAG,UAAU,KAAK,KAAK;CAC1E,MAAM,YAAY,KAAK,KAAK,UAAU,GAAG,KAAK,MAAM,UAAU;CAE9D,IAAK,MAAM,gBAAgB,QAAQ,UAAU,KAAK,SAAS,GAAG,KAAK,MAAM,OAAO,KAAM,CAAC,OAAO;EAC5F,QAAQ,IAAI,OAAO,IAAI,iBAAiB,KAAK,MAAM,iBAAiB,CAAC;EACrE,QAAQ,IAAI,OAAO,OAAO,0BAA0B,CAAC;EACrD,QAAQ,KAAK,CAAC;CAChB;CAGA,MAAM,qBAAqB,QAAQ;CACnC,MAAM,qBAAqB,KAAK,KAAK,UAAU,YAAY,CAAC;CAI5D,MAAM,aAAa,WADE,UAAU,MAAM;EAAE;EAAW,cAAc,CAAC,CAAC;CAAa,CACtC,CAAC;CAG1C,MAAM,eAAe,oBAAoB,KAAK,MAAM;;CAEpD,MAAM,aAAa,KAAK,KAAK,UAAU,UAAU,GAAG,YAAY;CAGhE,MAAM,6BAAY,IAAI,KAAK,EAAC,CAAC,YAAY,CAAC,CAAC,QAAQ,UAAU,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;CAW7E,MAAM,aAVgB,KAAK,KACzB,UACA,cACA,GAAG,UAAU,GAAG,KAAK,MAAM,cAOE,GAJN,cAAc,MAAM,EAC3C,YAAY,KAAK,QAAQ,eAAe,WAAW,KAAK,QAAQ,eAAe,MACjF,CAEiD,CAAC;CAElD,QAAQ,IAAI,OAAO,KAAK,gBAAgB,KAAK,OAAO,0BAA0B,CAAC;CAC/E,QAAQ,IAAI,OAAO,KAAK,eAAe,CAAC;CACxC,QAAQ,IAAI,OAAO,KAAK,+BAA+B,KAAK,MAAM,UAAU,CAAC;CAC7E,QAAQ,IACN,OAAO,KAAK,uCAAuC,UAAU,GAAG,KAAK,MAAM,cAAc,CAC3F;CACA,QAAQ,IAAI,OAAO,KAAK,qCAAqC,CAAC;AAChE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stubs.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/templates/stubs.ts"],"sourcesContent":["import type { ParsedName } from \"../types\";\nimport { Name } from \"../utils/name-parser\";\n\n/**\n * Controller template stub\n *\n * Controllers default to the guarded handler type since application\n * routes run behind `guarded()`. When `withValidation` is set, the\n * schema's exported type + value are imported directly from the\n * `schema/` folder and bound to the handler — no `requests/` alias.\n */\nexport function controllerStub(\n name: ParsedName,\n options: { withValidation?: boolean } = {},\n): string {\n const { withValidation } = options;\n\n if (!withValidation) {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\n\nexport const ${name.camel}Controller: GuardedRequestHandler = async (request, response) => {\n // TODO: Implement controller logic\n return response.success({});\n};\n`;\n }\n\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type ${name.pascal}Schema, ${name.camel}Schema } from \"../schema/${name.kebab}.schema\";\n\nexport const ${name.camel}Controller: GuardedRequestHandler<${name.pascal}Schema> = async (\n request,\n response,\n) => {\n // TODO: Implement controller logic\n return response.success({});\n};\n\n${name.camel}Controller.validation = {\n schema: ${name.camel}Schema,\n};\n`;\n}\n\n/**\n * CRUD Create Controller template\n * Note: moduleName is the module (plural), we need singular entity name\n */\nexport function crudCreateControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type Create${moduleName.pascal}Schema, create${moduleName.pascal}Schema } from \"../schema/create-${moduleName.kebab}.schema\";\nimport { create${moduleName.pascal}Service } from \"../services/create-${moduleName.kebab}.service\";\n\nexport const create${moduleName.pascal}Controller: GuardedRequestHandler<Create${moduleName.pascal}Schema> = async (\n request,\n response,\n) => {\n const ${moduleName.camel} = await create${moduleName.pascal}Service(request.validated());\n\n return response.successCreate({\n ${moduleName.camel},\n });\n};\n\ncreate${moduleName.pascal}Controller.validation = {\n schema: create${moduleName.pascal}Schema,\n};\n`;\n}\n\n/**\n * CRUD Update Controller template\n */\nexport function crudUpdateControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type Update${moduleName.pascal}Schema, update${moduleName.pascal}Schema } from \"../schema/update-${moduleName.kebab}.schema\";\nimport { update${moduleName.pascal}Service } from \"../services/update-${moduleName.kebab}.service\";\n\nexport const update${moduleName.pascal}Controller: GuardedRequestHandler<Update${moduleName.pascal}Schema> = async (\n request,\n response,\n) => {\n const ${moduleName.camel} = await update${moduleName.pascal}Service(request.input(\"id\"), request.validated());\n\n return response.success({\n ${moduleName.camel},\n });\n};\n\nupdate${moduleName.pascal}Controller.validation = {\n schema: update${moduleName.pascal}Schema,\n};\n`;\n}\n\n/**\n * CRUD List Controller template\n */\nexport function crudListControllerStub(moduleName: Name): string {\n const plural = moduleName.plural;\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { list${plural.pascal}Service } from \"../services/list-${plural.kebab}.service\";\n\nexport const list${plural.pascal}Controller: GuardedRequestHandler = async (\n request,\n response,\n) => {\n const { data, pagination } = await list${plural.pascal}Service(request.all());\n\n return response.success({\n data,\n pagination,\n });\n};\n`;\n}\n\n/**\n * CRUD Show/Get Controller template\n */\nexport function crudShowControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { get${moduleName.pascal}Service } from \"../services/get-${moduleName.kebab}.service\";\n\nexport const get${moduleName.pascal}Controller: GuardedRequestHandler = async (\n request,\n response,\n) => {\n const ${moduleName.camel} = await get${moduleName.pascal}Service(request.input(\"id\"));\n\n if (!${moduleName.camel}) {\n return response.notFound();\n }\n\n return response.success({\n ${moduleName.camel},\n });\n};\n`;\n}\n\n/**\n * CRUD Delete Controller template\n */\nexport function crudDeleteControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { delete${moduleName.pascal}Service } from \"../services/delete-${moduleName.kebab}.service\";\n\nexport const delete${moduleName.pascal}Controller: GuardedRequestHandler = async (\n request,\n response,\n) => {\n await delete${moduleName.pascal}Service(request.input(\"id\"));\n\n return response.noContent();\n};\n`;\n}\n\n/**\n * CRUD Routes template\n */\nexport function crudRoutesStub(moduleName: Name): string {\n const singular = moduleName.singular;\n const plural = moduleName.plural;\n\n return `import { router } from \"@warlock.js/core\";\nimport { guarded } from \"app/shared/utils/router\";\nimport { create${singular.pascal}Controller } from \"./controllers/create-${singular.kebab}.controller\";\nimport { delete${singular.pascal}Controller } from \"./controllers/delete-${singular.kebab}.controller\";\nimport { get${singular.pascal}Controller } from \"./controllers/get-${singular.kebab}.controller\";\nimport { list${plural.pascal}Controller } from \"./controllers/list-${plural.kebab}.controller\";\nimport { update${singular.pascal}Controller } from \"./controllers/update-${singular.kebab}.controller\";\n\nguarded(() => {\n router\n .route(\"/${plural.kebab}\")\n .list(list${plural.pascal}Controller)\n .show(get${singular.pascal}Controller)\n .create(create${singular.pascal}Controller)\n .update(update${singular.pascal}Controller)\n .destroy(delete${singular.pascal}Controller);\n});\n`;\n}\n\n/**\n * CRUD Model template\n */\nexport function crudModelStub(moduleName: Name): string {\n const singular = moduleName.singular;\n const plural = moduleName.plural;\n\n return `import { Model, RegisterModel } from \"@warlock.js/cascade\";\nimport { type Infer, v } from \"@warlock.js/seal\";\nimport { ${singular.pascal}Resource } from \"app/${plural.kebab}/resources/${singular.kebab}.resource\";\n\nexport const ${singular.camel}Schema = v.object({\n // TODO: Add more fields\n});\n\nexport type ${singular.pascal}Schema = Infer.Output<typeof ${singular.camel}Schema>;\n\n@RegisterModel()\nexport class ${singular.pascal} extends Model<${singular.pascal}Schema> {\n public static table = \"${plural.snake}\";\n\n public static schema = ${singular.camel}Schema;\n\n public static relations = {};\n\n public static resource = ${singular.pascal}Resource;\n}\n`;\n}\n\n/**\n * CRUD Resource template\n */\nexport function crudResourceStub(moduleName: Name): string {\n // Get singular entity name\n const entity = moduleName.singular;\n\n return `import { defineResource } from \"@warlock.js/core\";\n\nexport const ${entity.pascal}Resource = defineResource({\n schema: {\n id: \"number\",\n // TODO: Add more resource fields\n },\n});\n`;\n}\n\n/**\n * CRUD Repository template\n */\nexport function crudRepositoryStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n const modulePluralName = entity.plural;\n return `import type { FilterRules, TypedRepositoryOptions } from \"@warlock.js/core\";\nimport { RepositoryManager } from \"@warlock.js/core\";\nimport { ${moduleSingularName.pascal} } from \"../models/${moduleSingularName.kebab}\";\n\ntype ${moduleSingularName.pascal}ListFilter = {\n // Repository list filters\n};\n\nexport type ${moduleSingularName.pascal}ListOptions = TypedRepositoryOptions<${moduleSingularName.pascal}ListFilter>;\n\nclass ${modulePluralName.pascal}Repository extends RepositoryManager<${moduleSingularName.pascal}, ${moduleSingularName.pascal}ListOptions> {\n public source = ${moduleSingularName.pascal};\n\n public simpleSelectColumns: string[] = [\"id\"];\n\n public filterBy: FilterRules = {\n id: \"=\",\n };\n\n public defaultOptions: RepositoryOptions = {\n orderBy: {\n id: \"desc\",\n },\n };\n}\n\nexport const ${modulePluralName.camel}Repository = new ${modulePluralName.pascal}Repository();\n`;\n}\n\n/**\n * CRUD Create Service template\n */\nexport function crudCreateServiceStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n return `import { ${moduleSingularName.pascal} } from \"../models/${moduleSingularName.kebab}\";\nimport type { Create${moduleSingularName.pascal}Schema } from \"../schema/create-${moduleSingularName.kebab}.schema\";\n\nexport async function create${moduleSingularName.pascal}Service(data: Create${moduleSingularName.pascal}Schema) {\n const ${moduleSingularName.camel} = await ${moduleSingularName.pascal}.create(data);\n return ${moduleSingularName.camel};\n}\n`;\n}\n\n/**\n * CRUD Update Service template\n */\nexport function crudUpdateServiceStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n return `import { ResourceNotFoundError } from \"@warlock.js/core\";\nimport { get${moduleSingularName.pascal}Service } from \"./get-${moduleSingularName.kebab}.service\";\nimport type { Update${moduleSingularName.pascal}Schema } from \"../schema/update-${moduleSingularName.kebab}.schema\";\n\nexport async function update${moduleSingularName.pascal}Service(id: number | string, data: Update${moduleSingularName.pascal}Schema) {\n const ${moduleSingularName.camel} = await get${moduleSingularName.pascal}Service(id);\n\n await ${moduleSingularName.camel}.save({ merge: data });\n return ${moduleSingularName.camel};\n}\n`;\n}\n\n/**\n * CRUD List Service template\n */\nexport function crudListServiceStub(entity: Name): string {\n const modulePluralName = entity.plural;\n return `import { ${modulePluralName.camel}Repository } from \"../repositories/${modulePluralName.kebab}.repository\";\n\nexport async function list${modulePluralName.pascal}Service(filters: any) {\n return ${modulePluralName.camel}Repository.listCached(filters);\n}\n`;\n}\n\n/**\n * CRUD Get Service template\n */\nexport function crudGetServiceStub(entity: Name): string {\n return `import { ${entity.plural.camel}Repository } from \"../repositories/${entity.plural.kebab}.repository\";\nimport { ResourceNotFoundError } from \"@warlock.js/core\";\n\nexport async function get${entity.singular.pascal}Service(id: number | string) {\n const ${entity.singular.camel} = await ${entity.plural.camel}Repository.getCached(id);\n\n if (!${entity.singular.camel}) {\n throw new ResourceNotFoundError(\"${entity.singular.pascal} resource not found!\");\n }\n\n return ${entity.singular.camel};\n}\n`;\n}\n\n/**\n * CRUD Delete Service template\n */\nexport function crudDeleteServiceStub(entity: Name): string {\n const singular = entity.singular;\n return `import { ResourceNotFoundError } from \"@warlock.js/core\";\nimport { get${singular.pascal}Service } from \"./get-${singular.kebab}.service\";\n\nexport async function delete${singular.pascal}Service(id: number | string) {\n const ${singular.camel} = await get${singular.pascal}Service(id);\n if (!${singular.camel}) {\n throw new ResourceNotFoundError(\"${singular.pascal} not found\");\n }\n await ${singular.camel}.destroy();\n}\n`;\n}\n\n/**\n * CRUD Seed template\n */\nexport function crudSeedStub(entity: Name): string {\n return `import { seeder } from \"@warlock.js/core\";\nimport { ${entity.singular.pascal} } from \"../models/${entity.singular.kebab}\";\n\nexport default seeder({\n name: \"Seed ${entity.plural.pascal}\",\n once: true,\n enabled: true,\n run: async ({ track }) => {\n const total = 10;\n for (let i = 0; i < total; i++) {\n track(\n await ${entity.singular.pascal}.create({\n // TODO: Add more fields\n }),\n );\n }\n },\n});\n`;\n}\n\n/**\n * Migration template\n */\n/**\n * Migration Create template\n */\nexport function migrationStub(\n entityName: ParsedName,\n options: {\n columns?: string;\n imports?: string[];\n timestamps?: boolean;\n tableName?: string;\n } = {},\n): string {\n const { columns = \"\", imports = [], timestamps = true } = options;\n\n const allImports = [\"Migration\", ...imports].join(\", \");\n\n let optionsString = \"\";\n if (timestamps === false) {\n optionsString = `, { timestamps: false }`;\n }\n\n return `import { ${allImports} } from \"@warlock.js/cascade\";\nimport { ${entityName.pascal} } from \"../${entityName.kebab}.model\";\n\nexport default Migration.create(${entityName.pascal}, {\n${columns ? columns : \" // add your columns here, id is auto added to the list\"}\n}${optionsString});\n`;\n}\n\n/**\n * Migration Alter template\n */\nexport function migrationAlterStub(\n entityName: ParsedName,\n options: {\n add?: string;\n drop?: string; // stringified array like `\"[\\\"col1\\\", \\\"col2\\\"]\"`\n rename?: string; // stringified object like `{ old: \"new\" }`\n imports?: string[];\n } = {},\n): string {\n const { add = \"\", drop, rename, imports = [] } = options;\n const allImports = [\"Migration\", ...imports].join(\", \");\n\n // Build the schema object dynamically\n const schemaParts: string[] = [];\n\n if (add) {\n schemaParts.push(` add: {\\n${add}\\n },`);\n }\n\n if (drop) {\n schemaParts.push(` drop: ${drop},`);\n }\n\n if (rename) {\n schemaParts.push(` rename: ${rename},`);\n }\n\n return `import { ${allImports} } from \"@warlock.js/cascade\";\nimport { ${entityName.pascal} } from \"../${entityName.kebab}.model\";\n\nexport default Migration.alter(${entityName.pascal}, {\n${schemaParts.join(\"\\n\")}\n});\n`;\n}\n\n/**\n * CRUD Create Schema template\n * Outputs to: schema/create-{entity}.schema.ts\n */\nexport function crudCreateSchemaStub(moduleName: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const create${moduleName.pascal}Schema = v.object({\n // TODO: Add validation rules\n});\n\nexport type Create${moduleName.pascal}Schema = Infer<typeof create${moduleName.pascal}Schema>;\n`;\n}\n\n/**\n * CRUD Update Schema template\n * Outputs to: schema/update-{entity}.schema.ts\n */\nexport function crudUpdateSchemaStub(moduleName: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const update${moduleName.pascal}Schema = v.object({\n // TODO: Add validation rules\n});\n\nexport type Update${moduleName.pascal}Schema = Infer<typeof update${moduleName.pascal}Schema>;\n`;\n}\n\n/**\n * Service template stub\n */\nexport function serviceStub(name: Name): string {\n return `export async function ${name.camel}Service(data: any): Promise<any> {\n // TODO: Implement service logic\n throw new Error(\"${name.camel}Service not implemented\");\n}\n`;\n}\n\n/**\n * Schema template stub\n * Outputs to: schema/{name}.schema.ts\n */\nexport function schemaStub(name: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const ${name.camel}Schema = v.object({\n // TODO: Define validation schema\n});\n\nexport type ${name.pascal}Schema = Infer<typeof ${name.camel}Schema>;\n`;\n}\n\n/**\n * Model template stub\n */\nexport function modelStub(\n name: Name,\n options: { tableName?: string; withResource?: boolean } = {},\n): string {\n const { tableName = `${name.plural.snake}`, withResource } = options;\n\n return `import { Model, type StrictMode } from \"@warlock.js/cascade\";\nimport { v, type Infer } from \"@warlock.js/seal\";\n${withResource ? `import { ${name.singular.pascal}Resource } from \"../../resources/${name.singular.kebab}.resource\";` : \"\"}\n\nconst ${name.singular.camel}Schema = v.object({\n // TODO: Define model schema\n});\n\nexport type ${name.singular.pascal}Type = Infer.Output<typeof ${name.singular.camel}Schema>;\n\nexport class ${name.singular.pascal} extends Model<${name.singular.pascal}Type> {\n public static table = \"${tableName}\";\n public static strictMode: StrictMode = \"fail\";\n${withResource ? ` public static resource = ${name.singular.pascal}Resource;` : \"\"}\n\n public static schema = ${name.singular.camel}Schema;\n\n public static relations = {\n // TODO: Define relations\n };\n}\n`;\n}\n\n/**\n * Repository template stub\n */\nexport function repositoryStub(name: Name): string {\n return `import type { FilterByOptions, RepositoryOptions } from \"@warlock.js/core\";\nimport { RepositoryManager } from \"@warlock.js/core\";\nimport { ${name.singular.pascal} } from \"../models/${name.singular.kebab}\";\n\ntype ${name.singular.pascal}ListFilter = {\n // Repository list filters\n};\n\nexport type ${name.singular.pascal}ListOptions = RepositoryOptions & ${name.singular.pascal}ListFilter;\n\nexport class ${name.plural.pascal}Repository extends RepositoryManager<${name.singular.pascal}, ${name.singular.pascal}ListFilter> {\n public source = ${name.singular.pascal};\n\n protected defaultOptions: RepositoryOptions = this.withDefaultOptions({});\n\n protected filterBy: FilterByOptions = this.withDefaultFilters({\n name: \"like\",\n });\n}\n\nexport const ${name.plural.camel}Repository = new ${name.plural.pascal}Repository();\n`;\n}\n\n/**\n * Resource template stub\n */\nexport function resourceStub(name: Name): string {\n return `import { Resource } from \"@warlock.js/core\";\n\nexport class ${name.singular.pascal}Resource extends Resource {\n public schema = {\n id: \"int\",\n name: \"string\",\n // TODO: Define resource schema\n };\n}\n`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stubs.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/templates/stubs.ts"],"sourcesContent":["import type { ParsedName } from \"../types\";\nimport { Name } from \"../utils/name-parser\";\n\n/**\n * Controller template stub\n *\n * Controllers default to the guarded handler type since application\n * routes run behind `guarded()`. When `withValidation` is set, the\n * schema's exported type + value are imported directly from the\n * `schema/` folder and bound to the handler — no `requests/` alias.\n */\nexport function controllerStub(\n name: ParsedName,\n options: { withValidation?: boolean } = {},\n): string {\n const { withValidation } = options;\n\n if (!withValidation) {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\n\nexport const ${name.camel}Controller: GuardedRequestHandler = async (request, response) => {\n // TODO: Implement controller logic\n return response.success({});\n};\n`;\n }\n\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type ${name.pascal}Schema, ${name.camel}Schema } from \"../schema/${name.kebab}.schema\";\n\nexport const ${name.camel}Controller: GuardedRequestHandler<${name.pascal}Schema> = async (\n request,\n response,\n) => {\n // TODO: Implement controller logic\n return response.success({});\n};\n\n${name.camel}Controller.validation = {\n schema: ${name.camel}Schema,\n};\n`;\n}\n\n/**\n * CRUD Create Controller template\n * Note: moduleName is the module (plural), we need singular entity name\n */\nexport function crudCreateControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type Create${moduleName.pascal}Schema, create${moduleName.pascal}Schema } from \"../schema/create-${moduleName.kebab}.schema\";\nimport { create${moduleName.pascal}Service } from \"../services/create-${moduleName.kebab}.service\";\n\nexport const create${moduleName.pascal}Controller: GuardedRequestHandler<Create${moduleName.pascal}Schema> = async (\n request,\n response,\n) => {\n const ${moduleName.camel} = await create${moduleName.pascal}Service(request.validated());\n\n return response.successCreate({\n ${moduleName.camel},\n });\n};\n\ncreate${moduleName.pascal}Controller.validation = {\n schema: create${moduleName.pascal}Schema,\n};\n`;\n}\n\n/**\n * CRUD Update Controller template\n */\nexport function crudUpdateControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { type Update${moduleName.pascal}Schema, update${moduleName.pascal}Schema } from \"../schema/update-${moduleName.kebab}.schema\";\nimport { update${moduleName.pascal}Service } from \"../services/update-${moduleName.kebab}.service\";\n\nexport const update${moduleName.pascal}Controller: GuardedRequestHandler<Update${moduleName.pascal}Schema> = async (\n request,\n response,\n) => {\n const ${moduleName.camel} = await update${moduleName.pascal}Service(request.input(\"id\"), request.validated());\n\n return response.success({\n ${moduleName.camel},\n });\n};\n\nupdate${moduleName.pascal}Controller.validation = {\n schema: update${moduleName.pascal}Schema,\n};\n`;\n}\n\n/**\n * CRUD List Controller template\n */\nexport function crudListControllerStub(moduleName: Name): string {\n const plural = moduleName.plural;\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { list${plural.pascal}Service } from \"../services/list-${plural.kebab}.service\";\n\nexport const list${plural.pascal}Controller: GuardedRequestHandler = async (\n request,\n response,\n) => {\n const { data, pagination } = await list${plural.pascal}Service(request.all());\n\n return response.success({\n data,\n pagination,\n });\n};\n`;\n}\n\n/**\n * CRUD Show/Get Controller template\n */\nexport function crudShowControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { get${moduleName.pascal}Service } from \"../services/get-${moduleName.kebab}.service\";\n\nexport const get${moduleName.pascal}Controller: GuardedRequestHandler = async (\n request,\n response,\n) => {\n const ${moduleName.camel} = await get${moduleName.pascal}Service(request.input(\"id\"));\n\n if (!${moduleName.camel}) {\n return response.notFound();\n }\n\n return response.success({\n ${moduleName.camel},\n });\n};\n`;\n}\n\n/**\n * CRUD Delete Controller template\n */\nexport function crudDeleteControllerStub(moduleName: Name): string {\n return `import { type GuardedRequestHandler } from \"app/auth/requests/guarded.request\";\nimport { delete${moduleName.pascal}Service } from \"../services/delete-${moduleName.kebab}.service\";\n\nexport const delete${moduleName.pascal}Controller: GuardedRequestHandler = async (\n request,\n response,\n) => {\n await delete${moduleName.pascal}Service(request.input(\"id\"));\n\n return response.noContent();\n};\n`;\n}\n\n/**\n * CRUD Routes template\n */\nexport function crudRoutesStub(moduleName: Name): string {\n const singular = moduleName.singular;\n const plural = moduleName.plural;\n\n return `import { router } from \"@warlock.js/core\";\nimport { guarded } from \"app/shared/utils/router\";\nimport { create${singular.pascal}Controller } from \"./controllers/create-${singular.kebab}.controller\";\nimport { delete${singular.pascal}Controller } from \"./controllers/delete-${singular.kebab}.controller\";\nimport { get${singular.pascal}Controller } from \"./controllers/get-${singular.kebab}.controller\";\nimport { list${plural.pascal}Controller } from \"./controllers/list-${plural.kebab}.controller\";\nimport { update${singular.pascal}Controller } from \"./controllers/update-${singular.kebab}.controller\";\n\nguarded(() => {\n router\n .route(\"/${plural.kebab}\")\n .list(list${plural.pascal}Controller)\n .show(get${singular.pascal}Controller)\n .create(create${singular.pascal}Controller)\n .update(update${singular.pascal}Controller)\n .destroy(delete${singular.pascal}Controller);\n});\n`;\n}\n\n/**\n * CRUD Model template\n */\nexport function crudModelStub(moduleName: Name): string {\n const singular = moduleName.singular;\n const plural = moduleName.plural;\n\n return `import { Model, RegisterModel } from \"@warlock.js/cascade\";\nimport { type Infer, v } from \"@warlock.js/seal\";\nimport { ${singular.pascal}Resource } from \"app/${plural.kebab}/resources/${singular.kebab}.resource\";\n\nexport const ${singular.camel}Schema = v.object({\n // TODO: Add more fields\n});\n\nexport type ${singular.pascal}Schema = Infer.Output<typeof ${singular.camel}Schema>;\n\n@RegisterModel()\nexport class ${singular.pascal} extends Model<${singular.pascal}Schema> {\n public static table = \"${plural.snake}\";\n\n public static schema = ${singular.camel}Schema;\n\n public static relations = {};\n\n public static resource = ${singular.pascal}Resource;\n}\n`;\n}\n\n/**\n * CRUD Resource template\n */\nexport function crudResourceStub(moduleName: Name): string {\n // Get singular entity name\n const entity = moduleName.singular;\n\n return `import { defineResource } from \"@warlock.js/core\";\n\nexport const ${entity.pascal}Resource = defineResource({\n schema: {\n id: \"number\",\n // TODO: Add more resource fields\n },\n});\n`;\n}\n\n/**\n * CRUD Repository template\n */\nexport function crudRepositoryStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n const modulePluralName = entity.plural;\n return `import type { FilterRules, TypedRepositoryOptions } from \"@warlock.js/core\";\nimport { RepositoryManager } from \"@warlock.js/core\";\nimport { ${moduleSingularName.pascal} } from \"../models/${moduleSingularName.kebab}\";\n\ntype ${moduleSingularName.pascal}ListFilter = {\n // Repository list filters\n};\n\nexport type ${moduleSingularName.pascal}ListOptions = TypedRepositoryOptions<${moduleSingularName.pascal}ListFilter>;\n\nclass ${modulePluralName.pascal}Repository extends RepositoryManager<${moduleSingularName.pascal}, ${moduleSingularName.pascal}ListOptions> {\n public source = ${moduleSingularName.pascal};\n\n public simpleSelectColumns: string[] = [\"id\"];\n\n public filterBy: FilterRules = {\n id: \"=\",\n };\n\n public defaultOptions: RepositoryOptions = {\n orderBy: {\n id: \"desc\",\n },\n };\n}\n\nexport const ${modulePluralName.camel}Repository = new ${modulePluralName.pascal}Repository();\n`;\n}\n\n/**\n * CRUD Create Service template\n */\nexport function crudCreateServiceStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n return `import { ${moduleSingularName.pascal} } from \"../models/${moduleSingularName.kebab}\";\nimport type { Create${moduleSingularName.pascal}Schema } from \"../schema/create-${moduleSingularName.kebab}.schema\";\n\nexport async function create${moduleSingularName.pascal}Service(data: Create${moduleSingularName.pascal}Schema) {\n const ${moduleSingularName.camel} = await ${moduleSingularName.pascal}.create(data);\n return ${moduleSingularName.camel};\n}\n`;\n}\n\n/**\n * CRUD Update Service template\n */\nexport function crudUpdateServiceStub(entity: Name): string {\n const moduleSingularName = entity.singular;\n return `import { ResourceNotFoundError } from \"@warlock.js/core\";\nimport { get${moduleSingularName.pascal}Service } from \"./get-${moduleSingularName.kebab}.service\";\nimport type { Update${moduleSingularName.pascal}Schema } from \"../schema/update-${moduleSingularName.kebab}.schema\";\n\nexport async function update${moduleSingularName.pascal}Service(id: number | string, data: Update${moduleSingularName.pascal}Schema) {\n const ${moduleSingularName.camel} = await get${moduleSingularName.pascal}Service(id);\n\n await ${moduleSingularName.camel}.save({ merge: data });\n return ${moduleSingularName.camel};\n}\n`;\n}\n\n/**\n * CRUD List Service template\n */\nexport function crudListServiceStub(entity: Name): string {\n const modulePluralName = entity.plural;\n return `import { ${modulePluralName.camel}Repository } from \"../repositories/${modulePluralName.kebab}.repository\";\n\nexport async function list${modulePluralName.pascal}Service(filters: any) {\n return ${modulePluralName.camel}Repository.listCached(filters);\n}\n`;\n}\n\n/**\n * CRUD Get Service template\n */\nexport function crudGetServiceStub(entity: Name): string {\n return `import { ${entity.plural.camel}Repository } from \"../repositories/${entity.plural.kebab}.repository\";\nimport { ResourceNotFoundError } from \"@warlock.js/core\";\n\nexport async function get${entity.singular.pascal}Service(id: number | string) {\n const ${entity.singular.camel} = await ${entity.plural.camel}Repository.getCached(id);\n\n if (!${entity.singular.camel}) {\n throw new ResourceNotFoundError(\"${entity.singular.pascal} resource not found!\");\n }\n\n return ${entity.singular.camel};\n}\n`;\n}\n\n/**\n * CRUD Delete Service template\n */\nexport function crudDeleteServiceStub(entity: Name): string {\n const singular = entity.singular;\n return `import { ResourceNotFoundError } from \"@warlock.js/core\";\nimport { get${singular.pascal}Service } from \"./get-${singular.kebab}.service\";\n\nexport async function delete${singular.pascal}Service(id: number | string) {\n const ${singular.camel} = await get${singular.pascal}Service(id);\n if (!${singular.camel}) {\n throw new ResourceNotFoundError(\"${singular.pascal} not found\");\n }\n await ${singular.camel}.destroy();\n}\n`;\n}\n\n/**\n * CRUD Seed template\n */\nexport function crudSeedStub(entity: Name): string {\n return `import { seeder } from \"@warlock.js/core\";\nimport { ${entity.singular.pascal} } from \"../models/${entity.singular.kebab}\";\n\nexport default seeder({\n name: \"Seed ${entity.plural.pascal}\",\n once: true,\n enabled: true,\n run: async ({ track }) => {\n const total = 10;\n for (let i = 0; i < total; i++) {\n track(\n await ${entity.singular.pascal}.create({\n // TODO: Add more fields\n }),\n );\n }\n },\n});\n`;\n}\n\n/**\n * Migration template\n */\n/**\n * Migration Create template\n */\nexport function migrationStub(\n entityName: ParsedName,\n options: {\n columns?: string;\n imports?: string[];\n timestamps?: boolean;\n tableName?: string;\n } = {},\n): string {\n const { columns = \"\", imports = [], timestamps = true } = options;\n\n const allImports = [\"Migration\", ...imports].join(\", \");\n\n let optionsString = \"\";\n if (timestamps === false) {\n optionsString = `, { timestamps: false }`;\n }\n\n return `import { ${allImports} } from \"@warlock.js/cascade\";\nimport { ${entityName.pascal} } from \"../${entityName.kebab}.model\";\n\nexport default Migration.create(${entityName.pascal}, {\n${columns ? columns : \" // add your columns here, id is auto added to the list\"}\n}${optionsString});\n`;\n}\n\n/**\n * Migration Alter template\n */\nexport function migrationAlterStub(\n entityName: ParsedName,\n options: {\n add?: string;\n drop?: string; // stringified array like `\"[\\\"col1\\\", \\\"col2\\\"]\"`\n rename?: string; // stringified object like `{ old: \"new\" }`\n imports?: string[];\n } = {},\n): string {\n const { add = \"\", drop, rename, imports = [] } = options;\n const allImports = [\"Migration\", ...imports].join(\", \");\n\n // Build the schema object dynamically\n const schemaParts: string[] = [];\n\n if (add) {\n schemaParts.push(` add: {\\n${add}\\n },`);\n }\n\n if (drop) {\n schemaParts.push(` drop: ${drop},`);\n }\n\n if (rename) {\n schemaParts.push(` rename: ${rename},`);\n }\n\n return `import { ${allImports} } from \"@warlock.js/cascade\";\nimport { ${entityName.pascal} } from \"../${entityName.kebab}.model\";\n\nexport default Migration.alter(${entityName.pascal}, {\n${schemaParts.join(\"\\n\")}\n});\n`;\n}\n\n/**\n * CRUD Create Schema template\n * Outputs to: schema/create-{entity}.schema.ts\n */\nexport function crudCreateSchemaStub(moduleName: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const create${moduleName.pascal}Schema = v.object({\n // TODO: Add validation rules\n});\n\nexport type Create${moduleName.pascal}Schema = Infer<typeof create${moduleName.pascal}Schema>;\n`;\n}\n\n/**\n * CRUD Update Schema template\n * Outputs to: schema/update-{entity}.schema.ts\n */\nexport function crudUpdateSchemaStub(moduleName: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const update${moduleName.pascal}Schema = v.object({\n // TODO: Add validation rules\n});\n\nexport type Update${moduleName.pascal}Schema = Infer<typeof update${moduleName.pascal}Schema>;\n`;\n}\n\n/**\n * Service template stub\n */\nexport function serviceStub(name: Name): string {\n return `export async function ${name.camel}Service(data: any): Promise<any> {\n // TODO: Implement service logic\n throw new Error(\"${name.camel}Service not implemented\");\n}\n`;\n}\n\n/**\n * Schema template stub\n * Outputs to: schema/{name}.schema.ts\n */\nexport function schemaStub(name: Name): string {\n return `import { type Infer, v } from \"@warlock.js/seal\";\n\nexport const ${name.camel}Schema = v.object({\n // TODO: Define validation schema\n});\n\nexport type ${name.pascal}Schema = Infer<typeof ${name.camel}Schema>;\n`;\n}\n\n/**\n * Model template stub\n */\nexport function modelStub(\n name: Name,\n options: { tableName?: string; withResource?: boolean } = {},\n): string {\n const { tableName = `${name.plural.snake}`, withResource } = options;\n\n return `import { Model, type StrictMode } from \"@warlock.js/cascade\";\nimport { v, type Infer } from \"@warlock.js/seal\";\n${withResource ? `import { ${name.singular.pascal}Resource } from \"../../resources/${name.singular.kebab}.resource\";` : \"\"}\n\nconst ${name.singular.camel}Schema = v.object({\n // TODO: Define model schema\n});\n\nexport type ${name.singular.pascal}Type = Infer.Output<typeof ${name.singular.camel}Schema>;\n\nexport class ${name.singular.pascal} extends Model<${name.singular.pascal}Type> {\n public static table = \"${tableName}\";\n public static strictMode: StrictMode = \"fail\";\n${withResource ? ` public static resource = ${name.singular.pascal}Resource;` : \"\"}\n\n public static schema = ${name.singular.camel}Schema;\n\n public static relations = {\n // TODO: Define relations\n };\n}\n`;\n}\n\n/**\n * Repository template stub\n */\nexport function repositoryStub(name: Name): string {\n return `import type { FilterByOptions, RepositoryOptions } from \"@warlock.js/core\";\nimport { RepositoryManager } from \"@warlock.js/core\";\nimport { ${name.singular.pascal} } from \"../models/${name.singular.kebab}\";\n\ntype ${name.singular.pascal}ListFilter = {\n // Repository list filters\n};\n\nexport type ${name.singular.pascal}ListOptions = RepositoryOptions & ${name.singular.pascal}ListFilter;\n\nexport class ${name.plural.pascal}Repository extends RepositoryManager<${name.singular.pascal}, ${name.singular.pascal}ListFilter> {\n public source = ${name.singular.pascal};\n\n protected defaultOptions: RepositoryOptions = this.withDefaultOptions({});\n\n protected filterBy: FilterByOptions = this.withDefaultFilters({\n name: \"like\",\n });\n}\n\nexport const ${name.plural.camel}Repository = new ${name.plural.pascal}Repository();\n`;\n}\n\n/**\n * Resource template stub\n */\nexport function resourceStub(name: Name): string {\n return `import { Resource } from \"@warlock.js/core\";\n\nexport class ${name.singular.pascal}Resource extends Resource {\n public schema = {\n id: \"int\",\n name: \"string\",\n // TODO: Define resource schema\n };\n}\n`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"name-parser.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/utils/name-parser.ts"],"sourcesContent":["import { toCamelCase, toKebabCase, toSnakeCase, toStudlyCase } from \"@mongez/reinforcements\";\nimport pluralize from \"pluralize-esm\";\nimport type { ParsedName } from \"../types\";\n\n/**\n * Parse module path from input\n * Examples:\n * - \"users/create-user\" → { module: \"users\", name: \"create-user\" }\n * - \"create-user\" → { module: undefined, name: \"create-user\" }\n */\nexport function parseModulePath(input: string): { module?: string; name: string } {\n const parts = input.split(\"/\");\n\n if (parts.length === 1) {\n return { name: parts[0] };\n }\n\n return {\n module: parts[0],\n name: parts.slice(1).join(\"/\"),\n };\n}\n\n/**\n * Parse name into all case variants\n */\n// Returns the Name class, which is what it has always constructed. Annotating it as\n// ParsedName understated the result: Name carries `plural` and `singular` getters that\n// the CRUD stubs need, and a protected member that makes it nominal in practice — so\n// four generators could not pass this value to a stub expecting a Name, even though\n// that is exactly what they had. Name still satisfies ParsedName for callers that only\n// want the cased strings.\nexport function parseName(input: string): Name {\n return new Name(input);\n}\n\n/**\n * Get plural name\n */\nexport function pluralName(name: string) {\n return new Name(pluralize(name));\n}\n\n/**\n * Get singular name\n */\nexport function singularName(name: string) {\n return new Name(pluralize.singular(name));\n}\n\nexport class Name {\n protected parsedData: {\n kebab: string;\n camel: string;\n snake: string;\n studly: string;\n };\n\n public constructor(public readonly raw: string) {\n this.parsedData = {\n kebab: toKebabCase(raw),\n camel: toCamelCase(raw),\n snake: toSnakeCase(raw),\n studly: toStudlyCase(raw),\n };\n }\n\n public get plural() {\n return new Name(pluralize(this.raw));\n }\n\n public get singular() {\n return new Name(pluralize.singular(this.raw));\n }\n\n public get pascal(): string {\n return this.parsedData.studly;\n }\n\n public get camel(): string {\n return this.parsedData.camel;\n }\n\n public get kebab(): string {\n return this.parsedData.kebab;\n }\n\n public get snake(): string {\n return this.parsedData.snake;\n }\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,gBAAgB,OAAkD;CAChF,MAAM,QAAQ,MAAM,MAAM,GAAG;CAE7B,IAAI,MAAM,WAAW,GACnB,OAAO,EAAE,MAAM,MAAM,GAAG;CAG1B,OAAO;EACL,QAAQ,MAAM;EACd,MAAM,MAAM,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"name-parser.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/utils/name-parser.ts"],"sourcesContent":["import { toCamelCase, toKebabCase, toSnakeCase, toStudlyCase } from \"@mongez/reinforcements\";\nimport pluralize from \"pluralize-esm\";\nimport type { ParsedName } from \"../types\";\n\n/**\n * Parse module path from input\n * Examples:\n * - \"users/create-user\" → { module: \"users\", name: \"create-user\" }\n * - \"create-user\" → { module: undefined, name: \"create-user\" }\n */\nexport function parseModulePath(input: string): { module?: string; name: string } {\n const parts = input.split(\"/\");\n\n if (parts.length === 1) {\n return { name: parts[0] };\n }\n\n return {\n module: parts[0],\n name: parts.slice(1).join(\"/\"),\n };\n}\n\n/**\n * Parse name into all case variants\n */\n// Returns the Name class, which is what it has always constructed. Annotating it as\n// ParsedName understated the result: Name carries `plural` and `singular` getters that\n// the CRUD stubs need, and a protected member that makes it nominal in practice — so\n// four generators could not pass this value to a stub expecting a Name, even though\n// that is exactly what they had. Name still satisfies ParsedName for callers that only\n// want the cased strings.\nexport function parseName(input: string): Name {\n return new Name(input);\n}\n\n/**\n * Get plural name\n */\nexport function pluralName(name: string) {\n return new Name(pluralize(name));\n}\n\n/**\n * Get singular name\n */\nexport function singularName(name: string) {\n return new Name(pluralize.singular(name));\n}\n\nexport class Name {\n protected parsedData: {\n kebab: string;\n camel: string;\n snake: string;\n studly: string;\n };\n\n public constructor(public readonly raw: string) {\n this.parsedData = {\n kebab: toKebabCase(raw),\n camel: toCamelCase(raw),\n snake: toSnakeCase(raw),\n studly: toStudlyCase(raw),\n };\n }\n\n public get plural() {\n return new Name(pluralize(this.raw));\n }\n\n public get singular() {\n return new Name(pluralize.singular(this.raw));\n }\n\n public get pascal(): string {\n return this.parsedData.studly;\n }\n\n public get camel(): string {\n return this.parsedData.camel;\n }\n\n public get kebab(): string {\n return this.parsedData.kebab;\n }\n\n public get snake(): string {\n return this.parsedData.snake;\n }\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,gBAAgB,OAAkD;CAChF,MAAM,QAAQ,MAAM,MAAM,GAAG;CAE7B,IAAI,MAAM,WAAW,GACnB,OAAO,EAAE,MAAM,MAAM,GAAG;CAG1B,OAAO;EACL,QAAQ,MAAM;EACd,MAAM,MAAM,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;CAC/B;AACF;;;;AAWA,SAAgB,UAAU,OAAqB;CAC7C,OAAO,IAAI,KAAK,KAAK;AACvB;;;;AAKA,SAAgB,WAAW,MAAc;CACvC,OAAO,IAAI,KAAK,UAAU,IAAI,CAAC;AACjC;;;;AAKA,SAAgB,aAAa,MAAc;CACzC,OAAO,IAAI,KAAK,UAAU,SAAS,IAAI,CAAC;AAC1C;AAEA,IAAa,OAAb,MAAa,KAAK;CAQhB,AAAO,YAAY,AAAgB,KAAa;EAAb;EACjC,KAAK,aAAa;GAChB,OAAO,YAAY,GAAG;GACtB,OAAO,YAAY,GAAG;GACtB,OAAO,YAAY,GAAG;GACtB,QAAQ,aAAa,GAAG;EAC1B;CACF;CAEA,IAAW,SAAS;EAClB,OAAO,IAAI,KAAK,UAAU,KAAK,GAAG,CAAC;CACrC;CAEA,IAAW,WAAW;EACpB,OAAO,IAAI,KAAK,UAAU,SAAS,KAAK,GAAG,CAAC;CAC9C;CAEA,IAAW,SAAiB;EAC1B,OAAO,KAAK,WAAW;CACzB;CAEA,IAAW,QAAgB;EACzB,OAAO,KAAK,WAAW;CACzB;CAEA,IAAW,QAAgB;EACzB,OAAO,KAAK,WAAW;CACzB;CAEA,IAAW,QAAgB;EACzB,OAAO,KAAK,WAAW;CACzB;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/utils/prompt.ts"],"sourcesContent":["import readline from \"node:readline\";\n\n/**\n * Prompt user for confirmation\n */\nexport async function confirm(message: string, defaultValue = false): Promise<boolean> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n const defaultText = defaultValue ? \"Y/n\" : \"y/N\";\n rl.question(`${message} (${defaultText}): `, (answer) => {\n rl.close();\n\n if (!answer.trim()) {\n resolve(defaultValue);\n return;\n }\n\n const normalized = answer.toLowerCase().trim();\n resolve(normalized === \"y\" || normalized === \"yes\");\n });\n });\n}\n\n/**\n * Prompt user for text input\n */\nexport async function input(message: string, defaultValue?: string): Promise<string> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n const prompt = defaultValue ? `${message} (${defaultValue}): ` : `${message}: `;\n\n rl.question(prompt, (answer) => {\n rl.close();\n resolve(answer.trim() || defaultValue || \"\");\n });\n });\n}\n\n/**\n * Prompt user to select from choices\n */\nexport async function select(message: string, choices: string[]): Promise<string> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n console.log(message);\n choices.forEach((choice, index) => {\n console.log(` ${index + 1}. ${choice}`);\n });\n\n rl.question(\"Select (1-\" + choices.length + \"): \", (answer) => {\n rl.close();\n\n const index = parseInt(answer.trim(), 10) - 1;\n\n if (index >= 0 && index < choices.length) {\n resolve(choices[index]);\n } else {\n resolve(choices[0]);\n }\n });\n });\n}\n"],"mappings":";;;;;;AAKA,eAAsB,QAAQ,SAAiB,eAAe,OAAyB;CACrF,MAAM,KAAK,SAAS,gBAAgB;EAClC,OAAO,QAAQ;EACf,QAAQ,QAAQ;CAClB,CAAC;CAED,OAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,cAAc,eAAe,QAAQ;EAC3C,GAAG,SAAS,GAAG,QAAQ,IAAI,YAAY,OAAO,WAAW;GACvD,GAAG,MAAM;GAET,IAAI,CAAC,OAAO,KAAK,GAAG;IAClB,QAAQ,YAAY;IACpB;GACF;GAEA,MAAM,aAAa,OAAO,YAAY,
|
|
1
|
+
{"version":3,"file":"prompt.mjs","names":[],"sources":["../../../../../../../../../../core/src/cli/commands/generate/utils/prompt.ts"],"sourcesContent":["import readline from \"node:readline\";\n\n/**\n * Prompt user for confirmation\n */\nexport async function confirm(message: string, defaultValue = false): Promise<boolean> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n const defaultText = defaultValue ? \"Y/n\" : \"y/N\";\n rl.question(`${message} (${defaultText}): `, (answer) => {\n rl.close();\n\n if (!answer.trim()) {\n resolve(defaultValue);\n return;\n }\n\n const normalized = answer.toLowerCase().trim();\n resolve(normalized === \"y\" || normalized === \"yes\");\n });\n });\n}\n\n/**\n * Prompt user for text input\n */\nexport async function input(message: string, defaultValue?: string): Promise<string> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n const prompt = defaultValue ? `${message} (${defaultValue}): ` : `${message}: `;\n\n rl.question(prompt, (answer) => {\n rl.close();\n resolve(answer.trim() || defaultValue || \"\");\n });\n });\n}\n\n/**\n * Prompt user to select from choices\n */\nexport async function select(message: string, choices: string[]): Promise<string> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n return new Promise((resolve) => {\n console.log(message);\n choices.forEach((choice, index) => {\n console.log(` ${index + 1}. ${choice}`);\n });\n\n rl.question(\"Select (1-\" + choices.length + \"): \", (answer) => {\n rl.close();\n\n const index = parseInt(answer.trim(), 10) - 1;\n\n if (index >= 0 && index < choices.length) {\n resolve(choices[index]);\n } else {\n resolve(choices[0]);\n }\n });\n });\n}\n"],"mappings":";;;;;;AAKA,eAAsB,QAAQ,SAAiB,eAAe,OAAyB;CACrF,MAAM,KAAK,SAAS,gBAAgB;EAClC,OAAO,QAAQ;EACf,QAAQ,QAAQ;CAClB,CAAC;CAED,OAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,cAAc,eAAe,QAAQ;EAC3C,GAAG,SAAS,GAAG,QAAQ,IAAI,YAAY,OAAO,WAAW;GACvD,GAAG,MAAM;GAET,IAAI,CAAC,OAAO,KAAK,GAAG;IAClB,QAAQ,YAAY;IACpB;GACF;GAEA,MAAM,aAAa,OAAO,YAAY,CAAC,CAAC,KAAK;GAC7C,QAAQ,eAAe,OAAO,eAAe,KAAK;EACpD,CAAC;CACH,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-page-routes.mjs","names":[],"sources":["../../../../../../../../../core/src/cli/commands/routes/diff-page-routes.ts"],"sourcesContent":["/**\n * The comparison half of `warlock routes:diff`, kept free of the filesystem,\n * the boot and the console so it can be exercised directly.\n *\n * WHAT THE TWO SIDES ACTUALLY ARE\n *\n * `expected` comes from `page-routes.manifest.json`, which `warlock build`\n * writes from a pure FILESYSTEM SCAN of the page graph. No application module\n * is imported, no connector boots, no route is registered.\n *\n * `live` comes from the running router, after real registration.\n *\n * Every difference between the two that is NOT drift has to be removed on the\n * producing side, so this comparison can stay a strict string equality — see\n * `normalizeRoutePath` for the path half. Exactly one property survives that\n * rule and is handled here instead, because it is genuinely unknowable at\n * build time: the route-name method suffix (below).\n */\nimport { matchesDerivedRouteName } from \"../../../router/route-name-method-suffix\";\n\nexport type PageRoute = {\n method: \"GET\";\n path: string;\n name: string;\n source: string;\n};\n\nexport type PageRouteChange = {\n before: PageRoute;\n after: PageRoute;\n};\n\nexport type PageRoutesDiff = {\n changes: PageRouteChange[];\n removed: PageRoute[];\n added: PageRoute[];\n};\n\n/** Stable ordering for printed output: by path, then by name. */\nexport function comparePageRoutes(left: PageRoute, right: PageRoute): number {\n return left.path.localeCompare(right.path) || left.name.localeCompare(right.name);\n}\n\n/**\n * Whether a live route is the same route the build recorded.\n *\n * Method and path must match EXACTLY. The name is compared derived-to-derived:\n * the router appends `.<method>` to a name already claimed by a route of\n * another method (`Router.add`), and which routes get that suffix depends on\n * the registration order across the whole route set — pages and API routes\n * alike. `warlock build` never boots connectors, so the manifest cannot see the\n * API routes that cause the collision and can never record the suffix.\n *\n * Ignoring it here is therefore not leniency, it is reading the manifest for\n * what it is: a record of what each page's name was DERIVED as, not of what it\n * was REGISTERED as. `matchesDerivedRouteName` accepts only the one suffix the\n * router itself could have appended; any other name difference is still drift.\n *\n * THE LIMIT THIS BUYS: a clean diff does not prove that no page name collided\n * at registration. Use `warlock routes` to see the names as registered.\n */\nfunction isSameRoute(expected: PageRoute, live: PageRoute): boolean {\n return (\n live.method === expected.method &&\n live.path === expected.path &&\n matchesDerivedRouteName(expected.name, live.name, live.method)\n );\n}\n\n/**\n * Diff the build-time page route surface against the live one.\n *\n * Neither input is mutated and neither has to be pre-sorted; the returned lists\n * are sorted for printing.\n */\nexport function diffPageRoutes(expected: PageRoute[], live: PageRoute[]): PageRoutesDiff {\n const remainingExpected = new Set(expected);\n const remainingLive = new Set(live);\n const changes: PageRouteChange[] = [];\n\n // Route identity is the comparison contract. A differing source is displayed\n // only as context and must not make an otherwise identical route drift (a\n // checkout can be relocated without changing its surface).\n for (const before of expected) {\n const after = [...remainingLive].find((candidate) => isSameRoute(before, candidate));\n\n if (after) {\n remainingExpected.delete(before);\n remainingLive.delete(after);\n }\n }\n\n // A stable source is diagnostic context, never itself a diff. It only groups\n // a changed page into one useful line instead of a removal plus an addition.\n for (const before of remainingExpected) {\n const after = [...remainingLive].find(\n (candidate) => candidate.source !== \"\" && candidate.source === before.source,\n );\n\n if (after && !isSameRoute(before, after)) {\n changes.push({ before, after });\n remainingExpected.delete(before);\n remainingLive.delete(after);\n }\n }\n\n return {\n changes: changes.sort((left, right) => comparePageRoutes(left.before, right.before)),\n removed: [...remainingExpected].sort(comparePageRoutes),\n added: [...remainingLive].sort(comparePageRoutes),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,kBAAkB,MAAiB,OAA0B;CAC3E,OAAO,KAAK,KAAK,cAAc,MAAM,IAAI,KAAK,KAAK,KAAK,cAAc,MAAM,IAAI;AAClF;;;;;;;;;;;;;;;;;;;AAoBA,SAAS,YAAY,UAAqB,MAA0B;CAClE,OACE,KAAK,WAAW,SAAS,UACzB,KAAK,SAAS,SAAS,QACvB,wBAAwB,SAAS,MAAM,KAAK,MAAM,KAAK,MAAM;AAEjE;;;;;;;AAQA,SAAgB,eAAe,UAAuB,MAAmC;CACvF,MAAM,oBAAoB,IAAI,IAAI,QAAQ;CAC1C,MAAM,gBAAgB,IAAI,IAAI,IAAI;CAClC,MAAM,UAA6B,CAAC;CAKpC,KAAK,MAAM,UAAU,UAAU;EAC7B,MAAM,QAAQ,CAAC,GAAG,aAAa,
|
|
1
|
+
{"version":3,"file":"diff-page-routes.mjs","names":[],"sources":["../../../../../../../../../core/src/cli/commands/routes/diff-page-routes.ts"],"sourcesContent":["/**\n * The comparison half of `warlock routes:diff`, kept free of the filesystem,\n * the boot and the console so it can be exercised directly.\n *\n * WHAT THE TWO SIDES ACTUALLY ARE\n *\n * `expected` comes from `page-routes.manifest.json`, which `warlock build`\n * writes from a pure FILESYSTEM SCAN of the page graph. No application module\n * is imported, no connector boots, no route is registered.\n *\n * `live` comes from the running router, after real registration.\n *\n * Every difference between the two that is NOT drift has to be removed on the\n * producing side, so this comparison can stay a strict string equality — see\n * `normalizeRoutePath` for the path half. Exactly one property survives that\n * rule and is handled here instead, because it is genuinely unknowable at\n * build time: the route-name method suffix (below).\n */\nimport { matchesDerivedRouteName } from \"../../../router/route-name-method-suffix\";\n\nexport type PageRoute = {\n method: \"GET\";\n path: string;\n name: string;\n source: string;\n};\n\nexport type PageRouteChange = {\n before: PageRoute;\n after: PageRoute;\n};\n\nexport type PageRoutesDiff = {\n changes: PageRouteChange[];\n removed: PageRoute[];\n added: PageRoute[];\n};\n\n/** Stable ordering for printed output: by path, then by name. */\nexport function comparePageRoutes(left: PageRoute, right: PageRoute): number {\n return left.path.localeCompare(right.path) || left.name.localeCompare(right.name);\n}\n\n/**\n * Whether a live route is the same route the build recorded.\n *\n * Method and path must match EXACTLY. The name is compared derived-to-derived:\n * the router appends `.<method>` to a name already claimed by a route of\n * another method (`Router.add`), and which routes get that suffix depends on\n * the registration order across the whole route set — pages and API routes\n * alike. `warlock build` never boots connectors, so the manifest cannot see the\n * API routes that cause the collision and can never record the suffix.\n *\n * Ignoring it here is therefore not leniency, it is reading the manifest for\n * what it is: a record of what each page's name was DERIVED as, not of what it\n * was REGISTERED as. `matchesDerivedRouteName` accepts only the one suffix the\n * router itself could have appended; any other name difference is still drift.\n *\n * THE LIMIT THIS BUYS: a clean diff does not prove that no page name collided\n * at registration. Use `warlock routes` to see the names as registered.\n */\nfunction isSameRoute(expected: PageRoute, live: PageRoute): boolean {\n return (\n live.method === expected.method &&\n live.path === expected.path &&\n matchesDerivedRouteName(expected.name, live.name, live.method)\n );\n}\n\n/**\n * Diff the build-time page route surface against the live one.\n *\n * Neither input is mutated and neither has to be pre-sorted; the returned lists\n * are sorted for printing.\n */\nexport function diffPageRoutes(expected: PageRoute[], live: PageRoute[]): PageRoutesDiff {\n const remainingExpected = new Set(expected);\n const remainingLive = new Set(live);\n const changes: PageRouteChange[] = [];\n\n // Route identity is the comparison contract. A differing source is displayed\n // only as context and must not make an otherwise identical route drift (a\n // checkout can be relocated without changing its surface).\n for (const before of expected) {\n const after = [...remainingLive].find((candidate) => isSameRoute(before, candidate));\n\n if (after) {\n remainingExpected.delete(before);\n remainingLive.delete(after);\n }\n }\n\n // A stable source is diagnostic context, never itself a diff. It only groups\n // a changed page into one useful line instead of a removal plus an addition.\n for (const before of remainingExpected) {\n const after = [...remainingLive].find(\n (candidate) => candidate.source !== \"\" && candidate.source === before.source,\n );\n\n if (after && !isSameRoute(before, after)) {\n changes.push({ before, after });\n remainingExpected.delete(before);\n remainingLive.delete(after);\n }\n }\n\n return {\n changes: changes.sort((left, right) => comparePageRoutes(left.before, right.before)),\n removed: [...remainingExpected].sort(comparePageRoutes),\n added: [...remainingLive].sort(comparePageRoutes),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,kBAAkB,MAAiB,OAA0B;CAC3E,OAAO,KAAK,KAAK,cAAc,MAAM,IAAI,KAAK,KAAK,KAAK,cAAc,MAAM,IAAI;AAClF;;;;;;;;;;;;;;;;;;;AAoBA,SAAS,YAAY,UAAqB,MAA0B;CAClE,OACE,KAAK,WAAW,SAAS,UACzB,KAAK,SAAS,SAAS,QACvB,wBAAwB,SAAS,MAAM,KAAK,MAAM,KAAK,MAAM;AAEjE;;;;;;;AAQA,SAAgB,eAAe,UAAuB,MAAmC;CACvF,MAAM,oBAAoB,IAAI,IAAI,QAAQ;CAC1C,MAAM,gBAAgB,IAAI,IAAI,IAAI;CAClC,MAAM,UAA6B,CAAC;CAKpC,KAAK,MAAM,UAAU,UAAU;EAC7B,MAAM,QAAQ,CAAC,GAAG,aAAa,CAAC,CAAC,MAAM,cAAc,YAAY,QAAQ,SAAS,CAAC;EAEnF,IAAI,OAAO;GACT,kBAAkB,OAAO,MAAM;GAC/B,cAAc,OAAO,KAAK;EAC5B;CACF;CAIA,KAAK,MAAM,UAAU,mBAAmB;EACtC,MAAM,QAAQ,CAAC,GAAG,aAAa,CAAC,CAAC,MAC9B,cAAc,UAAU,WAAW,MAAM,UAAU,WAAW,OAAO,MACxE;EAEA,IAAI,SAAS,CAAC,YAAY,QAAQ,KAAK,GAAG;GACxC,QAAQ,KAAK;IAAE;IAAQ;GAAM,CAAC;GAC9B,kBAAkB,OAAO,MAAM;GAC/B,cAAc,OAAO,KAAK;EAC5B;CACF;CAEA,OAAO;EACL,SAAS,QAAQ,MAAM,MAAM,UAAU,kBAAkB,KAAK,QAAQ,MAAM,MAAM,CAAC;EACnF,SAAS,CAAC,GAAG,iBAAiB,CAAC,CAAC,KAAK,iBAAiB;EACtD,OAAO,CAAC,GAAG,aAAa,CAAC,CAAC,KAAK,iBAAiB;CAClD;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-routes-table.mjs","names":[],"sources":["../../../../../../../../../core/src/cli/commands/routes/format-routes-table.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { methodRank, type RouteRow } from \"./route-row\";\n\n/** The columns rendered, in order, with their header labels. */\nconst COLUMNS = [\n { key: \"method\", header: \"METHOD\" },\n { key: \"path\", header: \"PATH\" },\n { key: \"name\", header: \"NAME\" },\n { key: \"action\", header: \"ACTION\" },\n { key: \"middleware\", header: \"MW\" },\n { key: \"source\", header: \"SOURCE\" },\n] as const;\n\ntype ColumnKey = (typeof COLUMNS)[number][\"key\"];\n\n/** Two spaces between columns. */\nconst GAP = \" \";\n\n/** Shown for an empty string cell so the column doesn't read as a gap. */\nconst EMPTY_CELL = \"—\";\n\n/**\n * The plain (uncolored) text of one cell. Numbers render as-is; an empty string\n * renders as {@link EMPTY_CELL}. This is the single source of truth for both\n * the width math and the rendered text, so colored and uncolored output stay\n * aligned.\n *\n * @param row - The row\n * @param key - The column key\n * @returns The cell's printable text\n */\nfunction cellText(row: RouteRow, key: ColumnKey): string {\n if (key === \"middleware\") return String(row.middleware);\n\n const value = row[key] as string;\n\n return value.length > 0 ? value : EMPTY_CELL;\n}\n\n/**\n * Per-column display width: the longest of the header and every cell.\n *\n * @param rows - The rows to measure\n * @returns One width per column, in column order\n */\nfunction columnWidths(rows: RouteRow[]): number[] {\n return COLUMNS.map((column) =>\n Math.max(column.header.length, ...rows.map((row) => cellText(row, column.key).length)),\n );\n}\n\n/**\n * Build a one-line, human-readable summary: the total plus a per-method\n * breakdown ordered by {@link methodRank}, e.g.\n * `42 routes (38 GET · 2 POST · 1 PUT · 1 DELETE)`.\n *\n * @param rows - The rows being summarized\n * @returns The summary line\n */\nexport function routesSummary(rows: RouteRow[]): string {\n const noun = rows.length === 1 ? \"route\" : \"routes\";\n\n if (rows.length === 0) return `0 ${noun}`;\n\n const counts = new Map<string, number>();\n\n for (const row of rows) {\n counts.set(row.method, (counts.get(row.method) ?? 0) + 1);\n }\n\n const breakdown = [...counts.entries()]\n .sort(([a], [b]) => methodRank(a) - methodRank(b))\n .map(([method, count]) => `${count} ${method}`)\n .join(\" · \");\n\n return `${rows.length} ${noun} (${breakdown})`;\n}\n\n/**\n * Render the routes table as an ordered list of plain (uncolored) lines:\n * a header row, one row per route, a blank line, then the summary. Returned as\n * strings (not printed) so the layout is unit-testable without ANSI noise.\n *\n * @param rows - The rows to render (already filtered/sorted by the caller)\n * @returns The lines to print, top to bottom\n */\nexport function formatRoutesTableLines(rows: RouteRow[]): string[] {\n if (rows.length === 0) {\n return [\"No routes registered.\"];\n }\n\n const widths = columnWidths(rows);\n\n const renderRow = (cells: string[]) =>\n cells.map((text, index) => text.padEnd(widths[index])).join(GAP).trimEnd();\n\n const lines = [renderRow(COLUMNS.map((column) => column.header))];\n\n for (const row of rows) {\n lines.push(renderRow(COLUMNS.map((column) => cellText(row, column.key))));\n }\n\n lines.push(\"\");\n lines.push(routesSummary(rows));\n\n return lines;\n}\n\n/** Per-verb color for the METHOD column. Unknown verbs fall back to white. */\nconst METHOD_COLOR: Record<string, (input: string) => string> = {\n GET: colors.green,\n POST: colors.blue,\n PUT: colors.yellow,\n PATCH: colors.yellow,\n DELETE: colors.red,\n OPTIONS: colors.gray,\n HEAD: colors.gray,\n ALL: colors.magenta,\n};\n\n/**\n * Colorize the method label, then right-pad with plain spaces so column\n * alignment is computed from the visible width, not the ANSI-coded length.\n *\n * @param method - The uppercased HTTP verb\n * @param width - The METHOD column width\n * @returns The colored, padded cell\n */\nfunction colorizeMethodCell(method: string, width: number): string {\n const colorize = METHOD_COLOR[method] ?? colors.white;\n\n return colorize(method) + \" \".repeat(Math.max(0, width - method.length));\n}\n\n/**\n * Print the routes table to stdout with a bold header, verb-colored METHOD\n * column, and a dimmed summary. The empty case prints a single yellow notice\n * (a booted app with zero routes usually means a route module failed to load).\n *\n * Thin presentation wrapper over {@link formatRoutesTableLines} /\n * {@link routesSummary} — those stay the source of truth for layout.\n *\n * @param rows - The rows to print (already filtered/sorted by the caller)\n */\nexport function printRoutesTable(rows: RouteRow[]): void {\n if (rows.length === 0) {\n console.log(colors.yellow(\"No routes registered — did a route module fail to load?\"));\n return;\n }\n\n const widths = columnWidths(rows);\n\n const headerLine = COLUMNS.map((column, index) => column.header.padEnd(widths[index]))\n .join(GAP)\n .trimEnd();\n\n console.log(colors.bold(headerLine));\n\n for (const row of rows) {\n const cells = COLUMNS.map((column, index) => {\n if (column.key === \"method\") {\n return colorizeMethodCell(row.method, widths[index]);\n }\n\n return cellText(row, column.key).padEnd(widths[index]);\n });\n\n console.log(cells.join(GAP).trimEnd());\n }\n\n console.log(\"\");\n console.log(colors.gray(routesSummary(rows)));\n}\n"],"mappings":";;;;;AAIA,MAAM,UAAU;CACd;EAAE,KAAK;EAAU,QAAQ;CAAS;CAClC;EAAE,KAAK;EAAQ,QAAQ;CAAO;CAC9B;EAAE,KAAK;EAAQ,QAAQ;CAAO;CAC9B;EAAE,KAAK;EAAU,QAAQ;CAAS;CAClC;EAAE,KAAK;EAAc,QAAQ;CAAK;CAClC;EAAE,KAAK;EAAU,QAAQ;CAAS;AACpC;;AAKA,MAAM,MAAM;;AAGZ,MAAM,aAAa;;;;;;;;;;;AAYnB,SAAS,SAAS,KAAe,KAAwB;CACvD,IAAI,QAAQ,cAAc,OAAO,OAAO,IAAI,UAAU;CAEtD,MAAM,QAAQ,IAAI;CAElB,OAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;;;;;;;AAQA,SAAS,aAAa,MAA4B;CAChD,OAAO,QAAQ,KAAK,WAClB,KAAK,IAAI,OAAO,OAAO,QAAQ,GAAG,KAAK,KAAK,QAAQ,SAAS,KAAK,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"format-routes-table.mjs","names":[],"sources":["../../../../../../../../../core/src/cli/commands/routes/format-routes-table.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { methodRank, type RouteRow } from \"./route-row\";\n\n/** The columns rendered, in order, with their header labels. */\nconst COLUMNS = [\n { key: \"method\", header: \"METHOD\" },\n { key: \"path\", header: \"PATH\" },\n { key: \"name\", header: \"NAME\" },\n { key: \"action\", header: \"ACTION\" },\n { key: \"middleware\", header: \"MW\" },\n { key: \"source\", header: \"SOURCE\" },\n] as const;\n\ntype ColumnKey = (typeof COLUMNS)[number][\"key\"];\n\n/** Two spaces between columns. */\nconst GAP = \" \";\n\n/** Shown for an empty string cell so the column doesn't read as a gap. */\nconst EMPTY_CELL = \"—\";\n\n/**\n * The plain (uncolored) text of one cell. Numbers render as-is; an empty string\n * renders as {@link EMPTY_CELL}. This is the single source of truth for both\n * the width math and the rendered text, so colored and uncolored output stay\n * aligned.\n *\n * @param row - The row\n * @param key - The column key\n * @returns The cell's printable text\n */\nfunction cellText(row: RouteRow, key: ColumnKey): string {\n if (key === \"middleware\") return String(row.middleware);\n\n const value = row[key] as string;\n\n return value.length > 0 ? value : EMPTY_CELL;\n}\n\n/**\n * Per-column display width: the longest of the header and every cell.\n *\n * @param rows - The rows to measure\n * @returns One width per column, in column order\n */\nfunction columnWidths(rows: RouteRow[]): number[] {\n return COLUMNS.map((column) =>\n Math.max(column.header.length, ...rows.map((row) => cellText(row, column.key).length)),\n );\n}\n\n/**\n * Build a one-line, human-readable summary: the total plus a per-method\n * breakdown ordered by {@link methodRank}, e.g.\n * `42 routes (38 GET · 2 POST · 1 PUT · 1 DELETE)`.\n *\n * @param rows - The rows being summarized\n * @returns The summary line\n */\nexport function routesSummary(rows: RouteRow[]): string {\n const noun = rows.length === 1 ? \"route\" : \"routes\";\n\n if (rows.length === 0) return `0 ${noun}`;\n\n const counts = new Map<string, number>();\n\n for (const row of rows) {\n counts.set(row.method, (counts.get(row.method) ?? 0) + 1);\n }\n\n const breakdown = [...counts.entries()]\n .sort(([a], [b]) => methodRank(a) - methodRank(b))\n .map(([method, count]) => `${count} ${method}`)\n .join(\" · \");\n\n return `${rows.length} ${noun} (${breakdown})`;\n}\n\n/**\n * Render the routes table as an ordered list of plain (uncolored) lines:\n * a header row, one row per route, a blank line, then the summary. Returned as\n * strings (not printed) so the layout is unit-testable without ANSI noise.\n *\n * @param rows - The rows to render (already filtered/sorted by the caller)\n * @returns The lines to print, top to bottom\n */\nexport function formatRoutesTableLines(rows: RouteRow[]): string[] {\n if (rows.length === 0) {\n return [\"No routes registered.\"];\n }\n\n const widths = columnWidths(rows);\n\n const renderRow = (cells: string[]) =>\n cells.map((text, index) => text.padEnd(widths[index])).join(GAP).trimEnd();\n\n const lines = [renderRow(COLUMNS.map((column) => column.header))];\n\n for (const row of rows) {\n lines.push(renderRow(COLUMNS.map((column) => cellText(row, column.key))));\n }\n\n lines.push(\"\");\n lines.push(routesSummary(rows));\n\n return lines;\n}\n\n/** Per-verb color for the METHOD column. Unknown verbs fall back to white. */\nconst METHOD_COLOR: Record<string, (input: string) => string> = {\n GET: colors.green,\n POST: colors.blue,\n PUT: colors.yellow,\n PATCH: colors.yellow,\n DELETE: colors.red,\n OPTIONS: colors.gray,\n HEAD: colors.gray,\n ALL: colors.magenta,\n};\n\n/**\n * Colorize the method label, then right-pad with plain spaces so column\n * alignment is computed from the visible width, not the ANSI-coded length.\n *\n * @param method - The uppercased HTTP verb\n * @param width - The METHOD column width\n * @returns The colored, padded cell\n */\nfunction colorizeMethodCell(method: string, width: number): string {\n const colorize = METHOD_COLOR[method] ?? colors.white;\n\n return colorize(method) + \" \".repeat(Math.max(0, width - method.length));\n}\n\n/**\n * Print the routes table to stdout with a bold header, verb-colored METHOD\n * column, and a dimmed summary. The empty case prints a single yellow notice\n * (a booted app with zero routes usually means a route module failed to load).\n *\n * Thin presentation wrapper over {@link formatRoutesTableLines} /\n * {@link routesSummary} — those stay the source of truth for layout.\n *\n * @param rows - The rows to print (already filtered/sorted by the caller)\n */\nexport function printRoutesTable(rows: RouteRow[]): void {\n if (rows.length === 0) {\n console.log(colors.yellow(\"No routes registered — did a route module fail to load?\"));\n return;\n }\n\n const widths = columnWidths(rows);\n\n const headerLine = COLUMNS.map((column, index) => column.header.padEnd(widths[index]))\n .join(GAP)\n .trimEnd();\n\n console.log(colors.bold(headerLine));\n\n for (const row of rows) {\n const cells = COLUMNS.map((column, index) => {\n if (column.key === \"method\") {\n return colorizeMethodCell(row.method, widths[index]);\n }\n\n return cellText(row, column.key).padEnd(widths[index]);\n });\n\n console.log(cells.join(GAP).trimEnd());\n }\n\n console.log(\"\");\n console.log(colors.gray(routesSummary(rows)));\n}\n"],"mappings":";;;;;AAIA,MAAM,UAAU;CACd;EAAE,KAAK;EAAU,QAAQ;CAAS;CAClC;EAAE,KAAK;EAAQ,QAAQ;CAAO;CAC9B;EAAE,KAAK;EAAQ,QAAQ;CAAO;CAC9B;EAAE,KAAK;EAAU,QAAQ;CAAS;CAClC;EAAE,KAAK;EAAc,QAAQ;CAAK;CAClC;EAAE,KAAK;EAAU,QAAQ;CAAS;AACpC;;AAKA,MAAM,MAAM;;AAGZ,MAAM,aAAa;;;;;;;;;;;AAYnB,SAAS,SAAS,KAAe,KAAwB;CACvD,IAAI,QAAQ,cAAc,OAAO,OAAO,IAAI,UAAU;CAEtD,MAAM,QAAQ,IAAI;CAElB,OAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;;;;;;;AAQA,SAAS,aAAa,MAA4B;CAChD,OAAO,QAAQ,KAAK,WAClB,KAAK,IAAI,OAAO,OAAO,QAAQ,GAAG,KAAK,KAAK,QAAQ,SAAS,KAAK,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,CACvF;AACF;;;;;;;;;AAUA,SAAgB,cAAc,MAA0B;CACtD,MAAM,OAAO,KAAK,WAAW,IAAI,UAAU;CAE3C,IAAI,KAAK,WAAW,GAAG,OAAO,KAAK;CAEnC,MAAM,yBAAS,IAAI,IAAoB;CAEvC,KAAK,MAAM,OAAO,MAChB,OAAO,IAAI,IAAI,SAAS,OAAO,IAAI,IAAI,MAAM,KAAK,KAAK,CAAC;CAG1D,MAAM,YAAY,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CACpC,MAAM,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CACjD,KAAK,CAAC,QAAQ,WAAW,GAAG,MAAM,GAAG,QAAQ,CAAC,CAC9C,KAAK,KAAK;CAEb,OAAO,GAAG,KAAK,OAAO,GAAG,KAAK,IAAI,UAAU;AAC9C;;AAiCA,MAAM,eAA0D;CAC9D,KAAK,OAAO;CACZ,MAAM,OAAO;CACb,KAAK,OAAO;CACZ,OAAO,OAAO;CACd,QAAQ,OAAO;CACf,SAAS,OAAO;CAChB,MAAM,OAAO;CACb,KAAK,OAAO;AACd;;;;;;;;;AAUA,SAAS,mBAAmB,QAAgB,OAAuB;CAGjE,QAFiB,aAAa,WAAW,OAAO,MAEjC,CAAC,MAAM,IAAI,IAAI,OAAO,KAAK,IAAI,GAAG,QAAQ,OAAO,MAAM,CAAC;AACzE;;;;;;;;;;;AAYA,SAAgB,iBAAiB,MAAwB;CACvD,IAAI,KAAK,WAAW,GAAG;EACrB,QAAQ,IAAI,OAAO,OAAO,yDAAyD,CAAC;EACpF;CACF;CAEA,MAAM,SAAS,aAAa,IAAI;CAEhC,MAAM,aAAa,QAAQ,KAAK,QAAQ,UAAU,OAAO,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,CACnF,KAAK,GAAG,CAAC,CACT,QAAQ;CAEX,QAAQ,IAAI,OAAO,KAAK,UAAU,CAAC;CAEnC,KAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,QAAQ,KAAK,QAAQ,UAAU;GAC3C,IAAI,OAAO,QAAQ,UACjB,OAAO,mBAAmB,IAAI,QAAQ,OAAO,MAAM;GAGrD,OAAO,SAAS,KAAK,OAAO,GAAG,CAAC,CAAC,OAAO,OAAO,MAAM;EACvD,CAAC;EAED,QAAQ,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC;CACvC;CAEA,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,OAAO,KAAK,cAAc,IAAI,CAAC,CAAC;AAC9C"}
|