@warlock.js/core 5.30.0 → 5.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +142 -36
- package/README.md +28 -23
- package/bin/source-load-hook.mjs +63 -0
- package/bin/warlock.js +102 -42
- package/esm/cli/commands/add.command.mjs +4 -2
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/agents-md/agents-md-command.action.mjs +69 -0
- package/esm/cli/commands/agents-md/agents-md-command.action.mjs.map +1 -0
- package/esm/cli/commands/agents-md.command.mjs +38 -0
- package/esm/cli/commands/agents-md.command.mjs.map +1 -0
- package/esm/cli/commands/board/board-command.action.mjs +279 -0
- package/esm/cli/commands/board/board-command.action.mjs.map +1 -0
- package/esm/cli/commands/board.command.mjs +78 -0
- package/esm/cli/commands/board.command.mjs.map +1 -0
- package/esm/cli/commands/build.command.mjs +6 -1
- package/esm/cli/commands/build.command.mjs.map +1 -1
- package/esm/cli/commands/check/check-command.action.mjs +57 -0
- package/esm/cli/commands/check/check-command.action.mjs.map +1 -0
- package/esm/cli/commands/check/child-typecheck.mjs +66 -0
- package/esm/cli/commands/check/child-typecheck.mjs.map +1 -0
- package/esm/cli/commands/check/format-problems.mjs +43 -0
- package/esm/cli/commands/check/format-problems.mjs.map +1 -0
- package/esm/cli/commands/check/typecheck-command.action.mjs +61 -0
- package/esm/cli/commands/check/typecheck-command.action.mjs.map +1 -0
- package/esm/cli/commands/check.command.mjs +50 -0
- package/esm/cli/commands/check.command.mjs.map +1 -0
- package/esm/cli/commands/dev-server.command.mjs +9 -1
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs +8 -3
- package/esm/cli/commands/doctor/boot-for-diagnostics.mjs.map +1 -1
- package/esm/cli/commands/doctor/check.types.d.mts +76 -0
- package/esm/cli/commands/doctor/check.types.d.mts.map +1 -0
- package/esm/cli/commands/doctor/checks/config.check.mjs +1 -0
- package/esm/cli/commands/doctor/checks/config.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/connectors.check.mjs +2 -0
- package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/handler-signature.check.mjs +1 -0
- package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/health.check.mjs +3 -0
- package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs +1 -0
- package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs +1 -0
- package/esm/cli/commands/doctor/checks/locales-ts-deprecated.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +1 -0
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs +4 -0
- package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/routes.check.mjs +3 -0
- package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -1
- package/esm/cli/commands/doctor/doctor-command.action.mjs +14 -6
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
- package/esm/cli/commands/doctor/format-report.mjs +30 -2
- package/esm/cli/commands/doctor/format-report.mjs.map +1 -1
- package/esm/cli/commands/doctor.command.mjs +4 -0
- package/esm/cli/commands/doctor.command.mjs.map +1 -1
- package/esm/cli/commands/explain/explain-command.action.mjs +64 -0
- package/esm/cli/commands/explain/explain-command.action.mjs.map +1 -0
- package/esm/cli/commands/explain.command.mjs +23 -0
- package/esm/cli/commands/explain.command.mjs.map +1 -0
- package/esm/cli/commands/generate/generate.command.mjs +124 -124
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/endpoint.generator.mjs +311 -0
- package/esm/cli/commands/generate/generators/endpoint.generator.mjs.map +1 -0
- package/esm/cli/commands/generate/generators/migration.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs +1 -1
- package/esm/cli/commands/generate/templates/endpoint-stubs.mjs +341 -0
- package/esm/cli/commands/generate/templates/endpoint-stubs.mjs.map +1 -0
- package/esm/cli/commands/generate/templates/stubs.mjs +19 -6
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/endpoint-schema.mjs +137 -0
- package/esm/cli/commands/generate/utils/endpoint-schema.mjs.map +1 -0
- package/esm/cli/commands/generate/utils/name-parser.mjs +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/generate/utils/writer.mjs +38 -13
- package/esm/cli/commands/generate/utils/writer.mjs.map +1 -1
- package/esm/cli/commands/inspect/inspect-command.action.mjs +72 -0
- package/esm/cli/commands/inspect/inspect-command.action.mjs.map +1 -0
- package/esm/cli/commands/inspect.command.mjs +39 -0
- package/esm/cli/commands/inspect.command.mjs.map +1 -0
- package/esm/cli/commands/mcp/mcp-command.action.mjs +103 -0
- package/esm/cli/commands/mcp/mcp-command.action.mjs.map +1 -0
- package/esm/cli/commands/mcp.command.mjs +76 -0
- package/esm/cli/commands/mcp.command.mjs.map +1 -0
- package/esm/cli/commands/openapi-generator.action.mjs.map +1 -1
- package/esm/cli/commands/openapi-generator.command.mjs.map +1 -1
- package/esm/cli/commands/postman-generator.action.mjs.map +1 -1
- package/esm/cli/commands/postman-generator.command.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-diff.action.mjs +1 -1
- package/esm/cli/commands/test.command.mjs +131 -0
- package/esm/cli/commands/test.command.mjs.map +1 -0
- package/esm/cli/commands/trace/trace-command.action.mjs +145 -0
- package/esm/cli/commands/trace/trace-command.action.mjs.map +1 -0
- package/esm/cli/commands/trace.command.mjs +60 -0
- package/esm/cli/commands/trace.command.mjs.map +1 -0
- package/esm/cli/commands/typecheck.command.mjs +40 -0
- package/esm/cli/commands/typecheck.command.mjs.map +1 -0
- package/esm/cli/commands/upgrade.command.mjs +46 -0
- package/esm/cli/commands/upgrade.command.mjs.map +1 -0
- package/esm/cli/flush-and-exit.mjs +34 -0
- package/esm/cli/flush-and-exit.mjs.map +1 -0
- package/esm/cli/framework-cli-commands.mjs +23 -1
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/cli/parse-cli-args.mjs +1 -0
- package/esm/cli/parse-cli-args.mjs.map +1 -1
- package/esm/config/config-loader.d.mts.map +1 -1
- package/esm/config/config-loader.mjs +34 -13
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/config/config-schema.d.mts +67 -0
- package/esm/config/config-schema.d.mts.map +1 -0
- package/esm/config/config-schema.mjs +77 -0
- package/esm/config/config-schema.mjs.map +1 -0
- package/esm/config/index.d.mts +6 -0
- package/esm/config/index.mjs +1 -0
- package/esm/connectors/assert-no-reserved-connector-names.d.mts.map +1 -1
- package/esm/connectors/assert-no-reserved-connector-names.mjs +2 -1
- package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -1
- package/esm/connectors/assert-unique-connector-names.d.mts.map +1 -1
- package/esm/connectors/assert-unique-connector-names.mjs +26 -2
- package/esm/connectors/assert-unique-connector-names.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +3 -2
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/single-server-warnings.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/container/index.d.mts.map +1 -1
- package/esm/container/index.mjs.map +1 -1
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/models/database-log/database-log.d.mts +0 -28
- package/esm/database/models/database-log/database-log.d.mts.map +1 -1
- package/esm/database/seeds/seeder.errors.d.mts +4 -0
- package/esm/database/seeds/seeder.errors.d.mts.map +1 -1
- package/esm/database/seeds/seeder.errors.mjs +2 -0
- package/esm/database/seeds/seeder.errors.mjs.map +1 -1
- package/esm/dev-server/boot-precondition-error.mjs +1 -0
- package/esm/dev-server/boot-precondition-error.mjs.map +1 -1
- package/esm/dev-server/create-worker.mjs +15 -9
- package/esm/dev-server/create-worker.mjs.map +1 -1
- package/esm/dev-server/dev-agent-tooling.mjs +135 -0
- package/esm/dev-server/dev-agent-tooling.mjs.map +1 -0
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +8 -0
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +4 -5
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs +1 -1
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +2 -3
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs +33 -0
- package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs.map +1 -0
- package/esm/dev-server/health-checker/default-health-checkers.mjs +31 -0
- package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -0
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +9 -3
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
- package/esm/dev-server/health-checker/files-healthcare.manager.mjs +15 -4
- package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
- package/esm/dev-server/health-checker/oxlint-runner.mjs +302 -0
- package/esm/dev-server/health-checker/oxlint-runner.mjs.map +1 -0
- package/esm/dev-server/health-checker/workers/oxlint-health.worker.d.mts +1 -0
- package/esm/dev-server/health-checker/workers/oxlint-health.worker.mjs +42 -0
- package/esm/dev-server/health-checker/workers/oxlint-health.worker.mjs.map +1 -0
- package/esm/dev-server/loader/loader-port-acks.mjs +1 -1
- package/esm/dev-server/loader/loader-port-acks.mjs.map +1 -1
- package/esm/dev-server/loader/register-loader.mjs +1 -1
- package/esm/dev-server/loader/register-loader.mjs.map +1 -1
- package/esm/dev-server/loader/resolve-hook.mjs +1 -1
- package/esm/dev-server/loader/transpile-cache.mjs +1 -1
- package/esm/dev-server/model-resource-typings.mjs.map +1 -1
- package/esm/dev-server/module-loader.mjs +1 -0
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs +1 -1
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/route-types-publisher.mjs +1 -1
- package/esm/dev-server/start-development-server.mjs.map +1 -1
- package/esm/dev-server/translation-keys-sources.mjs +1 -1
- package/esm/dev-server/translation-keys-sources.mjs.map +1 -1
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +3 -3
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/dev-server/types.d.mts.map +1 -1
- package/esm/errors/container-key-missing-error.mjs +1 -0
- package/esm/errors/container-key-missing-error.mjs.map +1 -1
- package/esm/errors/error-codes.d.mts +653 -0
- package/esm/errors/error-codes.d.mts.map +1 -0
- package/esm/errors/error-codes.mjs +634 -0
- package/esm/errors/error-codes.mjs.map +1 -0
- package/esm/errors/esbuild-binary-missing-error.mjs +1 -0
- package/esm/errors/esbuild-binary-missing-error.mjs.map +1 -1
- package/esm/errors/index.d.mts +3 -1
- package/esm/errors/index.mjs +3 -1
- package/esm/errors/unknown-locale-error.mjs +1 -0
- package/esm/errors/unknown-locale-error.mjs.map +1 -1
- package/esm/errors/warlock-error.d.mts +23 -0
- package/esm/errors/warlock-error.d.mts.map +1 -0
- package/esm/errors/warlock-error.mjs +22 -0
- package/esm/errors/warlock-error.mjs.map +1 -0
- package/esm/generations/add-command.action.mjs +19 -3
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/features/access.feature.mjs +1 -1
- package/esm/generations/features/access.feature.mjs.map +1 -1
- package/esm/generations/features/bull-board.feature.mjs +1 -1
- package/esm/generations/features/bull-board.feature.mjs.map +1 -1
- package/esm/generations/features/mysql.feature.mjs.map +1 -1
- package/esm/generations/features/notifications.feature.mjs +5 -2
- package/esm/generations/features/notifications.feature.mjs.map +1 -1
- package/esm/generations/features/queue.feature.mjs +1 -1
- package/esm/generations/features/queue.feature.mjs.map +1 -1
- package/esm/generations/features/react-email.feature.mjs +1 -1
- package/esm/generations/features/react-email.feature.mjs.map +1 -1
- package/esm/generations/features/shadcn.feature.mjs +1 -1
- package/esm/generations/features/shadcn.feature.mjs.map +1 -1
- package/esm/generations/features/shared/link-ai-package-import.mjs +1 -1
- package/esm/generations/features/shared/link-ai-package-import.mjs.map +1 -1
- package/esm/generations/features/shared/merge-web-sitemap-config.mjs.map +1 -1
- package/esm/generations/features/shared/patch-tsconfig-include.mjs +1 -1
- package/esm/generations/features/shared/patch-tsconfig-include.mjs.map +1 -1
- package/esm/generations/features/shared/relocate-conflicting-home-route.mjs +1 -1
- package/esm/generations/features/shared/relocate-conflicting-home-route.mjs.map +1 -1
- package/esm/generations/features/sitemap.feature.mjs +1 -1
- package/esm/generations/features/sitemap.feature.mjs.map +1 -1
- package/esm/generations/features/tailwind.feature.mjs +1 -1
- package/esm/generations/features/tailwind.feature.mjs.map +1 -1
- package/esm/generations/features/test.feature.mjs +11 -6
- package/esm/generations/features/test.feature.mjs.map +1 -1
- package/esm/generations/features/web.feature.mjs +1 -1
- package/esm/generations/features/web.feature.mjs.map +1 -1
- package/esm/generations/generation-fs.mjs +73 -0
- package/esm/generations/generation-fs.mjs.map +1 -0
- package/esm/generations/generation-run.mjs +185 -0
- package/esm/generations/generation-run.mjs.map +1 -0
- package/esm/generations/stubs.mjs +155 -13
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/bounded-stream-queue.d.mts +10 -0
- package/esm/http/bounded-stream-queue.d.mts.map +1 -0
- package/esm/http/bounded-stream-queue.mjs +97 -0
- package/esm/http/bounded-stream-queue.mjs.map +1 -0
- package/esm/http/build-rate-limit-options.d.mts +13 -0
- package/esm/http/build-rate-limit-options.d.mts.map +1 -1
- package/esm/http/build-rate-limit-options.mjs.map +1 -1
- package/esm/http/conditional-request.d.mts +86 -0
- package/esm/http/conditional-request.d.mts.map +1 -0
- package/esm/http/conditional-request.mjs +121 -0
- package/esm/http/conditional-request.mjs.map +1 -0
- package/esm/http/csp.d.mts +2 -0
- package/esm/http/csp.d.mts.map +1 -1
- package/esm/http/csp.mjs +1 -0
- package/esm/http/csp.mjs.map +1 -1
- package/esm/http/csrf-origin-policy.mjs.map +1 -1
- package/esm/http/database/RequestLog.d.mts +24 -84
- package/esm/http/database/RequestLog.d.mts.map +1 -1
- package/esm/http/errors/errors.d.mts +2 -0
- package/esm/http/errors/errors.d.mts.map +1 -1
- package/esm/http/errors/errors.mjs +1 -0
- package/esm/http/errors/errors.mjs.map +1 -1
- package/esm/http/escape-header-value.d.mts +18 -0
- package/esm/http/escape-header-value.d.mts.map +1 -0
- package/esm/http/escape-header-value.mjs +27 -0
- package/esm/http/escape-header-value.mjs.map +1 -0
- package/esm/http/flush-pending-cookies.mjs.map +1 -1
- package/esm/http/index.d.mts +5 -2
- package/esm/http/index.mjs +4 -1
- package/esm/http/lazy-static.mjs +194 -0
- package/esm/http/lazy-static.mjs.map +1 -0
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/plugins.d.mts.map +1 -1
- package/esm/http/plugins.mjs +7 -4
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/rate-limit-cache-store.mjs +2 -1
- package/esm/http/rate-limit-cache-store.mjs.map +1 -1
- package/esm/http/request-log-snapshot.d.mts +12 -5
- package/esm/http/request-log-snapshot.d.mts.map +1 -1
- package/esm/http/request-log-snapshot.mjs +83 -4
- package/esm/http/request-log-snapshot.mjs.map +1 -1
- package/esm/http/request.d.mts +2 -2
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +5 -4
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.d.mts +11 -1
- package/esm/http/response.d.mts.map +1 -1
- package/esm/http/response.mjs +164 -76
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/safe-redirect.d.mts +51 -0
- package/esm/http/safe-redirect.d.mts.map +1 -0
- package/esm/http/safe-redirect.mjs +80 -0
- package/esm/http/safe-redirect.mjs.map +1 -0
- package/esm/http/serialized.d.mts.map +1 -1
- package/esm/http/server.d.mts +3 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs +14 -4
- package/esm/http/server.mjs.map +1 -1
- package/esm/http/stream-transport.mjs +4 -2
- package/esm/http/stream-transport.mjs.map +1 -1
- package/esm/http/types.d.mts +44 -11
- package/esm/http/types.d.mts.map +1 -1
- package/esm/http/uploads/generate-image-variant.mjs +1 -1
- package/esm/http/uploads/generate-image-variants.d.mts +10 -7
- package/esm/http/uploads/generate-image-variants.d.mts.map +1 -1
- package/esm/http/uploads/generate-image-variants.mjs +61 -24
- package/esm/http/uploads/generate-image-variants.mjs.map +1 -1
- package/esm/http/uploads/image-variant-cache-entries.mjs +78 -0
- package/esm/http/uploads/image-variant-cache-entries.mjs.map +1 -0
- package/esm/http/uploads/image-variant-cache.mjs +72 -30
- package/esm/http/uploads/image-variant-cache.mjs.map +1 -1
- package/esm/http/uploads/image-variant-types.d.mts +2 -0
- package/esm/http/uploads/image-variant-types.d.mts.map +1 -1
- package/esm/http/uploads/image-variants-config-error.d.mts +3 -1
- package/esm/http/uploads/image-variants-config-error.d.mts.map +1 -1
- package/esm/http/uploads/image-variants-config-error.mjs +3 -2
- package/esm/http/uploads/image-variants-config-error.mjs.map +1 -1
- package/esm/http/uploads/load-variant-source.mjs +1 -1
- package/esm/http/uploads/load-variant-source.mjs.map +1 -1
- package/esm/http/uploads/purge-image-variants.d.mts +7 -2
- package/esm/http/uploads/purge-image-variants.d.mts.map +1 -1
- package/esm/http/uploads/purge-image-variants.mjs +16 -16
- package/esm/http/uploads/purge-image-variants.mjs.map +1 -1
- package/esm/http/uploads/remote-variant-source.mjs +115 -0
- package/esm/http/uploads/remote-variant-source.mjs.map +1 -0
- package/esm/http/uploads/resolve-image-variant.d.mts +3 -4
- package/esm/http/uploads/resolve-image-variant.d.mts.map +1 -1
- package/esm/http/uploads/resolve-image-variant.mjs +10 -18
- package/esm/http/uploads/resolve-image-variant.mjs.map +1 -1
- package/esm/http/uploads/resolve-image-variants-config.mjs +5 -3
- package/esm/http/uploads/resolve-image-variants-config.mjs.map +1 -1
- package/esm/http/uploads/resolve-remote-image-variant.mjs +89 -0
- package/esm/http/uploads/resolve-remote-image-variant.mjs.map +1 -0
- package/esm/http/uploads/trim-image-variant-cache.mjs +193 -0
- package/esm/http/uploads/trim-image-variant-cache.mjs.map +1 -0
- package/esm/http/uploads/uploaded-file.controller.d.mts +5 -3
- package/esm/http/uploads/uploaded-file.controller.d.mts.map +1 -1
- package/esm/http/uploads/uploaded-file.controller.mjs +16 -8
- package/esm/http/uploads/uploaded-file.controller.mjs.map +1 -1
- package/esm/http/uploads/variant-cache-key.mjs +1 -1
- package/esm/http/uploads-types.d.mts +6 -2
- package/esm/http/uploads-types.d.mts.map +1 -1
- package/esm/index.d.mts +17 -6
- package/esm/index.mjs +14 -3
- package/esm/localization/parse-locale-dictionary.d.mts +2 -0
- package/esm/localization/parse-locale-dictionary.d.mts.map +1 -1
- package/esm/localization/parse-locale-dictionary.mjs +1 -0
- package/esm/localization/parse-locale-dictionary.mjs.map +1 -1
- package/esm/mail/config.d.mts +12 -1
- package/esm/mail/config.d.mts.map +1 -1
- package/esm/mail/config.mjs +16 -1
- package/esm/mail/config.mjs.map +1 -1
- package/esm/mail/index.d.mts +1 -1
- package/esm/mail/index.mjs +1 -1
- package/esm/net/index.d.mts +2 -0
- package/esm/net/index.mjs +3 -0
- package/esm/openapi/build-openapi-document.d.mts.map +1 -1
- package/esm/openapi/build-openapi-document.mjs +8 -5
- package/esm/openapi/build-openapi-document.mjs.map +1 -1
- package/esm/openapi/error-responses.mjs +28 -2
- package/esm/openapi/error-responses.mjs.map +1 -1
- package/esm/openapi/postman/build-request-body.mjs.map +1 -1
- package/esm/openapi/postman/build-request-url.mjs.map +1 -1
- package/esm/openapi/postman/build-saved-responses.mjs.map +1 -1
- package/esm/openapi/postman/example-from-schema.d.mts.map +1 -1
- package/esm/openapi/postman/example-from-schema.mjs.map +1 -1
- package/esm/openapi/postman/openapi-to-postman-collection.d.mts.map +1 -1
- package/esm/openapi/postman/openapi-to-postman-collection.mjs.map +1 -1
- package/esm/openapi/postman/schema-refs.mjs.map +1 -1
- package/esm/openapi/postman/uuid-v5.mjs.map +1 -1
- package/esm/openapi/request-schemas.mjs.map +1 -1
- package/esm/openapi/response-schemas.mjs.map +1 -1
- package/esm/openapi/schema-registry.d.mts +1 -0
- package/esm/openapi/schema-registry.mjs.map +1 -1
- package/esm/openapi/security.mjs.map +1 -1
- package/esm/openapi/types.d.mts +3 -2
- package/esm/openapi/types.d.mts.map +1 -1
- package/esm/production/assert-generated-imports.mjs +1 -0
- package/esm/production/assert-generated-imports.mjs.map +1 -1
- package/esm/production/production-builder.mjs +12 -4
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/resolve-build-config.mjs +1 -0
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/production/route-registration-child.mjs.map +1 -1
- package/esm/production/route-registration-snapshot.mjs.map +1 -1
- package/esm/repositories/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/repositories/contracts/types.d.mts.map +1 -1
- package/esm/repositories/repository.manager.mjs.map +1 -1
- package/esm/resource/resource.d.mts.map +1 -1
- package/esm/resource/resource.mjs.map +1 -1
- package/esm/resource/types.d.mts.map +1 -1
- package/esm/restful/restful.d.mts +5 -5
- package/esm/restful/restful.d.mts.map +1 -1
- package/esm/restful/restful.mjs.map +1 -1
- package/esm/router/build-route-rate-limit.mjs.map +1 -1
- package/esm/router/dev-dispatch.mjs +31 -12
- package/esm/router/dev-dispatch.mjs.map +1 -1
- package/esm/router/index.d.mts +4 -2
- package/esm/router/index.mjs +2 -0
- package/esm/router/log-request-lifecycle.mjs.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/resolve-route-rate-limit.mjs +47 -0
- package/esm/router/resolve-route-rate-limit.mjs.map +1 -0
- package/esm/router/resolve-route-validation.mjs +19 -0
- package/esm/router/resolve-route-validation.mjs.map +1 -0
- package/esm/router/route-guard-names.mjs +30 -0
- package/esm/router/route-guard-names.mjs.map +1 -0
- package/esm/router/route-name-taken-error.d.mts +14 -0
- package/esm/router/route-name-taken-error.d.mts.map +1 -0
- package/esm/router/route-name-taken-error.mjs +18 -0
- package/esm/router/route-name-taken-error.mjs.map +1 -0
- package/esm/router/router.d.mts +32 -13
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +71 -38
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +137 -17
- package/esm/router/types.d.mts.map +1 -1
- package/esm/router/unguarded-route-diagnostics.d.mts +40 -0
- package/esm/router/unguarded-route-diagnostics.d.mts.map +1 -0
- package/esm/router/unguarded-route-diagnostics.mjs +95 -0
- package/esm/router/unguarded-route-diagnostics.mjs.map +1 -0
- 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/local-driver.d.mts.map +1 -1
- package/esm/storage/drivers/local-driver.mjs.map +1 -1
- package/esm/storage/local-disk-roots.mjs +24 -0
- package/esm/storage/local-disk-roots.mjs.map +1 -0
- package/esm/storage/storage.d.mts +14 -0
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/storage.mjs +22 -0
- package/esm/storage/storage.mjs.map +1 -1
- package/esm/storage/types.d.mts.map +1 -1
- package/esm/storage/utils/storage-not-initialized-error.d.mts +2 -0
- package/esm/storage/utils/storage-not-initialized-error.d.mts.map +1 -1
- package/esm/storage/utils/storage-not-initialized-error.mjs +1 -0
- package/esm/storage/utils/storage-not-initialized-error.mjs.map +1 -1
- package/esm/tests/authenticated-test-client.d.mts +32 -0
- package/esm/tests/authenticated-test-client.d.mts.map +1 -0
- package/esm/tests/authenticated-test-client.mjs +40 -0
- package/esm/tests/authenticated-test-client.mjs.map +1 -0
- package/esm/tests/factories.d.mts +59 -0
- package/esm/tests/factories.d.mts.map +1 -0
- package/esm/tests/factories.mjs +105 -0
- package/esm/tests/factories.mjs.map +1 -0
- package/esm/tests/fakes/cache-fake.d.mts +29 -0
- package/esm/tests/fakes/cache-fake.d.mts.map +1 -0
- package/esm/tests/fakes/cache-fake.mjs +79 -0
- package/esm/tests/fakes/cache-fake.mjs.map +1 -0
- package/esm/tests/fakes/clock-fake.d.mts +32 -0
- package/esm/tests/fakes/clock-fake.d.mts.map +1 -0
- package/esm/tests/fakes/clock-fake.mjs +69 -0
- package/esm/tests/fakes/clock-fake.mjs.map +1 -0
- package/esm/tests/fakes/fake-assertion-error.d.mts +13 -0
- package/esm/tests/fakes/fake-assertion-error.d.mts.map +1 -0
- package/esm/tests/fakes/fake-assertion-error.mjs +20 -0
- package/esm/tests/fakes/fake-assertion-error.mjs.map +1 -0
- package/esm/tests/fakes/ids-fake.d.mts +22 -0
- package/esm/tests/fakes/ids-fake.d.mts.map +1 -0
- package/esm/tests/fakes/ids-fake.mjs +57 -0
- package/esm/tests/fakes/ids-fake.mjs.map +1 -0
- package/esm/tests/fakes/index.d.mts +8 -0
- package/esm/tests/fakes/index.mjs +10 -0
- package/esm/tests/fakes/mail-fake.d.mts +41 -0
- package/esm/tests/fakes/mail-fake.d.mts.map +1 -0
- package/esm/tests/fakes/mail-fake.mjs +111 -0
- package/esm/tests/fakes/mail-fake.mjs.map +1 -0
- package/esm/tests/fakes/queue-fake.d.mts +48 -0
- package/esm/tests/fakes/queue-fake.d.mts.map +1 -0
- package/esm/tests/fakes/queue-fake.mjs +134 -0
- package/esm/tests/fakes/queue-fake.mjs.map +1 -0
- package/esm/tests/fakes/storage-fake.d.mts +31 -0
- package/esm/tests/fakes/storage-fake.d.mts.map +1 -0
- package/esm/tests/fakes/storage-fake.mjs +122 -0
- package/esm/tests/fakes/storage-fake.mjs.map +1 -0
- package/esm/tests/fakes/test-fakes.d.mts +39 -0
- package/esm/tests/fakes/test-fakes.d.mts.map +1 -0
- package/esm/tests/fakes/test-fakes.mjs +90 -0
- package/esm/tests/fakes/test-fakes.mjs.map +1 -0
- package/esm/tests/index.d.mts +14 -2
- package/esm/tests/index.mjs +15 -2
- package/esm/tests/snapshot-response.d.mts +34 -0
- package/esm/tests/snapshot-response.d.mts.map +1 -0
- package/esm/tests/snapshot-response.mjs +42 -0
- package/esm/tests/snapshot-response.mjs.map +1 -0
- package/esm/tests/start-http-development-server.d.mts +22 -0
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +36 -3
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/test-helpers.d.mts +20 -1
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-helpers.mjs +40 -27
- package/esm/tests/test-helpers.mjs.map +1 -1
- package/esm/tests/test-http-server.d.mts +62 -0
- package/esm/tests/test-http-server.d.mts.map +1 -0
- package/esm/tests/test-http-server.mjs +119 -0
- package/esm/tests/test-http-server.mjs.map +1 -0
- package/esm/tests/vitest-setup.d.mts +11 -0
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs +12 -4
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/tooling/agents-md/agents-md-markers.d.mts +27 -0
- package/esm/tooling/agents-md/agents-md-markers.d.mts.map +1 -0
- package/esm/tooling/agents-md/agents-md-markers.mjs +59 -0
- package/esm/tooling/agents-md/agents-md-markers.mjs.map +1 -0
- package/esm/tooling/agents-md/agents-md-refresher.d.mts +36 -0
- package/esm/tooling/agents-md/agents-md-refresher.d.mts.map +1 -0
- package/esm/tooling/agents-md/agents-md-refresher.mjs +72 -0
- package/esm/tooling/agents-md/agents-md-refresher.mjs.map +1 -0
- package/esm/tooling/agents-md/index.mjs +7 -0
- package/esm/tooling/agents-md/installed-skills.d.mts +30 -0
- package/esm/tooling/agents-md/installed-skills.d.mts.map +1 -0
- package/esm/tooling/agents-md/installed-skills.mjs +74 -0
- package/esm/tooling/agents-md/installed-skills.mjs.map +1 -0
- package/esm/tooling/agents-md/render-agents-md.d.mts +33 -0
- package/esm/tooling/agents-md/render-agents-md.d.mts.map +1 -0
- package/esm/tooling/agents-md/render-agents-md.mjs +195 -0
- package/esm/tooling/agents-md/render-agents-md.mjs.map +1 -0
- package/esm/tooling/agents-md/write-agents-md.d.mts +33 -0
- package/esm/tooling/agents-md/write-agents-md.d.mts.map +1 -0
- package/esm/tooling/agents-md/write-agents-md.mjs +49 -0
- package/esm/tooling/agents-md/write-agents-md.mjs.map +1 -0
- package/esm/tooling/board/board-message.d.mts +31 -0
- package/esm/tooling/board/board-message.d.mts.map +1 -0
- package/esm/tooling/board/board-message.mjs +86 -0
- package/esm/tooling/board/board-message.mjs.map +1 -0
- package/esm/tooling/board/board.d.mts +109 -0
- package/esm/tooling/board/board.d.mts.map +1 -0
- package/esm/tooling/board/board.mjs +406 -0
- package/esm/tooling/board/board.mjs.map +1 -0
- package/esm/tooling/board/board.types.d.mts +72 -0
- package/esm/tooling/board/board.types.d.mts.map +1 -0
- package/esm/tooling/board/board.types.mjs +54 -0
- package/esm/tooling/board/board.types.mjs.map +1 -0
- package/esm/tooling/check/changed-files.d.mts +33 -0
- package/esm/tooling/check/changed-files.d.mts.map +1 -0
- package/esm/tooling/check/changed-files.mjs +73 -0
- package/esm/tooling/check/changed-files.mjs.map +1 -0
- package/esm/tooling/check/check-report.types.d.mts +62 -0
- package/esm/tooling/check/check-report.types.d.mts.map +1 -0
- package/esm/tooling/check/check-report.types.mjs +37 -0
- package/esm/tooling/check/check-report.types.mjs.map +1 -0
- package/esm/tooling/check/code-style-conventions.mjs +106 -0
- package/esm/tooling/check/code-style-conventions.mjs.map +1 -0
- package/esm/tooling/check/conventions-step.d.mts +34 -0
- package/esm/tooling/check/conventions-step.d.mts.map +1 -0
- package/esm/tooling/check/conventions-step.mjs +184 -0
- package/esm/tooling/check/conventions-step.mjs.map +1 -0
- package/esm/tooling/check/environment-step.d.mts +18 -0
- package/esm/tooling/check/environment-step.d.mts.map +1 -0
- package/esm/tooling/check/environment-step.mjs +78 -0
- package/esm/tooling/check/environment-step.mjs.map +1 -0
- package/esm/tooling/check/index.d.mts +8 -0
- package/esm/tooling/check/index.mjs +10 -0
- package/esm/tooling/check/lint-step.d.mts +19 -0
- package/esm/tooling/check/lint-step.d.mts.map +1 -0
- package/esm/tooling/check/lint-step.mjs +111 -0
- package/esm/tooling/check/lint-step.mjs.map +1 -0
- package/esm/tooling/check/request-input.mjs +132 -0
- package/esm/tooling/check/request-input.mjs.map +1 -0
- package/esm/tooling/check/run-check.d.mts +35 -0
- package/esm/tooling/check/run-check.d.mts.map +1 -0
- package/esm/tooling/check/run-check.mjs +192 -0
- package/esm/tooling/check/run-check.mjs.map +1 -0
- package/esm/tooling/check/run-tests.d.mts +48 -0
- package/esm/tooling/check/run-tests.d.mts.map +1 -0
- package/esm/tooling/check/run-tests.mjs +93 -0
- package/esm/tooling/check/run-tests.mjs.map +1 -0
- package/esm/tooling/check/security-conventions.mjs +648 -0
- package/esm/tooling/check/security-conventions.mjs.map +1 -0
- package/esm/tooling/check/source-ast.mjs +195 -0
- package/esm/tooling/check/source-ast.mjs.map +1 -0
- package/esm/tooling/check/source-files.mjs +27 -0
- package/esm/tooling/check/source-files.mjs.map +1 -0
- package/esm/tooling/check/tests-step.d.mts +34 -0
- package/esm/tooling/check/tests-step.d.mts.map +1 -0
- package/esm/tooling/check/tests-step.mjs +177 -0
- package/esm/tooling/check/tests-step.mjs.map +1 -0
- package/esm/tooling/dev-state/dev-server-state.d.mts +39 -0
- package/esm/tooling/dev-state/dev-server-state.d.mts.map +1 -0
- package/esm/tooling/dev-state/dev-server-state.mjs +67 -0
- package/esm/tooling/dev-state/dev-server-state.mjs.map +1 -0
- package/esm/tooling/devtools/devtools-client.d.mts +91 -0
- package/esm/tooling/devtools/devtools-client.d.mts.map +1 -0
- package/esm/tooling/devtools/devtools-client.mjs +155 -0
- package/esm/tooling/devtools/devtools-client.mjs.map +1 -0
- package/esm/tooling/generate/generation.d.mts +78 -0
- package/esm/tooling/generate/generation.d.mts.map +1 -0
- package/esm/tooling/generate/generation.mjs +123 -0
- package/esm/tooling/generate/generation.mjs.map +1 -0
- package/esm/tooling/generate/generator-spec.d.mts +57 -0
- package/esm/tooling/generate/generator-spec.d.mts.map +1 -0
- package/esm/tooling/generate/generator-spec.mjs +102 -0
- package/esm/tooling/generate/generator-spec.mjs.map +1 -0
- package/esm/tooling/generate/index.mjs +6 -0
- package/esm/tooling/generate/project-files.mjs +121 -0
- package/esm/tooling/generate/project-files.mjs.map +1 -0
- package/esm/tooling/generate/run-cli.d.mts +40 -0
- package/esm/tooling/generate/run-cli.d.mts.map +1 -0
- package/esm/tooling/generate/run-cli.mjs +93 -0
- package/esm/tooling/generate/run-cli.mjs.map +1 -0
- package/esm/tooling/generate/unified-diff.d.mts +37 -0
- package/esm/tooling/generate/unified-diff.d.mts.map +1 -0
- package/esm/tooling/generate/unified-diff.mjs +161 -0
- package/esm/tooling/generate/unified-diff.mjs.map +1 -0
- package/esm/tooling/index.d.mts +41 -0
- package/esm/tooling/index.mjs +45 -0
- package/esm/tooling/inspect/boot-for-inspect.d.mts +23 -0
- package/esm/tooling/inspect/boot-for-inspect.d.mts.map +1 -0
- package/esm/tooling/inspect/boot-for-inspect.mjs +63 -0
- package/esm/tooling/inspect/boot-for-inspect.mjs.map +1 -0
- package/esm/tooling/inspect/collect-config.mjs +77 -0
- package/esm/tooling/inspect/collect-config.mjs.map +1 -0
- package/esm/tooling/inspect/collect-models.d.mts +9 -0
- package/esm/tooling/inspect/collect-models.d.mts.map +1 -0
- package/esm/tooling/inspect/collect-models.mjs +154 -0
- package/esm/tooling/inspect/collect-models.mjs.map +1 -0
- package/esm/tooling/inspect/collect-packages.d.mts +10 -0
- package/esm/tooling/inspect/collect-packages.d.mts.map +1 -0
- package/esm/tooling/inspect/collect-packages.mjs +157 -0
- package/esm/tooling/inspect/collect-packages.mjs.map +1 -0
- package/esm/tooling/inspect/collect-pages.mjs +108 -0
- package/esm/tooling/inspect/collect-pages.mjs.map +1 -0
- package/esm/tooling/inspect/collect-routes.mjs +168 -0
- package/esm/tooling/inspect/collect-routes.mjs.map +1 -0
- package/esm/tooling/inspect/event-recorder.d.mts +37 -0
- package/esm/tooling/inspect/event-recorder.d.mts.map +1 -0
- package/esm/tooling/inspect/event-recorder.mjs +91 -0
- package/esm/tooling/inspect/event-recorder.mjs.map +1 -0
- package/esm/tooling/inspect/index.d.mts +7 -0
- package/esm/tooling/inspect/index.mjs +7 -0
- package/esm/tooling/inspect/inspect-app.d.mts +53 -0
- package/esm/tooling/inspect/inspect-app.d.mts.map +1 -0
- package/esm/tooling/inspect/inspect-app.mjs +92 -0
- package/esm/tooling/inspect/inspect-app.mjs.map +1 -0
- package/esm/tooling/inspect/inspect-report.schema.d.mts +15 -0
- package/esm/tooling/inspect/inspect-report.schema.d.mts.map +1 -0
- package/esm/tooling/inspect/inspect-report.schema.mjs +463 -0
- package/esm/tooling/inspect/inspect-report.schema.mjs.map +1 -0
- package/esm/tooling/inspect/inspect-report.types.d.mts +196 -0
- package/esm/tooling/inspect/inspect-report.types.d.mts.map +1 -0
- package/esm/tooling/inspect/inspect-report.types.mjs +33 -0
- package/esm/tooling/inspect/inspect-report.types.mjs.map +1 -0
- package/esm/tooling/mcp/create-mcp-context.d.mts +26 -0
- package/esm/tooling/mcp/create-mcp-context.d.mts.map +1 -0
- package/esm/tooling/mcp/create-mcp-context.mjs +75 -0
- package/esm/tooling/mcp/create-mcp-context.mjs.map +1 -0
- package/esm/tooling/mcp/db-schema.d.mts +49 -0
- package/esm/tooling/mcp/db-schema.d.mts.map +1 -0
- package/esm/tooling/mcp/db-schema.mjs +56 -0
- package/esm/tooling/mcp/db-schema.mjs.map +1 -0
- package/esm/tooling/mcp/docs-search.d.mts +72 -0
- package/esm/tooling/mcp/docs-search.d.mts.map +1 -0
- package/esm/tooling/mcp/docs-search.mjs +178 -0
- package/esm/tooling/mcp/docs-search.mjs.map +1 -0
- package/esm/tooling/mcp/index.mjs +9 -0
- package/esm/tooling/mcp/load-ai-tools.d.mts +62 -0
- package/esm/tooling/mcp/load-ai-tools.d.mts.map +1 -0
- package/esm/tooling/mcp/load-ai-tools.mjs +44 -0
- package/esm/tooling/mcp/load-ai-tools.mjs.map +1 -0
- package/esm/tooling/mcp/mcp-tools.d.mts +60 -0
- package/esm/tooling/mcp/mcp-tools.d.mts.map +1 -0
- package/esm/tooling/mcp/mcp-tools.mjs +513 -0
- package/esm/tooling/mcp/mcp-tools.mjs.map +1 -0
- package/esm/tooling/mcp/start-mcp-server.d.mts +56 -0
- package/esm/tooling/mcp/start-mcp-server.d.mts.map +1 -0
- package/esm/tooling/mcp/start-mcp-server.mjs +126 -0
- package/esm/tooling/mcp/start-mcp-server.mjs.map +1 -0
- package/esm/tooling/mcp/tool-input.d.mts +75 -0
- package/esm/tooling/mcp/tool-input.d.mts.map +1 -0
- package/esm/tooling/mcp/tool-input.mjs +72 -0
- package/esm/tooling/mcp/tool-input.mjs.map +1 -0
- package/esm/tooling/problem.d.mts +45 -0
- package/esm/tooling/problem.d.mts.map +1 -0
- package/esm/tooling/problem.mjs +28 -0
- package/esm/tooling/problem.mjs.map +1 -0
- package/esm/tooling/trace/trace-report.types.d.mts +71 -0
- package/esm/tooling/trace/trace-report.types.d.mts.map +1 -0
- package/esm/tooling/trace/trace-report.types.mjs +7 -0
- package/esm/tooling/trace/trace-report.types.mjs.map +1 -0
- package/esm/tooling/trace/trace-request.d.mts +83 -0
- package/esm/tooling/trace/trace-request.d.mts.map +1 -0
- package/esm/tooling/trace/trace-request.mjs +241 -0
- package/esm/tooling/trace/trace-request.mjs.map +1 -0
- package/esm/tooling/typecheck/index.d.mts +1 -0
- package/esm/tooling/typecheck/index.mjs +3 -0
- package/esm/tooling/typecheck/run-typecheck.d.mts +59 -0
- package/esm/tooling/typecheck/run-typecheck.d.mts.map +1 -0
- package/esm/tooling/typecheck/run-typecheck.mjs +153 -0
- package/esm/tooling/typecheck/run-typecheck.mjs.map +1 -0
- package/esm/upgrade/codemods/head-viewport.mjs +46 -0
- package/esm/upgrade/codemods/head-viewport.mjs.map +1 -0
- package/esm/upgrade/codemods/helpers.mjs +81 -0
- package/esm/upgrade/codemods/helpers.mjs.map +1 -0
- package/esm/upgrade/codemods/index.mjs +34 -0
- package/esm/upgrade/codemods/index.mjs.map +1 -0
- package/esm/upgrade/codemods/logger-sync-calls.mjs +57 -0
- package/esm/upgrade/codemods/logger-sync-calls.mjs.map +1 -0
- package/esm/upgrade/codemods/package-json.mjs +122 -0
- package/esm/upgrade/codemods/package-json.mjs.map +1 -0
- package/esm/upgrade/codemods/request-log-snapshot.mjs +82 -0
- package/esm/upgrade/codemods/request-log-snapshot.mjs.map +1 -0
- package/esm/upgrade/codemods/review-sites.mjs +262 -0
- package/esm/upgrade/codemods/review-sites.mjs.map +1 -0
- package/esm/upgrade/diff.mjs +119 -0
- package/esm/upgrade/diff.mjs.map +1 -0
- package/esm/upgrade/format-upgrade-report.mjs +32 -0
- package/esm/upgrade/format-upgrade-report.mjs.map +1 -0
- package/esm/upgrade/load-ts-morph.mjs +48 -0
- package/esm/upgrade/load-ts-morph.mjs.map +1 -0
- package/esm/upgrade/notes.mjs +16 -0
- package/esm/upgrade/notes.mjs.map +1 -0
- package/esm/upgrade/run-upgrade.mjs +246 -0
- package/esm/upgrade/run-upgrade.mjs.map +1 -0
- package/esm/upgrade/upgrade-command.action.mjs +37 -0
- package/esm/upgrade/upgrade-command.action.mjs.map +1 -0
- package/esm/upgrade/version.mjs +39 -0
- package/esm/upgrade/version.mjs.map +1 -0
- package/esm/use-cases/use-cases-registry.mjs.map +1 -1
- package/esm/utils/clock.d.mts +33 -0
- package/esm/utils/clock.d.mts.map +1 -0
- package/esm/utils/clock.mjs +27 -0
- package/esm/utils/clock.mjs.map +1 -0
- package/esm/utils/framework-vesion.mjs +1 -1
- package/esm/utils/framework-vesion.mjs.map +1 -1
- package/esm/utils/ids.d.mts +32 -0
- package/esm/utils/ids.d.mts.map +1 -0
- package/esm/utils/ids.mjs +33 -0
- package/esm/utils/ids.mjs.map +1 -0
- package/esm/utils/index.d.mts +3 -0
- package/esm/utils/index.mjs +3 -0
- package/esm/utils/runtime.d.mts +59 -0
- package/esm/utils/runtime.d.mts.map +1 -0
- package/esm/utils/runtime.mjs +85 -0
- package/esm/utils/runtime.mjs.map +1 -0
- package/esm/validation/index.d.mts +1 -1
- package/esm/validation/types.d.mts +57 -2
- package/esm/validation/types.d.mts.map +1 -1
- package/esm/validation/validateAll.mjs +19 -2
- package/esm/validation/validateAll.mjs.map +1 -1
- package/esm/validation/validation-failure-status.mjs +15 -0
- package/esm/validation/validation-failure-status.mjs.map +1 -0
- package/esm/validation/validators/file-validator.d.mts +0 -16
- package/esm/validation/validators/file-validator.d.mts.map +1 -1
- package/esm/warlock-config/bundle-config.mjs +1 -1
- package/esm/warlock-config/types.d.mts +27 -3
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/llms-full.txt +3166 -736
- package/llms.txt +16 -6
- package/package.json +43 -13
- package/schemas/inspect-report.schema.json +554 -0
- package/skills/README.md +9 -0
- package/skills/add-connector/SKILL.md +26 -38
- package/skills/benchmark-code/SKILL.md +30 -24
- package/skills/build-restful/SKILL.md +14 -14
- package/skills/build-url/SKILL.md +18 -18
- package/skills/configure-app/SKILL.md +107 -14
- package/skills/create-controller/SKILL.md +25 -12
- package/skills/create-module/SKILL.md +3 -3
- package/skills/define-resource/SKILL.md +32 -29
- package/skills/encrypt-data/SKILL.md +21 -19
- package/skills/generate-openapi/SKILL.md +50 -24
- package/skills/hash-password/SKILL.md +2 -2
- package/skills/health-checks/SKILL.md +24 -7
- package/skills/index.md +9 -4
- package/skills/lower-stage3-decorators/SKILL.md +4 -4
- package/skills/make-endpoint/SKILL.md +110 -0
- package/skills/model-factories/SKILL.md +92 -0
- package/skills/process-image/SKILL.md +32 -30
- package/skills/register-route/SKILL.md +109 -10
- package/skills/request-memo/SKILL.md +1 -1
- package/skills/request-tracing/SKILL.md +4 -1
- package/skills/resolve-path/SKILL.md +45 -44
- package/skills/retry-operation/SKILL.md +20 -11
- package/skills/run-app/SKILL.md +106 -22
- package/skills/send-response/SKILL.md +179 -14
- package/skills/store-file/SKILL.md +93 -53
- package/skills/test-fakes/SKILL.md +123 -0
- package/skills/test-http/SKILL.md +125 -21
- package/skills/test-service/SKILL.md +41 -1
- package/skills/update-packages/SKILL.md +29 -18
- package/skills/upgrade-from-5-29/SKILL.md +413 -0
- package/skills/upload-file/SKILL.md +31 -33
- package/skills/use-app-context/SKILL.md +38 -31
- package/skills/use-middleware/SKILL.md +45 -25
- package/skills/use-model-transformers/SKILL.md +37 -28
- package/skills/use-repository/SKILL.md +2 -2
- package/skills/validate-input/SKILL.md +72 -28
- package/skills/warlock-agents-md/SKILL.md +96 -0
- package/skills/warlock-board/SKILL.md +123 -0
- package/skills/warlock-check/SKILL.md +132 -0
- package/skills/warlock-conventions/SKILL.md +115 -0
- package/skills/warlock-doctor/SKILL.md +55 -22
- package/skills/warlock-inspect/SKILL.md +134 -0
- package/skills/warlock-mcp/SKILL.md +120 -0
- package/skills/warlock-routes/SKILL.md +18 -11
- package/skills/warlock-trace/SKILL.md +81 -0
- package/skills/wire-socket/SKILL.md +8 -8
- package/skills/write-seeder/SKILL.md +46 -32
- package/skills/write-use-case/SKILL.md +40 -27
- package/esm/http/uploads/matches-if-none-match.mjs +0 -13
- package/esm/http/uploads/matches-if-none-match.mjs.map +0 -1
|
@@ -23,7 +23,7 @@ function getTestServerUrl() {
|
|
|
23
23
|
* Uses native fetch - lightweight, no extra dependencies
|
|
24
24
|
*/
|
|
25
25
|
async function testRequest(path, options = {}) {
|
|
26
|
-
return sendRequest(path, options, false);
|
|
26
|
+
return sendRequest(getTestServerUrl(), path, options, false);
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
29
|
* The single place a request is actually built.
|
|
@@ -33,8 +33,7 @@ async function testRequest(path, options = {}) {
|
|
|
33
33
|
* a `FormData` body carries a multipart boundary the runtime generates, and
|
|
34
34
|
* labelling it `application/json` produces a request no server can parse.
|
|
35
35
|
*/
|
|
36
|
-
async function sendRequest(path, options, serializedJson) {
|
|
37
|
-
const baseUrl = getTestServerUrl();
|
|
36
|
+
async function sendRequest(baseUrl, path, options, serializedJson) {
|
|
38
37
|
const url = path.startsWith("/") ? `${baseUrl}${path}` : `${baseUrl}/${path}`;
|
|
39
38
|
const headers = new Headers(options.headers);
|
|
40
39
|
if (serializedJson && !headers.has("content-type")) headers.set("content-type", "application/json");
|
|
@@ -55,52 +54,66 @@ function jsonBody(body) {
|
|
|
55
54
|
return body === void 0 ? void 0 : JSON.stringify(body);
|
|
56
55
|
}
|
|
57
56
|
/**
|
|
57
|
+
* Request helpers bound to `baseUrl` (a string, or a function read on every
|
|
58
|
+
* request). `testGet()` and friends are this, bound to the shared test server;
|
|
59
|
+
* `createTestHttpServer()` returns one bound to its own port.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* const client = createTestRequestClient("http://127.0.0.1:4100");
|
|
63
|
+
* await client.post("/posts", { title: "Hello" });
|
|
64
|
+
*/
|
|
65
|
+
function createTestRequestClient(baseUrl) {
|
|
66
|
+
const base = () => typeof baseUrl === "function" ? baseUrl() : baseUrl;
|
|
67
|
+
const send = (method, path, body, options = {}) => sendRequest(base(), path, {
|
|
68
|
+
...options,
|
|
69
|
+
method,
|
|
70
|
+
body: jsonBody(body)
|
|
71
|
+
}, body !== void 0);
|
|
72
|
+
return {
|
|
73
|
+
request: (path, options = {}) => sendRequest(base(), path, options, false),
|
|
74
|
+
get: (path, options = {}) => sendRequest(base(), path, {
|
|
75
|
+
...options,
|
|
76
|
+
method: "GET"
|
|
77
|
+
}, false),
|
|
78
|
+
post: (path, body, options) => send("POST", path, body, options),
|
|
79
|
+
put: (path, body, options) => send("PUT", path, body, options),
|
|
80
|
+
patch: (path, body, options) => send("PATCH", path, body, options),
|
|
81
|
+
delete: (path, options = {}) => sendRequest(base(), path, {
|
|
82
|
+
...options,
|
|
83
|
+
method: "DELETE"
|
|
84
|
+
}, false)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const sharedServer = createTestRequestClient(getTestServerUrl);
|
|
88
|
+
/**
|
|
58
89
|
* GET request helper
|
|
59
90
|
*/
|
|
60
91
|
async function testGet(path, options = {}) {
|
|
61
|
-
return
|
|
62
|
-
...options,
|
|
63
|
-
method: "GET"
|
|
64
|
-
});
|
|
92
|
+
return sharedServer.get(path, options);
|
|
65
93
|
}
|
|
66
94
|
/**
|
|
67
95
|
* POST request helper
|
|
68
96
|
*/
|
|
69
97
|
async function testPost(path, body, options = {}) {
|
|
70
|
-
return
|
|
71
|
-
...options,
|
|
72
|
-
method: "POST",
|
|
73
|
-
body: jsonBody(body)
|
|
74
|
-
}, body !== void 0);
|
|
98
|
+
return sharedServer.post(path, body, options);
|
|
75
99
|
}
|
|
76
100
|
/**
|
|
77
101
|
* PUT request helper
|
|
78
102
|
*/
|
|
79
103
|
async function testPut(path, body, options = {}) {
|
|
80
|
-
return
|
|
81
|
-
...options,
|
|
82
|
-
method: "PUT",
|
|
83
|
-
body: jsonBody(body)
|
|
84
|
-
}, body !== void 0);
|
|
104
|
+
return sharedServer.put(path, body, options);
|
|
85
105
|
}
|
|
86
106
|
/**
|
|
87
107
|
* DELETE request helper
|
|
88
108
|
*/
|
|
89
109
|
async function testDelete(path, options = {}) {
|
|
90
|
-
return
|
|
91
|
-
...options,
|
|
92
|
-
method: "DELETE"
|
|
93
|
-
});
|
|
110
|
+
return sharedServer.delete(path, options);
|
|
94
111
|
}
|
|
95
112
|
/**
|
|
96
113
|
* PATCH request helper
|
|
97
114
|
*/
|
|
98
115
|
async function testPatch(path, body, options = {}) {
|
|
99
|
-
return
|
|
100
|
-
...options,
|
|
101
|
-
method: "PATCH",
|
|
102
|
-
body: jsonBody(body)
|
|
103
|
-
}, body !== void 0);
|
|
116
|
+
return sharedServer.patch(path, body, options);
|
|
104
117
|
}
|
|
105
118
|
/**
|
|
106
119
|
* Parse JSON response with type safety
|
|
@@ -120,5 +133,5 @@ async function expectJson(response, expectedStatus = 200) {
|
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
//#endregion
|
|
123
|
-
export { expectJson, getTestServerUrl, parseJsonResponse, testDelete, testGet, testPatch, testPost, testPut, testRequest };
|
|
136
|
+
export { createTestRequestClient, expectJson, getTestServerUrl, parseJsonResponse, testDelete, testGet, testPatch, testPost, testPut, testRequest };
|
|
124
137
|
//# sourceMappingURL=test-helpers.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-helpers.mjs","names":[],"sources":["../../../../../../../../packages/core/src/tests/test-helpers.ts"],"sourcesContent":["/**\n * Warlock.js Test Helpers\n *\n * Utilities for testing Warlock.js applications.\n */\n\nimport { config } from \"../config\";\nimport { getHttpReadyReport } from \"../http/ready-report\";\nimport { TEST_SERVER_PORT_ENV_KEY } from \"./test-server-port-channel\";\n\n/**\n * Get the test server base URL\n */\nexport function getTestServerUrl(): string {\n // `startHttpTestServer` publishes the port it actually bound. Test workers are\n // separate processes whose own config resolves `http.port` from `.env`, so\n // without this a suite started on an explicit port would send every request to\n // the `.env` port instead of the one the server is listening on.\n const publishedPort = process.env[TEST_SERVER_PORT_ENV_KEY];\n\n // Same-process fallback: a test that boots the HTTP connector directly\n // (rather than through `startHttpTestServer`'s cross-process channel) never\n // writes `TEST_SERVER_PORT_ENV_KEY`, so without this the next fallback —\n // `config.key(\"http.port\", 2031)` — was the only source left, and it holds\n // the CONFIGURED port, not the bound one. For `http.port: 0` that is `0`,\n // producing `http://host:0` even though `HttpConnector.start()` had already\n // recorded the real bound port right here in `ready-report.ts`.\n const boundPort = getHttpReadyReport()?.port;\n\n // `config.key`'s default only substitutes for `undefined` — `0` is a\n // defined configured value, not an absent one — so it cannot be trusted to\n // paper over an unresolved port 0 either. `boundPort` is read first for\n // that exact reason.\n const port = publishedPort || boundPort || config.key(\"http.port\", 2031);\n const host = config.key(\"http.host\", \"localhost\");\n return `http://${host}:${port}`;\n}\n\n/**\n * Simple HTTP request helper for test server\n * Uses native fetch - lightweight, no extra dependencies\n */\nexport async function testRequest(path: string, options: RequestInit = {}): Promise<Response> {\n return sendRequest(path, options, false);\n}\n\n/**\n * The single place a request is actually built.\n *\n * `serializedJson` says whether THIS module turned the caller's value into a\n * JSON string. It is the only thing that justifies setting a JSON content type:\n * a `FormData` body carries a multipart boundary the runtime generates, and\n * labelling it `application/json` produces a request no server can parse.\n */\nasync function sendRequest(\n path: string,\n options: RequestInit,\n serializedJson: boolean,\n): Promise<Response> {\n const
|
|
1
|
+
{"version":3,"file":"test-helpers.mjs","names":[],"sources":["../../../../../../../../packages/core/src/tests/test-helpers.ts"],"sourcesContent":["/**\n * Warlock.js Test Helpers\n *\n * Utilities for testing Warlock.js applications.\n */\n\nimport { config } from \"../config\";\nimport { getHttpReadyReport } from \"../http/ready-report\";\nimport { TEST_SERVER_PORT_ENV_KEY } from \"./test-server-port-channel\";\n\n/**\n * Get the test server base URL\n */\nexport function getTestServerUrl(): string {\n // `startHttpTestServer` publishes the port it actually bound. Test workers are\n // separate processes whose own config resolves `http.port` from `.env`, so\n // without this a suite started on an explicit port would send every request to\n // the `.env` port instead of the one the server is listening on.\n const publishedPort = process.env[TEST_SERVER_PORT_ENV_KEY];\n\n // Same-process fallback: a test that boots the HTTP connector directly\n // (rather than through `startHttpTestServer`'s cross-process channel) never\n // writes `TEST_SERVER_PORT_ENV_KEY`, so without this the next fallback —\n // `config.key(\"http.port\", 2031)` — was the only source left, and it holds\n // the CONFIGURED port, not the bound one. For `http.port: 0` that is `0`,\n // producing `http://host:0` even though `HttpConnector.start()` had already\n // recorded the real bound port right here in `ready-report.ts`.\n const boundPort = getHttpReadyReport()?.port;\n\n // `config.key`'s default only substitutes for `undefined` — `0` is a\n // defined configured value, not an absent one — so it cannot be trusted to\n // paper over an unresolved port 0 either. `boundPort` is read first for\n // that exact reason.\n const port = publishedPort || boundPort || config.key(\"http.port\", 2031);\n const host = config.key(\"http.host\", \"localhost\");\n return `http://${host}:${port}`;\n}\n\n/**\n * Simple HTTP request helper for test server\n * Uses native fetch - lightweight, no extra dependencies\n */\nexport async function testRequest(path: string, options: RequestInit = {}): Promise<Response> {\n return sendRequest(getTestServerUrl(), path, options, false);\n}\n\n/**\n * The single place a request is actually built.\n *\n * `serializedJson` says whether THIS module turned the caller's value into a\n * JSON string. It is the only thing that justifies setting a JSON content type:\n * a `FormData` body carries a multipart boundary the runtime generates, and\n * labelling it `application/json` produces a request no server can parse.\n */\nasync function sendRequest(\n baseUrl: string,\n path: string,\n options: RequestInit,\n serializedJson: boolean,\n): Promise<Response> {\n const url = path.startsWith(\"/\") ? `${baseUrl}${path}` : `${baseUrl}/${path}`;\n\n // Normalised through `Headers` rather than object-spread: `RequestInit.headers`\n // may be a record, a `Headers` instance, or a list of `[name, value]` tuples,\n // and spreading the last two silently produces an object with numeric keys —\n // the header is simply lost, with no error anywhere.\n const headers = new Headers(options.headers);\n\n if (serializedJson && !headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n return fetch(url, { ...options, headers });\n}\n\n/**\n * Serialize a body for a JSON request.\n *\n * `undefined` means the caller omitted the argument; every other value —\n * including `false`, `0`, `\"\"` and `null` — is a legal JSON document and must\n * be sent. The old check was `body ? JSON.stringify(body) : undefined`, which\n * dropped all four.\n */\nfunction jsonBody(body: unknown): string | undefined {\n return body === undefined ? undefined : JSON.stringify(body);\n}\n\n/** The request helpers, bound to one server. */\nexport type TestRequestClient = {\n request(path: string, options?: RequestInit): Promise<Response>;\n get(path: string, options?: RequestInit): Promise<Response>;\n post(path: string, body?: unknown, options?: RequestInit): Promise<Response>;\n put(path: string, body?: unknown, options?: RequestInit): Promise<Response>;\n patch(path: string, body?: unknown, options?: RequestInit): Promise<Response>;\n delete(path: string, options?: RequestInit): Promise<Response>;\n};\n\n/**\n * Request helpers bound to `baseUrl` (a string, or a function read on every\n * request). `testGet()` and friends are this, bound to the shared test server;\n * `createTestHttpServer()` returns one bound to its own port.\n *\n * @example\n * const client = createTestRequestClient(\"http://127.0.0.1:4100\");\n * await client.post(\"/posts\", { title: \"Hello\" });\n */\nexport function createTestRequestClient(baseUrl: string | (() => string)): TestRequestClient {\n const base = () => (typeof baseUrl === \"function\" ? baseUrl() : baseUrl);\n const send = (method: string, path: string, body: unknown, options: RequestInit = {}) =>\n sendRequest(base(), path, { ...options, method, body: jsonBody(body) }, body !== undefined);\n\n return {\n request: (path, options = {}) => sendRequest(base(), path, options, false),\n get: (path, options = {}) => sendRequest(base(), path, { ...options, method: \"GET\" }, false),\n post: (path, body, options) => send(\"POST\", path, body, options),\n put: (path, body, options) => send(\"PUT\", path, body, options),\n patch: (path, body, options) => send(\"PATCH\", path, body, options),\n delete: (path, options = {}) =>\n sendRequest(base(), path, { ...options, method: \"DELETE\" }, false),\n };\n}\n\nconst sharedServer = createTestRequestClient(getTestServerUrl);\n\n/**\n * GET request helper\n */\nexport async function testGet(path: string, options: RequestInit = {}): Promise<Response> {\n return sharedServer.get(path, options);\n}\n\n/**\n * POST request helper\n */\nexport async function testPost(\n path: string,\n body?: unknown,\n options: RequestInit = {},\n): Promise<Response> {\n return sharedServer.post(path, body, options);\n}\n\n/**\n * PUT request helper\n */\nexport async function testPut(\n path: string,\n body?: unknown,\n options: RequestInit = {},\n): Promise<Response> {\n return sharedServer.put(path, body, options);\n}\n\n/**\n * DELETE request helper\n */\nexport async function testDelete(path: string, options: RequestInit = {}): Promise<Response> {\n return sharedServer.delete(path, options);\n}\n\n/**\n * PATCH request helper\n */\nexport async function testPatch(\n path: string,\n body?: unknown,\n options: RequestInit = {},\n): Promise<Response> {\n return sharedServer.patch(path, body, options);\n}\n\n/**\n * Parse JSON response with type safety\n */\nexport async function parseJsonResponse<T>(response: Response): Promise<T> {\n return response.json() as Promise<T>;\n}\n\n/**\n * Assert response status and return parsed JSON\n */\nexport async function expectJson<T>(response: Response, expectedStatus = 200): Promise<T> {\n if (response.status !== expectedStatus) {\n const text = await response.text();\n throw new Error(`Expected status ${expectedStatus}, got ${response.status}. Body: ${text}`);\n }\n return parseJsonResponse<T>(response);\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAgB,mBAA2B;CAKzC,MAAM,gBAAgB,QAAQ,IAAI;CASlC,MAAM,YAAY,mBAAmB,CAAC,EAAE;CAMxC,MAAM,OAAO,iBAAiB,aAAa,OAAO,IAAI,aAAa,IAAI;CAEvE,OAAO,UADM,OAAO,IAAI,aAAa,WACjB,EAAE,GAAG;AAC3B;;;;;AAMA,eAAsB,YAAY,MAAc,UAAuB,CAAC,GAAsB;CAC5F,OAAO,YAAY,iBAAiB,GAAG,MAAM,SAAS,KAAK;AAC7D;;;;;;;;;AAUA,eAAe,YACb,SACA,MACA,SACA,gBACmB;CACnB,MAAM,MAAM,KAAK,WAAW,GAAG,IAAI,GAAG,UAAU,SAAS,GAAG,QAAQ,GAAG;CAMvE,MAAM,UAAU,IAAI,QAAQ,QAAQ,OAAO;CAE3C,IAAI,kBAAkB,CAAC,QAAQ,IAAI,cAAc,GAC/C,QAAQ,IAAI,gBAAgB,kBAAkB;CAGhD,OAAO,MAAM,KAAK;EAAE,GAAG;EAAS;CAAQ,CAAC;AAC3C;;;;;;;;;AAUA,SAAS,SAAS,MAAmC;CACnD,OAAO,SAAS,SAAY,SAAY,KAAK,UAAU,IAAI;AAC7D;;;;;;;;;;AAqBA,SAAgB,wBAAwB,SAAqD;CAC3F,MAAM,aAAc,OAAO,YAAY,aAAa,QAAQ,IAAI;CAChE,MAAM,QAAQ,QAAgB,MAAc,MAAe,UAAuB,CAAC,MACjF,YAAY,KAAK,GAAG,MAAM;EAAE,GAAG;EAAS;EAAQ,MAAM,SAAS,IAAI;CAAE,GAAG,SAAS,MAAS;CAE5F,OAAO;EACL,UAAU,MAAM,UAAU,CAAC,MAAM,YAAY,KAAK,GAAG,MAAM,SAAS,KAAK;EACzE,MAAM,MAAM,UAAU,CAAC,MAAM,YAAY,KAAK,GAAG,MAAM;GAAE,GAAG;GAAS,QAAQ;EAAM,GAAG,KAAK;EAC3F,OAAO,MAAM,MAAM,YAAY,KAAK,QAAQ,MAAM,MAAM,OAAO;EAC/D,MAAM,MAAM,MAAM,YAAY,KAAK,OAAO,MAAM,MAAM,OAAO;EAC7D,QAAQ,MAAM,MAAM,YAAY,KAAK,SAAS,MAAM,MAAM,OAAO;EACjE,SAAS,MAAM,UAAU,CAAC,MACxB,YAAY,KAAK,GAAG,MAAM;GAAE,GAAG;GAAS,QAAQ;EAAS,GAAG,KAAK;CACrE;AACF;AAEA,MAAM,eAAe,wBAAwB,gBAAgB;;;;AAK7D,eAAsB,QAAQ,MAAc,UAAuB,CAAC,GAAsB;CACxF,OAAO,aAAa,IAAI,MAAM,OAAO;AACvC;;;;AAKA,eAAsB,SACpB,MACA,MACA,UAAuB,CAAC,GACL;CACnB,OAAO,aAAa,KAAK,MAAM,MAAM,OAAO;AAC9C;;;;AAKA,eAAsB,QACpB,MACA,MACA,UAAuB,CAAC,GACL;CACnB,OAAO,aAAa,IAAI,MAAM,MAAM,OAAO;AAC7C;;;;AAKA,eAAsB,WAAW,MAAc,UAAuB,CAAC,GAAsB;CAC3F,OAAO,aAAa,OAAO,MAAM,OAAO;AAC1C;;;;AAKA,eAAsB,UACpB,MACA,MACA,UAAuB,CAAC,GACL;CACnB,OAAO,aAAa,MAAM,MAAM,MAAM,OAAO;AAC/C;;;;AAKA,eAAsB,kBAAqB,UAAgC;CACzE,OAAO,SAAS,KAAK;AACvB;;;;AAKA,eAAsB,WAAc,UAAoB,iBAAiB,KAAiB;CACxF,IAAI,SAAS,WAAW,gBAAgB;EACtC,MAAM,OAAO,MAAM,SAAS,KAAK;EACjC,MAAM,IAAI,MAAM,mBAAmB,eAAe,QAAQ,SAAS,OAAO,UAAU,MAAM;CAC5F;CACA,OAAO,kBAAqB,QAAQ;AACtC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { FastifyInstance } from "../http/server.mjs";
|
|
2
|
+
import { Router } from "../router/router.mjs";
|
|
3
|
+
import { TestRequestClient } from "./test-helpers.mjs";
|
|
4
|
+
import { AuthenticatedTestClient, TestAuthenticableUser } from "./authenticated-test-client.mjs";
|
|
5
|
+
import { AppModuleImporter } from "../loader/app-module-importer.mjs";
|
|
6
|
+
|
|
7
|
+
//#region ../../packages/core/src/tests/test-http-server.d.ts
|
|
8
|
+
type CreateTestHttpServerOptions = {
|
|
9
|
+
/**
|
|
10
|
+
* Register routes for this server, exactly as a `routes.ts` would. They are
|
|
11
|
+
* tagged with a source of their own and removed again by `close()`.
|
|
12
|
+
*/
|
|
13
|
+
routes?: (router: Router) => unknown;
|
|
14
|
+
/**
|
|
15
|
+
* Load the application's modules (`routes.ts`, `main.ts`, events, ...) first,
|
|
16
|
+
* so the server answers every app route. Needs `setupTest()` to have run in
|
|
17
|
+
* this worker. Modules load once per process; later servers reuse them.
|
|
18
|
+
*/
|
|
19
|
+
loadAppModules?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* How app modules are imported when `loadAppModules` is set. Pass
|
|
22
|
+
* `(file) => import(file)` from the test file, as `setupTest()` takes it, so
|
|
23
|
+
* app classes are the same ones the test imports.
|
|
24
|
+
*/
|
|
25
|
+
importModule?: AppModuleImporter; /** Interface to bind. Defaults to `127.0.0.1`. */
|
|
26
|
+
host?: string;
|
|
27
|
+
};
|
|
28
|
+
type TestHttpServer = TestRequestClient & {
|
|
29
|
+
/** `http://127.0.0.1:<port>` */readonly url: string; /** The port the OS assigned. */
|
|
30
|
+
readonly port: number; /** The Fastify instance, for `inject()` or anything else Fastify offers. */
|
|
31
|
+
readonly server: FastifyInstance; /** A client for this server, signed in as `user` with a real access token. */
|
|
32
|
+
actingAs(user: TestAuthenticableUser): Promise<AuthenticatedTestClient>; /** Stop listening and remove the routes `routes` registered. */
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Start an in-process HTTP server with the production plugins, on a free port.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const app = await createTestHttpServer({
|
|
40
|
+
* routes: (router) => router.post("/echo", ({ request, response }) =>
|
|
41
|
+
* response.success({ body: request.all() })),
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* const response = await app.post("/echo", { name: "Ada" });
|
|
45
|
+
* expect(response.status).toBe(200);
|
|
46
|
+
*
|
|
47
|
+
* await app.close();
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // every app route, with the fakes seeing what the controller did
|
|
51
|
+
* const app = await createTestHttpServer({
|
|
52
|
+
* loadAppModules: true,
|
|
53
|
+
* importModule: (file) => import(file),
|
|
54
|
+
* });
|
|
55
|
+
*
|
|
56
|
+
* await app.post("/contact", { email: "ada@example.test", message: "Hi" });
|
|
57
|
+
* mailFake.assertSent({ to: "support@example.test" });
|
|
58
|
+
*/
|
|
59
|
+
declare function createTestHttpServer(options?: CreateTestHttpServerOptions): Promise<TestHttpServer>;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { CreateTestHttpServerOptions, TestHttpServer, createTestHttpServer };
|
|
62
|
+
//# sourceMappingURL=test-http-server.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-http-server.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/tests/test-http-server.ts"],"mappings":";;;;;;;KA+BY,2BAAA;;AAAZ;;;EAKE,MAAA,IAAU,MAAA,EAAQ,MAAA;EAAlB;;;;;EAMA,cAAA;EAQA;;AAAI;AAGN;;EALE,YAAA,GAAe,iBAAiB,EAKL;EAH3B,IAAA;AAAA;AAAA,KAGU,cAAA,GAAiB,iBAAA;EAQY,yCAN9B,GAAA,UAQO;EAAA,SANP,IAAA,UAJkB;EAAA,SAMlB,MAAA,EAAQ,eAAA,EAFR;EAIT,QAAA,CAAS,IAAA,EAAM,qBAAA,GAAwB,OAAA,CAAQ,uBAAA,GAF9B;EAIjB,KAAA,IAAS,OAAA;AAAA;;;;;;;AAAO;AAmElB;;;;;;;;;;;;;AAEyB;;;;iBAFH,oBAAA,CACpB,OAAA,GAAS,2BAAA,GACR,OAAA,CAAQ,cAAA"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { validateCspConfigAtBoot } from "../http/csp.mjs";
|
|
2
|
+
import { router } from "../router/router.mjs";
|
|
3
|
+
import { registerHttpPlugins } from "../http/plugins.mjs";
|
|
4
|
+
import { startHttpServer } from "../http/server.mjs";
|
|
5
|
+
import { setAppModuleImporter } from "../loader/app-module-importer.mjs";
|
|
6
|
+
import { createTestRequestClient } from "./test-helpers.mjs";
|
|
7
|
+
import { actingAs } from "./authenticated-test-client.mjs";
|
|
8
|
+
|
|
9
|
+
//#region ../../packages/core/src/tests/test-http-server.ts
|
|
10
|
+
/**
|
|
11
|
+
* An HTTP server inside the test process, built the way production builds it.
|
|
12
|
+
*
|
|
13
|
+
* `startHttpTestServer()` runs one server for the whole run, in Vitest's main
|
|
14
|
+
* process. This is the other shape: a server per test file (or per test) in the
|
|
15
|
+
* worker itself, on an OS-assigned port. Two things follow from living in the
|
|
16
|
+
* worker:
|
|
17
|
+
*
|
|
18
|
+
* - Requests run through the REAL stack: the Fastify instance `startHttpServer`
|
|
19
|
+
* creates, every plugin `registerHttpPlugins` registers in production (rate
|
|
20
|
+
* limit, CORS, multipart, cookies, ...), and the router's real route
|
|
21
|
+
* registration. A test cannot pass against a request shape production never
|
|
22
|
+
* produces, and the plugins' own behaviour (what the rate-limit store is
|
|
23
|
+
* handed, how a cookie is parsed) is what runs.
|
|
24
|
+
* - The test fakes see everything the request did: mail sent and jobs pushed
|
|
25
|
+
* by the controller land in this process's `mailFake` / `queueFake`.
|
|
26
|
+
*/
|
|
27
|
+
const APP_MODULES_LOADED_KEY = Symbol.for("warlock.core.testAppModulesLoaded");
|
|
28
|
+
let serverCounter = 0;
|
|
29
|
+
async function loadAppModulesOnce(importModule) {
|
|
30
|
+
const holder = globalThis;
|
|
31
|
+
holder[APP_MODULES_LOADED_KEY] ??= (async () => {
|
|
32
|
+
const { filesOrchestrator } = await import("../dev-server/files-orchestrator.mjs");
|
|
33
|
+
setAppModuleImporter(importModule);
|
|
34
|
+
try {
|
|
35
|
+
await filesOrchestrator.init();
|
|
36
|
+
await filesOrchestrator.initializeAll();
|
|
37
|
+
await filesOrchestrator.moduleLoader.loadAll();
|
|
38
|
+
} finally {
|
|
39
|
+
setAppModuleImporter(void 0);
|
|
40
|
+
}
|
|
41
|
+
})().catch((error) => {
|
|
42
|
+
holder[APP_MODULES_LOADED_KEY] = void 0;
|
|
43
|
+
throw error;
|
|
44
|
+
});
|
|
45
|
+
await holder[APP_MODULES_LOADED_KEY];
|
|
46
|
+
}
|
|
47
|
+
function boundPort(server) {
|
|
48
|
+
const address = server.server.address();
|
|
49
|
+
if (address === null || typeof address === "string") throw new Error("createTestHttpServer(): the server did not report a TCP port after listen().");
|
|
50
|
+
return address.port;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Start an in-process HTTP server with the production plugins, on a free port.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* const app = await createTestHttpServer({
|
|
57
|
+
* routes: (router) => router.post("/echo", ({ request, response }) =>
|
|
58
|
+
* response.success({ body: request.all() })),
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* const response = await app.post("/echo", { name: "Ada" });
|
|
62
|
+
* expect(response.status).toBe(200);
|
|
63
|
+
*
|
|
64
|
+
* await app.close();
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // every app route, with the fakes seeing what the controller did
|
|
68
|
+
* const app = await createTestHttpServer({
|
|
69
|
+
* loadAppModules: true,
|
|
70
|
+
* importModule: (file) => import(file),
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* await app.post("/contact", { email: "ada@example.test", message: "Hi" });
|
|
74
|
+
* mailFake.assertSent({ to: "support@example.test" });
|
|
75
|
+
*/
|
|
76
|
+
async function createTestHttpServer(options = {}) {
|
|
77
|
+
const host = options.host ?? "127.0.0.1";
|
|
78
|
+
const sourceFile = `test-http-server-${++serverCounter}`;
|
|
79
|
+
if (options.loadAppModules) await loadAppModulesOnce(options.importModule);
|
|
80
|
+
if (options.routes) {
|
|
81
|
+
const register = options.routes;
|
|
82
|
+
await router.withSourceFile(sourceFile, () => register(router));
|
|
83
|
+
}
|
|
84
|
+
validateCspConfigAtBoot();
|
|
85
|
+
const server = startHttpServer();
|
|
86
|
+
try {
|
|
87
|
+
await registerHttpPlugins(server);
|
|
88
|
+
router.scan(server);
|
|
89
|
+
await server.listen({
|
|
90
|
+
port: 0,
|
|
91
|
+
host
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
router.removeRoutesBySourceFile(sourceFile);
|
|
95
|
+
await server.close().catch(() => void 0);
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
const port = boundPort(server);
|
|
99
|
+
const url = `http://${host.includes(":") ? `[${host}]` : host}:${port}`;
|
|
100
|
+
const client = createTestRequestClient(url);
|
|
101
|
+
let closed = false;
|
|
102
|
+
return {
|
|
103
|
+
...client,
|
|
104
|
+
url,
|
|
105
|
+
port,
|
|
106
|
+
server,
|
|
107
|
+
actingAs: (user) => actingAs(user, client),
|
|
108
|
+
close: async () => {
|
|
109
|
+
if (closed) return;
|
|
110
|
+
closed = true;
|
|
111
|
+
router.removeRoutesBySourceFile(sourceFile);
|
|
112
|
+
await server.close();
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
export { createTestHttpServer };
|
|
119
|
+
//# sourceMappingURL=test-http-server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-http-server.mjs","names":[],"sources":["../../../../../../../../packages/core/src/tests/test-http-server.ts"],"sourcesContent":["/**\n * An HTTP server inside the test process, built the way production builds it.\n *\n * `startHttpTestServer()` runs one server for the whole run, in Vitest's main\n * process. This is the other shape: a server per test file (or per test) in the\n * worker itself, on an OS-assigned port. Two things follow from living in the\n * worker:\n *\n * - Requests run through the REAL stack: the Fastify instance `startHttpServer`\n * creates, every plugin `registerHttpPlugins` registers in production (rate\n * limit, CORS, multipart, cookies, ...), and the router's real route\n * registration. A test cannot pass against a request shape production never\n * produces, and the plugins' own behaviour (what the rate-limit store is\n * handed, how a cookie is parsed) is what runs.\n * - The test fakes see everything the request did: mail sent and jobs pushed\n * by the controller land in this process's `mailFake` / `queueFake`.\n */\nimport { router } from \"../router/router\";\nimport type { Router } from \"../router/router\";\nimport type { AppModuleImporter } from \"../loader/app-module-importer\";\nimport { setAppModuleImporter } from \"../loader/app-module-importer\";\nimport { validateCspConfigAtBoot } from \"../http/csp\";\nimport { registerHttpPlugins } from \"../http/plugins\";\nimport { type FastifyInstance, startHttpServer } from \"../http/server\";\nimport {\n type AuthenticatedTestClient,\n type TestAuthenticableUser,\n actingAs,\n} from \"./authenticated-test-client\";\nimport { type TestRequestClient, createTestRequestClient } from \"./test-helpers\";\n\nexport type CreateTestHttpServerOptions = {\n /**\n * Register routes for this server, exactly as a `routes.ts` would. They are\n * tagged with a source of their own and removed again by `close()`.\n */\n routes?: (router: Router) => unknown;\n /**\n * Load the application's modules (`routes.ts`, `main.ts`, events, ...) first,\n * so the server answers every app route. Needs `setupTest()` to have run in\n * this worker. Modules load once per process; later servers reuse them.\n */\n loadAppModules?: boolean;\n /**\n * How app modules are imported when `loadAppModules` is set. Pass\n * `(file) => import(file)` from the test file, as `setupTest()` takes it, so\n * app classes are the same ones the test imports.\n */\n importModule?: AppModuleImporter;\n /** Interface to bind. Defaults to `127.0.0.1`. */\n host?: string;\n};\n\nexport type TestHttpServer = TestRequestClient & {\n /** `http://127.0.0.1:<port>` */\n readonly url: string;\n /** The port the OS assigned. */\n readonly port: number;\n /** The Fastify instance, for `inject()` or anything else Fastify offers. */\n readonly server: FastifyInstance;\n /** A client for this server, signed in as `user` with a real access token. */\n actingAs(user: TestAuthenticableUser): Promise<AuthenticatedTestClient>;\n /** Stop listening and remove the routes `routes` registered. */\n close(): Promise<void>;\n};\n\nconst APP_MODULES_LOADED_KEY = Symbol.for(\"warlock.core.testAppModulesLoaded\");\n\ntype LoadedHolder = { [APP_MODULES_LOADED_KEY]?: Promise<void> };\n\nlet serverCounter = 0;\n\nasync function loadAppModulesOnce(importModule: AppModuleImporter | undefined): Promise<void> {\n const holder = globalThis as LoadedHolder;\n\n holder[APP_MODULES_LOADED_KEY] ??= (async () => {\n const { filesOrchestrator } = await import(\"../dev-server/files-orchestrator\");\n\n setAppModuleImporter(importModule);\n\n try {\n await filesOrchestrator.init();\n await filesOrchestrator.initializeAll();\n await filesOrchestrator.moduleLoader.loadAll();\n } finally {\n setAppModuleImporter(undefined);\n }\n })().catch((error: unknown) => {\n // A failed load may be retried by the next server.\n holder[APP_MODULES_LOADED_KEY] = undefined;\n throw error;\n });\n\n await holder[APP_MODULES_LOADED_KEY];\n}\n\nfunction boundPort(server: FastifyInstance): number {\n const address = server.server.address();\n\n if (address === null || typeof address === \"string\") {\n throw new Error(\"createTestHttpServer(): the server did not report a TCP port after listen().\");\n }\n\n return address.port;\n}\n\n/**\n * Start an in-process HTTP server with the production plugins, on a free port.\n *\n * @example\n * const app = await createTestHttpServer({\n * routes: (router) => router.post(\"/echo\", ({ request, response }) =>\n * response.success({ body: request.all() })),\n * });\n *\n * const response = await app.post(\"/echo\", { name: \"Ada\" });\n * expect(response.status).toBe(200);\n *\n * await app.close();\n *\n * @example\n * // every app route, with the fakes seeing what the controller did\n * const app = await createTestHttpServer({\n * loadAppModules: true,\n * importModule: (file) => import(file),\n * });\n *\n * await app.post(\"/contact\", { email: \"ada@example.test\", message: \"Hi\" });\n * mailFake.assertSent({ to: \"support@example.test\" });\n */\nexport async function createTestHttpServer(\n options: CreateTestHttpServerOptions = {},\n): Promise<TestHttpServer> {\n const host = options.host ?? \"127.0.0.1\";\n const sourceFile = `test-http-server-${++serverCounter}`;\n\n if (options.loadAppModules) {\n await loadAppModulesOnce(options.importModule);\n }\n\n if (options.routes) {\n const register = options.routes;\n\n await router.withSourceFile(sourceFile, () => register(router));\n }\n\n validateCspConfigAtBoot();\n\n const server = startHttpServer();\n\n try {\n await registerHttpPlugins(server);\n router.scan(server);\n await server.listen({ port: 0, host });\n } catch (error) {\n router.removeRoutesBySourceFile(sourceFile);\n await server.close().catch(() => undefined);\n throw error;\n }\n\n const port = boundPort(server);\n const url = `http://${host.includes(\":\") ? `[${host}]` : host}:${port}`;\n const client = createTestRequestClient(url);\n let closed = false;\n\n return {\n ...client,\n url,\n port,\n server,\n actingAs: (user) => actingAs(user, client),\n close: async () => {\n if (closed) return;\n\n closed = true;\n router.removeRoutesBySourceFile(sourceFile);\n await server.close();\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAkEA,MAAM,yBAAyB,OAAO,IAAI,mCAAmC;AAI7E,IAAI,gBAAgB;AAEpB,eAAe,mBAAmB,cAA4D;CAC5F,MAAM,SAAS;CAEf,OAAO,6BAA6B,YAAY;EAC9C,MAAM,EAAE,sBAAsB,MAAM,OAAO;EAE3C,qBAAqB,YAAY;EAEjC,IAAI;GACF,MAAM,kBAAkB,KAAK;GAC7B,MAAM,kBAAkB,cAAc;GACtC,MAAM,kBAAkB,aAAa,QAAQ;EAC/C,UAAU;GACR,qBAAqB,MAAS;EAChC;CACF,EAAC,CAAE,CAAC,CAAC,OAAO,UAAmB;EAE7B,OAAO,0BAA0B;EACjC,MAAM;CACR,CAAC;CAED,MAAM,OAAO;AACf;AAEA,SAAS,UAAU,QAAiC;CAClD,MAAM,UAAU,OAAO,OAAO,QAAQ;CAEtC,IAAI,YAAY,QAAQ,OAAO,YAAY,UACzC,MAAM,IAAI,MAAM,8EAA8E;CAGhG,OAAO,QAAQ;AACjB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,eAAsB,qBACpB,UAAuC,CAAC,GACf;CACzB,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,aAAa,oBAAoB,EAAE;CAEzC,IAAI,QAAQ,gBACV,MAAM,mBAAmB,QAAQ,YAAY;CAG/C,IAAI,QAAQ,QAAQ;EAClB,MAAM,WAAW,QAAQ;EAEzB,MAAM,OAAO,eAAe,kBAAkB,SAAS,MAAM,CAAC;CAChE;CAEA,wBAAwB;CAExB,MAAM,SAAS,gBAAgB;CAE/B,IAAI;EACF,MAAM,oBAAoB,MAAM;EAChC,OAAO,KAAK,MAAM;EAClB,MAAM,OAAO,OAAO;GAAE,MAAM;GAAG;EAAK,CAAC;CACvC,SAAS,OAAO;EACd,OAAO,yBAAyB,UAAU;EAC1C,MAAM,OAAO,MAAM,CAAC,CAAC,YAAY,MAAS;EAC1C,MAAM;CACR;CAEA,MAAM,OAAO,UAAU,MAAM;CAC7B,MAAM,MAAM,UAAU,KAAK,SAAS,GAAG,IAAI,IAAI,KAAK,KAAK,KAAK,GAAG;CACjE,MAAM,SAAS,wBAAwB,GAAG;CAC1C,IAAI,SAAS;CAEb,OAAO;EACL,GAAG;EACH;EACA;EACA;EACA,WAAW,SAAS,SAAS,MAAM,MAAM;EACzC,OAAO,YAAY;GACjB,IAAI,QAAQ;GAEZ,SAAS;GACT,OAAO,yBAAyB,UAAU;GAC1C,MAAM,OAAO,MAAM;EACrB;CACF;AACF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TestFakesSelection } from "./fakes/test-fakes.mjs";
|
|
1
2
|
import { AppModuleImporter } from "../loader/app-module-importer.mjs";
|
|
2
3
|
import { TestConnectorsSelection } from "./test-connectors-selection.mjs";
|
|
3
4
|
|
|
@@ -22,6 +23,16 @@ type TestSetupOptions = {
|
|
|
22
23
|
* fails. Defaults to native import.
|
|
23
24
|
*/
|
|
24
25
|
importModule?: AppModuleImporter;
|
|
26
|
+
/**
|
|
27
|
+
* Which test fakes to install once the connectors are up: mail, queue,
|
|
28
|
+
* cache, storage, clock and ids (see `installTestFakes`).
|
|
29
|
+
*
|
|
30
|
+
* Precedence is `explicit option > tests.fakes config > true`, so every fake
|
|
31
|
+
* is on unless the project or this call opts out. `false` keeps the real
|
|
32
|
+
* drivers; `{ cache: false }` keeps only the real cache. Teardown restores
|
|
33
|
+
* them.
|
|
34
|
+
*/
|
|
35
|
+
fakes?: TestFakesSelection;
|
|
25
36
|
};
|
|
26
37
|
/**
|
|
27
38
|
* Warn — once per process — that app modules are loaded outside Vitest's graph.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest-setup.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/tests/vitest-setup.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"vitest-setup.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/tests/vitest-setup.ts"],"mappings":";;;;;KAqDY,gBAAA;EAAA;;;;;;;;EASV,UAAA,GAAa,uBAAA;EAAA;;;;;;AAoBa;AAQ5B;;EAlBE,YAAA,GAAe,iBAAA;EAkBqC;AAA6B;AAkBnF;;;;;;;EA1BE,KAAA,GAAQ,kBAAA;AAAA;AAgEV;;;AAAA,iBAxDgB,uBAAA,CAAwB,YAA2C,EAA7B,iBAAiB;;;;;cAkB1D,kBAAA,SAA2B,KAAK;cACxB,OAAA;AAAA;;;AA+FwB;;;;;;;;;;;;;;;;;;;;;;;;;iBA1DvB,SAAA,CAAU,OAAA,GAAU,gBAAA,GAAmB,OAAO;;;;;;;;;;;;;;;;;;iBA0D9C,YAAA,IAAgB,OAAO"}
|
|
@@ -6,6 +6,7 @@ import "../connectors/index.mjs";
|
|
|
6
6
|
import { setAppModuleImporter } from "../loader/app-module-importer.mjs";
|
|
7
7
|
import { filesOrchestrator } from "../dev-server/files-orchestrator.mjs";
|
|
8
8
|
import { loadConfigFiles } from "../config/load-config-files.mjs";
|
|
9
|
+
import { installTestFakes, restoreTestFakes } from "./fakes/test-fakes.mjs";
|
|
9
10
|
import { describeConnectorsSelection, isSameConnectorsSelection, normalizeConnectorNames, readRequestedConnectors, resolveEffectiveConnectors } from "./test-connectors-selection.mjs";
|
|
10
11
|
import { getTestLifecycleRegistry } from "./test-lifecycle-state.mjs";
|
|
11
12
|
import { DEFAULT_TEST_SETUP_TIMEOUT, describeExpiredSetup, readConfiguredSetupTimeout, scheduleRealTimeout } from "./test-setup-timeout.mjs";
|
|
@@ -103,7 +104,7 @@ async function setupTest(options) {
|
|
|
103
104
|
}
|
|
104
105
|
setAppModuleImporter(options?.importModule);
|
|
105
106
|
try {
|
|
106
|
-
return await startTestRuntime(requested);
|
|
107
|
+
return await startTestRuntime(requested, options?.fakes);
|
|
107
108
|
} finally {
|
|
108
109
|
setAppModuleImporter(void 0);
|
|
109
110
|
}
|
|
@@ -214,7 +215,7 @@ async function awaitSetupWithinBound(attempt, bound, registry) {
|
|
|
214
215
|
/**
|
|
215
216
|
* Open a new setup attempt and publish it before it runs.
|
|
216
217
|
*/
|
|
217
|
-
function startTestRuntime(requested) {
|
|
218
|
+
function startTestRuntime(requested, fakes) {
|
|
218
219
|
const registry = getTestLifecycleRegistry();
|
|
219
220
|
let publishEffectiveSelection = () => void 0;
|
|
220
221
|
let failEffectiveSelection = () => void 0;
|
|
@@ -224,7 +225,7 @@ function startTestRuntime(requested) {
|
|
|
224
225
|
});
|
|
225
226
|
effectiveSelection.catch(() => void 0);
|
|
226
227
|
const bound = armSetupBound(registry);
|
|
227
|
-
const attempt = Promise.resolve().then(() => runTestRuntimeStartup(requested, publishEffectiveSelection, failEffectiveSelection, bound));
|
|
228
|
+
const attempt = Promise.resolve().then(() => runTestRuntimeStartup(requested, publishEffectiveSelection, failEffectiveSelection, bound, fakes));
|
|
228
229
|
attempt.catch(() => void 0);
|
|
229
230
|
const completion = awaitSetupWithinBound(attempt, bound, registry);
|
|
230
231
|
registry.state = "starting";
|
|
@@ -242,7 +243,7 @@ function startTestRuntime(requested) {
|
|
|
242
243
|
* unchanged. A cleanup failure is a secondary diagnostic and never replaces the
|
|
243
244
|
* cause the caller needs to read.
|
|
244
245
|
*/
|
|
245
|
-
async function runTestRuntimeStartup(requested, publishEffectiveSelection, failEffectiveSelection, bound) {
|
|
246
|
+
async function runTestRuntimeStartup(requested, publishEffectiveSelection, failEffectiveSelection, bound, fakes) {
|
|
246
247
|
const registry = getTestLifecycleRegistry();
|
|
247
248
|
try {
|
|
248
249
|
Application.setEnvironment("test");
|
|
@@ -256,6 +257,7 @@ async function runTestRuntimeStartup(requested, publishEffectiveSelection, failE
|
|
|
256
257
|
if (!bound.hasExpired()) registry.activeSelection = effectiveSelection;
|
|
257
258
|
publishEffectiveSelection(effectiveSelection);
|
|
258
259
|
await startSelectedConnectors(effectiveSelection);
|
|
260
|
+
await installTestFakes(fakes);
|
|
259
261
|
if (!bound.hasExpired()) {
|
|
260
262
|
registry.state = "ready";
|
|
261
263
|
registry.setupAttempt = void 0;
|
|
@@ -292,6 +294,7 @@ async function startSelectedConnectors(selection) {
|
|
|
292
294
|
* step is attempted independently.
|
|
293
295
|
*/
|
|
294
296
|
async function unwindPartialStartup() {
|
|
297
|
+
await attemptCleanupStep("test fakes", () => restoreTestFakes());
|
|
295
298
|
await attemptCleanupStep("application shutdown hooks", () => Application.runShutdownHooks());
|
|
296
299
|
await attemptCleanupStep("connectors shutdown", () => connectorsManager.shutdown());
|
|
297
300
|
}
|
|
@@ -312,6 +315,11 @@ async function runTestRuntimeShutdown() {
|
|
|
312
315
|
const registry = getTestLifecycleRegistry();
|
|
313
316
|
let shutdownSucceeded = false;
|
|
314
317
|
try {
|
|
318
|
+
try {
|
|
319
|
+
await restoreTestFakes();
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error("[vitest-setup] Restoring the test fakes did not complete:", error);
|
|
322
|
+
}
|
|
315
323
|
await connectorsManager.shutdown();
|
|
316
324
|
shutdownSucceeded = true;
|
|
317
325
|
} finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest-setup.mjs","names":[],"sources":["../../../../../../../../packages/core/src/tests/vitest-setup.ts"],"sourcesContent":["/**\n * Worker test lifecycle — `setupTest` / `teardownTest`.\n *\n * Implements `contracts/2026-08-12-test-worker-lifecycle.md`. The pair is\n * context-scoped, not \"once per worker\": Vitest runs `setupFiles` before EVERY\n * test file and rebuilds the module registry each time, so the harness that\n * calls `setupTest` owns pairing it with `teardownTest` in the same runtime\n * context.\n *\n * Ownership, stated once because teardown is wider than it looks:\n *\n * - the runtime assumes exclusive ownership of the process-local\n * `connectorsManager` for its lifetime;\n * - it owns the framework bootstrap, the application shutdown hooks, and the\n * connectors it starts — including the `connectors: false` case, where\n * bootstrap still registers hooks;\n * - teardown is MANAGER-WIDE. `connectorsManager.shutdown()` has no selective\n * ownership handle, so a lifecycle that started two connectors still closes\n * every connector the manager holds. Mixing `setupTest` with manual connector\n * startup in the same process is unsupported for exactly that reason.\n * - HTTP global setup stays separately owned by `startHttpTestServer` /\n * `stopHttpTestServer`.\n */\nimport { Application } from \"../application/application\";\nimport { bootstrap } from \"../bootstrap\";\nimport { setAppModuleImporter } from \"../loader/app-module-importer\";\nimport type { AppModuleImporter } from \"../loader/app-module-importer\";\nimport { loadConfigFiles } from \"../config/load-config-files\";\nimport { connectorsManager } from \"../connectors\";\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport {\n describeConnectorsSelection,\n isSameConnectorsSelection,\n normalizeConnectorNames,\n readRequestedConnectors,\n resolveEffectiveConnectors,\n} from \"./test-connectors-selection\";\nimport type { RequestedTestConnectors, TestConnectorsSelection } from \"./test-connectors-selection\";\nimport { getTestLifecycleRegistry } from \"./test-lifecycle-state\";\nimport type { TestLifecycleRegistry, TestSetupAttempt } from \"./test-lifecycle-state\";\nimport {\n DEFAULT_TEST_SETUP_TIMEOUT,\n describeExpiredSetup,\n readConfiguredSetupTimeout,\n scheduleRealTimeout,\n} from \"./test-setup-timeout\";\nimport type { TestTimeoutHandle } from \"./test-setup-timeout\";\n\nexport type { TestConnectorsSelection } from \"./test-connectors-selection\";\n\nexport type TestSetupOptions = {\n /**\n * Which connectors to start.\n *\n * Precedence is `explicit non-undefined option > tests.connectors config >\n * true`. Omitting the property, or passing `undefined`, lets project config\n * apply — so an optional variable that happens to be `undefined` cannot erase\n * it.\n */\n connectors?: TestConnectorsSelection;\n /**\n * Import hook for APPLICATION modules (config, routes, events, ...).\n *\n * Pass `(file) => import(file)` from your `src/test-setup.ts`: that file is\n * app code, so Vitest handles the `import()` and app modules land in the same\n * module graph as your test files. Without it Warlock imports them natively,\n * one worker holds two copies of every class (`User`, ...), and `instanceof`\n * fails. Defaults to native import.\n */\n importModule?: AppModuleImporter;\n};\n\nconst IMPORT_MODULE_WARNED_KEY = Symbol.for(\"warlock.core.importModuleWarned\");\n\n/**\n * Warn — once per process — that app modules are loaded outside Vitest's graph.\n */\nexport function warnMissingImportModule(importModule: AppModuleImporter | undefined): void {\n if (importModule || !process.env.VITEST) return;\n\n const holder = globalThis as Record<symbol, unknown>;\n\n if (holder[IMPORT_MODULE_WARNED_KEY]) return;\n\n holder[IMPORT_MODULE_WARNED_KEY] = true;\n\n console.warn(\n \"[warlock] Warlock loads app modules outside Vitest's module graph, so classes like User exist twice and instanceof fails. Pass importModule: (file) => import(file) to setupTest().\",\n );\n}\n\n/**\n * Raised by the lifecycle itself, never by the runtime it manages — a conflict\n * between two calls, or a refusal to reuse a runtime that did not close.\n */\nexport class TestLifecycleError extends Error {\n public constructor(message: string) {\n super(message);\n\n this.name = \"TestLifecycleError\";\n }\n}\n\nconst POISONED_MESSAGE =\n \"setupTest() refuses to start a test runtime: an earlier teardownTest() reported a shutdown failure, so the connectors, ports and pools it owned are not known to be closed. Restart the Vitest worker before setting up again, or call teardownTest() to retry the shutdown — the lifecycle only returns to idle on a fully successful retry.\";\n\n/**\n * Bootstrap the test runtime and start the selected connectors.\n *\n * ⚠ Runs once per TEST FILE, not once per worker: `setupFiles` is executed for\n * every file and its module registry is rebuilt with it, measured across both\n * pools with isolation on and off. The lifecycle state lives on the runtime\n * context rather than in this module precisely so that a rebuild cannot hide a\n * runtime that is still up.\n *\n * Repeated calls:\n *\n * - concurrent calls with the same effective selection share one startup;\n * - while ready, the same effective selection is a no-op;\n * - while starting or ready, a DIFFERENT effective selection rejects and leaves\n * the live runtime untouched;\n * - a failed setup unwinds and returns to idle, so a clean retry is allowed;\n * - after a successful `teardownTest`, a later setup may use a different\n * selection.\n *\n * @example\n * // let project config decide, falling back to the default set\n * await setupTest();\n *\n * @example\n * // bootstrap only — no connectors, whatever config says\n * await setupTest({ connectors: false });\n */\nexport async function setupTest(options?: TestSetupOptions): Promise<void> {\n warnMissingImportModule(options?.importModule);\n\n const registry = getTestLifecycleRegistry();\n const requested = readRequestedConnectors(options?.connectors);\n\n if (registry.state === \"poisoned\") {\n throw new TestLifecycleError(POISONED_MESSAGE);\n }\n\n if (registry.state === \"stopping\" && registry.teardownAttempt) {\n await settled(registry.teardownAttempt);\n\n return setupTest(options);\n }\n\n if (registry.state === \"ready\") {\n assertReadySelectionMatches(registry.activeSelection, requested);\n\n return;\n }\n\n if (registry.state === \"starting\" && registry.setupAttempt) {\n const attempt = registry.setupAttempt;\n\n await assertPendingSetupMatches(attempt, requested);\n\n return attempt.completion;\n }\n\n // Scoped to this startup: the loaders read it while app modules import, and\n // it is cleared whatever the outcome so it never leaks past setup.\n setAppModuleImporter(options?.importModule);\n\n try {\n return await startTestRuntime(requested);\n } finally {\n setAppModuleImporter(undefined);\n }\n}\n\n/**\n * Shut the test runtime down and release the lifecycle.\n *\n * - idle is a no-op;\n * - concurrent calls share one attempt;\n * - a call made while setup is still running waits for that attempt to settle,\n * then closes the runtime if it succeeded;\n * - the local \"ready\" state is always cleared, including when shutdown rejects;\n * - a shutdown rejection is surfaced, never swallowed, and poisons the\n * lifecycle — a reported close failure is not proof that anything closed, so\n * later `setupTest` calls refuse until the worker is restarted or a retry\n * fully succeeds.\n *\n * ⚠ Individual connector shutdown failures are caught and logged inside\n * `connectorsManager.shutdown()`, so they never reach this function and never\n * poison anything. This lifecycle can only surface what that layer reports.\n */\nexport async function teardownTest(): Promise<void> {\n const registry = getTestLifecycleRegistry();\n\n if (registry.state === \"stopping\" && registry.teardownAttempt) {\n return registry.teardownAttempt;\n }\n\n if (registry.state === \"starting\" && registry.setupAttempt) {\n // A runtime that is still opening cannot be closed halfway. Wait for the\n // attempt to settle: a failed one has already unwound itself and left the\n // lifecycle idle, which the re-entry below then reads as \"nothing to do\".\n await settled(registry.setupAttempt.completion);\n\n return teardownTest();\n }\n\n if (registry.state === \"idle\") {\n return;\n }\n\n registry.state = \"stopping\";\n\n // Deferred by a microtask so the registry is fully published before the\n // attempt's first line runs — a synchronous throw would otherwise reach the\n // `finally` below, and reset the state, before this function had recorded\n // that a teardown was in flight at all.\n const attempt = Promise.resolve().then(() => runTestRuntimeShutdown());\n\n registry.teardownAttempt = attempt;\n\n return attempt;\n}\n\n/**\n * The setup attempt's hang guard, as the lifecycle consumes it.\n *\n * The bound's POLICY — the default, the config key, the message — lives in\n * `test-setup-timeout.ts`. What expiry MEANS lives here.\n *\n * @internal\n */\ntype SetupBound = {\n /**\n * Rejects with the expiry error. Never resolves: the attempt is the only side\n * of the race that can succeed.\n */\n readonly expiry: Promise<never>;\n\n /**\n * Re-aim the bound at `next` ms measured from when the ATTEMPT STARTED, once\n * config has made `tests.setupTimeout` readable.\n */\n rearm(next: number): void;\n\n /** The race is over — cancel whatever timer is still pending. */\n settle(): void;\n\n /** Whether the bound, rather than the attempt, won the race. */\n hasExpired(): boolean;\n};\n\n/**\n * Arm the hang guard for one setup attempt.\n *\n * Armed at the default BEFORE the attempt runs, because `tests.setupTimeout` is\n * not readable until the attempt has loaded config — and loading config is\n * itself one of the steps that can hang. `rearm` then narrows or widens it the\n * moment config arrives.\n */\nfunction armSetupBound(registry: TestLifecycleRegistry): SetupBound {\n const schedule = registry.scheduleTimeout ?? scheduleRealTimeout;\n const armedAt = Date.now();\n\n let handle: TestTimeoutHandle | undefined;\n let expired = false;\n let closed = false;\n let expire: (error: unknown) => void = () => undefined;\n\n const expiry = new Promise<never>((_resolve, reject) => {\n expire = reject;\n });\n\n // Only `awaitSetupWithinBound` observes this, and only until the attempt wins\n // — after that nobody is listening, and an unobserved rejection surfaces as an\n // unhandled rejection that fails an unrelated test file. Same reason as the\n // `effectiveSelection` barrier below.\n expiry.catch(() => undefined);\n\n const expireNow = (bound: number): void => {\n if (closed) {\n return;\n }\n\n closed = true;\n expired = true;\n\n handle?.cancel();\n handle = undefined;\n\n expire(new TestLifecycleError(describeExpiredSetup(bound)));\n };\n\n const armFor = (bound: number, delay: number): void => {\n if (closed) {\n return;\n }\n\n handle?.cancel();\n\n // Already overdue. A bound the attempt has ALREADY exceeded must fire now,\n // not be rescheduled for time that was spent before it was known.\n if (delay <= 0) {\n expireNow(bound);\n\n return;\n }\n\n handle = schedule(delay, () => expireNow(bound));\n };\n\n const initialBound = registry.setupTimeoutOverride ?? DEFAULT_TEST_SETUP_TIMEOUT;\n\n armFor(initialBound, initialBound);\n\n return {\n expiry,\n\n // Measured from `armedAt`, never from now: adding a configured bound to the\n // time already spent would make the real bound default-plus-configured, and\n // a project that lowered the key would get a LONGER guard than the default.\n rearm: (next) => armFor(next, next - (Date.now() - armedAt)),\n\n settle: () => {\n closed = true;\n\n handle?.cancel();\n handle = undefined;\n },\n\n hasExpired: () => expired,\n };\n}\n\n/**\n * Race one setup attempt against its bound.\n *\n * The losing attempt is NOT cancellable — nothing here can interrupt a bootstrap\n * stuck in a socket connect — so when the bound wins, the lifecycle is poisoned\n * and every later state write from that abandoned attempt is dropped. That\n * dropping happens in `runTestRuntimeStartup`, guarded on `hasExpired()`;\n * without it the still-running attempt would quietly overwrite `poisoned` with\n * `ready` or `idle` and hand the next caller a runtime nobody owns.\n */\nasync function awaitSetupWithinBound(\n attempt: Promise<void>,\n bound: SetupBound,\n registry: TestLifecycleRegistry,\n): Promise<void> {\n try {\n await Promise.race([attempt, bound.expiry]);\n } catch (error) {\n if (bound.hasExpired()) {\n registry.state = \"poisoned\";\n registry.activeSelection = undefined;\n registry.setupAttempt = undefined;\n }\n\n throw error;\n } finally {\n bound.settle();\n }\n}\n\n/**\n * Open a new setup attempt and publish it before it runs.\n */\nfunction startTestRuntime(requested: RequestedTestConnectors): Promise<void> {\n const registry = getTestLifecycleRegistry();\n\n let publishEffectiveSelection: (selection: TestConnectorsSelection) => void = () => undefined;\n let failEffectiveSelection: (error: unknown) => void = () => undefined;\n\n const effectiveSelection = new Promise<TestConnectorsSelection>((resolve, reject) => {\n publishEffectiveSelection = resolve;\n failEffectiveSelection = reject;\n });\n\n // Only the mixed explicit/config comparison ever awaits this barrier, so its\n // rejection is usually unobserved — and an unobserved rejection would surface\n // as an unhandled rejection and fail an unrelated test file.\n effectiveSelection.catch(() => undefined);\n\n const bound = armSetupBound(registry);\n\n const attempt = Promise.resolve().then(() =>\n runTestRuntimeStartup(requested, publishEffectiveSelection, failEffectiveSelection, bound),\n );\n\n // The attempt outlives a bound that beat it, and by then `completion` has\n // already rejected with the expiry error — so the attempt's own late rejection\n // has no observer. Same unhandled-rejection hazard as above.\n attempt.catch(() => undefined);\n\n // `teardownTest` waits on this same promise, which is how a teardown requested\n // during setup inherits the bound. There is deliberately no second timer and\n // no separate teardown deadline.\n const completion = awaitSetupWithinBound(attempt, bound, registry);\n\n registry.state = \"starting\";\n registry.setupAttempt = { requested, effectiveSelection, completion };\n\n return completion;\n}\n\n/**\n * The startup attempt itself.\n *\n * On failure: best-effort unwind, reset bookkeeping, rethrow the ORIGINAL error\n * unchanged. A cleanup failure is a secondary diagnostic and never replaces the\n * cause the caller needs to read.\n */\nasync function runTestRuntimeStartup(\n requested: RequestedTestConnectors,\n publishEffectiveSelection: (selection: TestConnectorsSelection) => void,\n failEffectiveSelection: (error: unknown) => void,\n bound: SetupBound,\n): Promise<void> {\n const registry = getTestLifecycleRegistry();\n\n try {\n Application.setEnvironment(\"test\");\n\n await warlockConfigManager.load();\n await bootstrap();\n\n await filesOrchestrator.init();\n await loadConfigFiles(true);\n\n // The first point at which `tests.setupTimeout` is readable at all. An\n // invalid value throws from here and takes the normal failure path — falling\n // back to the default would silently erase what the project configured.\n const configuredTimeout = readConfiguredSetupTimeout();\n\n if (configuredTimeout !== undefined) {\n bound.rearm(configuredTimeout);\n }\n\n // Resolved here and not at call time: the config layer is only readable once\n // the four steps above have run.\n const effectiveSelection = resolveEffectiveConnectors(requested);\n\n // Every state write below is conditional on this attempt still being the one\n // the lifecycle is waiting for. Once the bound has expired the lifecycle is\n // poisoned and this attempt is abandoned — it may still be running, but it\n // no longer speaks for the runtime.\n if (!bound.hasExpired()) {\n registry.activeSelection = effectiveSelection;\n }\n\n publishEffectiveSelection(effectiveSelection);\n\n await startSelectedConnectors(effectiveSelection);\n\n if (!bound.hasExpired()) {\n registry.state = \"ready\";\n registry.setupAttempt = undefined;\n }\n } catch (error) {\n failEffectiveSelection(error);\n\n await unwindPartialStartup();\n\n if (!bound.hasExpired()) {\n registry.state = \"idle\";\n registry.activeSelection = undefined;\n registry.setupAttempt = undefined;\n }\n\n console.error(\"[vitest-setup] Failed to setup test environment:\", error);\n\n throw error;\n }\n}\n\n/**\n * `false` means none at all. Everything else starts something: an array starts\n * exactly those, and `true` starts the default set — all but http, which is the\n * global setup's job and shared across every worker.\n */\nasync function startSelectedConnectors(selection: TestConnectorsSelection): Promise<void> {\n if (selection === false) {\n return;\n }\n\n if (Array.isArray(selection)) {\n await connectorsManager.start(normalizeConnectorNames(selection));\n\n return;\n }\n\n await connectorsManager.startWithout([\"http\"]);\n}\n\n/**\n * Best-effort teardown of whatever a failed startup managed to start.\n *\n * Runs while an error is already in flight, so no step here may throw and every\n * step is attempted independently.\n */\nasync function unwindPartialStartup(): Promise<void> {\n // Two steps rather than one: `connectorsManager.shutdown()` runs the\n // application hooks itself, but it runs them FIRST and does not isolate their\n // rejection — so a hook that rejects would take the connector teardown down\n // with it and leave every connector up. Both calls are idempotent, so running\n // the hooks on their own first costs nothing and means neither failure can\n // skip the other step.\n await attemptCleanupStep(\"application shutdown hooks\", () => Application.runShutdownHooks());\n await attemptCleanupStep(\"connectors shutdown\", () => connectorsManager.shutdown());\n}\n\n/**\n * Run one cleanup step, isolating its failure so the next step still runs.\n */\nasync function attemptCleanupStep(step: string, run: () => Promise<void>): Promise<void> {\n try {\n await run();\n } catch (error) {\n // Reported, not thrown — it is secondary to the startup error about to be\n // rethrown, and losing that one to this would be the worse outcome.\n console.error(`[vitest-setup] Cleanup after a failed setup did not complete (${step}):`, error);\n }\n}\n\n/**\n * The teardown attempt itself.\n */\nasync function runTestRuntimeShutdown(): Promise<void> {\n const registry = getTestLifecycleRegistry();\n let shutdownSucceeded = false;\n\n try {\n // Manager-wide, and deliberately called whatever the selection was: with\n // `connectors: false` nothing was started, but bootstrap still registered\n // application shutdown hooks, and the manager runs those first (see\n // `connectors-manager.ts` — `Application.runShutdownHooks()` is its first\n // line). Skipping the call to \"match\" an empty selection would leak them.\n await connectorsManager.shutdown();\n\n shutdownSucceeded = true;\n } finally {\n // In a `finally`, not after the `await`: a rejected shutdown must not leave\n // the lifecycle claiming a runtime it no longer owns. Poisoned rather than\n // idle, because a cleared flag does not prove that ports, sockets, pools or\n // timers closed.\n registry.activeSelection = undefined;\n registry.setupAttempt = undefined;\n registry.teardownAttempt = undefined;\n registry.state = shutdownSucceeded ? \"idle\" : \"poisoned\";\n }\n}\n\n/**\n * Reject when a ready runtime was set up with a different selection.\n */\nfunction assertReadySelectionMatches(\n activeSelection: TestConnectorsSelection | undefined,\n requested: RequestedTestConnectors,\n): void {\n if (activeSelection === undefined) {\n return;\n }\n\n // Config is loaded by the time anything is ready, so the config-derived layer\n // is resolvable right here.\n const requestedSelection = resolveEffectiveConnectors(requested);\n\n assertSelectionsMatch(activeSelection, requestedSelection);\n}\n\n/**\n * Reject when an in-flight setup attempt is starting a different selection.\n */\nasync function assertPendingSetupMatches(\n attempt: TestSetupAttempt,\n requested: RequestedTestConnectors,\n): Promise<void> {\n const pending = attempt.requested;\n\n // Two calls that both defer to project config resolve to the same thing by\n // construction — knowable without waiting for config to load.\n if (!pending.isExplicit && !requested.isExplicit) {\n return;\n }\n\n if (pending.isExplicit && requested.isExplicit) {\n assertSelectionsMatch(pending.selection, requested.selection);\n\n return;\n }\n\n // Mixed: one side is config-derived and config is not readable until the\n // running attempt has loaded it, so wait for that barrier first.\n const activeSelection = await attempt.effectiveSelection;\n const requestedSelection = resolveEffectiveConnectors(requested);\n\n assertSelectionsMatch(activeSelection, requestedSelection);\n}\n\n/**\n * Name both selections: a caller looking at this message has to be able to tell\n * which call to change without reading the framework's source.\n */\nfunction assertSelectionsMatch(\n activeSelection: TestConnectorsSelection,\n requestedSelection: TestConnectorsSelection,\n): void {\n if (isSameConnectorsSelection(activeSelection, requestedSelection)) {\n return;\n }\n\n throw new TestLifecycleError(\n `setupTest() is already using ${describeConnectorsSelection(activeSelection)}, and this call asked for ${describeConnectorsSelection(requestedSelection)}. One test runtime serves one connector selection — call teardownTest() before setting up a different one.`,\n );\n}\n\n/**\n * Await an attempt for its state transition only, not its result.\n */\nfunction settled(attempt: Promise<void>): Promise<void> {\n return attempt.catch(() => undefined);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vitest-setup.mjs","names":[],"sources":["../../../../../../../../packages/core/src/tests/vitest-setup.ts"],"sourcesContent":["/**\n * Worker test lifecycle — `setupTest` / `teardownTest`.\n *\n * Implements `contracts/2026-08-12-test-worker-lifecycle.md`. The pair is\n * context-scoped, not \"once per worker\": Vitest runs `setupFiles` before EVERY\n * test file and rebuilds the module registry each time, so the harness that\n * calls `setupTest` owns pairing it with `teardownTest` in the same runtime\n * context.\n *\n * Ownership, stated once because teardown is wider than it looks:\n *\n * - the runtime assumes exclusive ownership of the process-local\n * `connectorsManager` for its lifetime;\n * - it owns the framework bootstrap, the application shutdown hooks, and the\n * connectors it starts — including the `connectors: false` case, where\n * bootstrap still registers hooks;\n * - teardown is MANAGER-WIDE. `connectorsManager.shutdown()` has no selective\n * ownership handle, so a lifecycle that started two connectors still closes\n * every connector the manager holds. Mixing `setupTest` with manual connector\n * startup in the same process is unsupported for exactly that reason.\n * - HTTP global setup stays separately owned by `startHttpTestServer` /\n * `stopHttpTestServer`.\n */\nimport { Application } from \"../application/application\";\nimport { bootstrap } from \"../bootstrap\";\nimport { setAppModuleImporter } from \"../loader/app-module-importer\";\nimport type { AppModuleImporter } from \"../loader/app-module-importer\";\nimport { loadConfigFiles } from \"../config/load-config-files\";\nimport { connectorsManager } from \"../connectors\";\nimport { filesOrchestrator } from \"../dev-server/files-orchestrator\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { installTestFakes, restoreTestFakes } from \"./fakes/test-fakes\";\nimport type { TestFakesSelection } from \"./fakes/test-fakes\";\nimport {\n describeConnectorsSelection,\n isSameConnectorsSelection,\n normalizeConnectorNames,\n readRequestedConnectors,\n resolveEffectiveConnectors,\n} from \"./test-connectors-selection\";\nimport type { RequestedTestConnectors, TestConnectorsSelection } from \"./test-connectors-selection\";\nimport { getTestLifecycleRegistry } from \"./test-lifecycle-state\";\nimport type { TestLifecycleRegistry, TestSetupAttempt } from \"./test-lifecycle-state\";\nimport {\n DEFAULT_TEST_SETUP_TIMEOUT,\n describeExpiredSetup,\n readConfiguredSetupTimeout,\n scheduleRealTimeout,\n} from \"./test-setup-timeout\";\nimport type { TestTimeoutHandle } from \"./test-setup-timeout\";\n\nexport type { TestConnectorsSelection } from \"./test-connectors-selection\";\n\nexport type TestSetupOptions = {\n /**\n * Which connectors to start.\n *\n * Precedence is `explicit non-undefined option > tests.connectors config >\n * true`. Omitting the property, or passing `undefined`, lets project config\n * apply — so an optional variable that happens to be `undefined` cannot erase\n * it.\n */\n connectors?: TestConnectorsSelection;\n /**\n * Import hook for APPLICATION modules (config, routes, events, ...).\n *\n * Pass `(file) => import(file)` from your `src/test-setup.ts`: that file is\n * app code, so Vitest handles the `import()` and app modules land in the same\n * module graph as your test files. Without it Warlock imports them natively,\n * one worker holds two copies of every class (`User`, ...), and `instanceof`\n * fails. Defaults to native import.\n */\n importModule?: AppModuleImporter;\n /**\n * Which test fakes to install once the connectors are up: mail, queue,\n * cache, storage, clock and ids (see `installTestFakes`).\n *\n * Precedence is `explicit option > tests.fakes config > true`, so every fake\n * is on unless the project or this call opts out. `false` keeps the real\n * drivers; `{ cache: false }` keeps only the real cache. Teardown restores\n * them.\n */\n fakes?: TestFakesSelection;\n};\n\nconst IMPORT_MODULE_WARNED_KEY = Symbol.for(\"warlock.core.importModuleWarned\");\n\n/**\n * Warn — once per process — that app modules are loaded outside Vitest's graph.\n */\nexport function warnMissingImportModule(importModule: AppModuleImporter | undefined): void {\n if (importModule || !process.env.VITEST) return;\n\n const holder = globalThis as Record<symbol, unknown>;\n\n if (holder[IMPORT_MODULE_WARNED_KEY]) return;\n\n holder[IMPORT_MODULE_WARNED_KEY] = true;\n\n console.warn(\n \"[warlock] Warlock loads app modules outside Vitest's module graph, so classes like User exist twice and instanceof fails. Pass importModule: (file) => import(file) to setupTest().\",\n );\n}\n\n/**\n * Raised by the lifecycle itself, never by the runtime it manages — a conflict\n * between two calls, or a refusal to reuse a runtime that did not close.\n */\nexport class TestLifecycleError extends Error {\n public constructor(message: string) {\n super(message);\n\n this.name = \"TestLifecycleError\";\n }\n}\n\nconst POISONED_MESSAGE =\n \"setupTest() refuses to start a test runtime: an earlier teardownTest() reported a shutdown failure, so the connectors, ports and pools it owned are not known to be closed. Restart the Vitest worker before setting up again, or call teardownTest() to retry the shutdown — the lifecycle only returns to idle on a fully successful retry.\";\n\n/**\n * Bootstrap the test runtime and start the selected connectors.\n *\n * ⚠ Runs once per TEST FILE, not once per worker: `setupFiles` is executed for\n * every file and its module registry is rebuilt with it, measured across both\n * pools with isolation on and off. The lifecycle state lives on the runtime\n * context rather than in this module precisely so that a rebuild cannot hide a\n * runtime that is still up.\n *\n * Repeated calls:\n *\n * - concurrent calls with the same effective selection share one startup;\n * - while ready, the same effective selection is a no-op;\n * - while starting or ready, a DIFFERENT effective selection rejects and leaves\n * the live runtime untouched;\n * - a failed setup unwinds and returns to idle, so a clean retry is allowed;\n * - after a successful `teardownTest`, a later setup may use a different\n * selection.\n *\n * @example\n * // let project config decide, falling back to the default set\n * await setupTest();\n *\n * @example\n * // bootstrap only — no connectors, whatever config says\n * await setupTest({ connectors: false });\n */\nexport async function setupTest(options?: TestSetupOptions): Promise<void> {\n warnMissingImportModule(options?.importModule);\n\n const registry = getTestLifecycleRegistry();\n const requested = readRequestedConnectors(options?.connectors);\n\n if (registry.state === \"poisoned\") {\n throw new TestLifecycleError(POISONED_MESSAGE);\n }\n\n if (registry.state === \"stopping\" && registry.teardownAttempt) {\n await settled(registry.teardownAttempt);\n\n return setupTest(options);\n }\n\n if (registry.state === \"ready\") {\n assertReadySelectionMatches(registry.activeSelection, requested);\n\n return;\n }\n\n if (registry.state === \"starting\" && registry.setupAttempt) {\n const attempt = registry.setupAttempt;\n\n await assertPendingSetupMatches(attempt, requested);\n\n return attempt.completion;\n }\n\n // Scoped to this startup: the loaders read it while app modules import, and\n // it is cleared whatever the outcome so it never leaks past setup.\n setAppModuleImporter(options?.importModule);\n\n try {\n return await startTestRuntime(requested, options?.fakes);\n } finally {\n setAppModuleImporter(undefined);\n }\n}\n\n/**\n * Shut the test runtime down and release the lifecycle.\n *\n * - idle is a no-op;\n * - concurrent calls share one attempt;\n * - a call made while setup is still running waits for that attempt to settle,\n * then closes the runtime if it succeeded;\n * - the local \"ready\" state is always cleared, including when shutdown rejects;\n * - a shutdown rejection is surfaced, never swallowed, and poisons the\n * lifecycle — a reported close failure is not proof that anything closed, so\n * later `setupTest` calls refuse until the worker is restarted or a retry\n * fully succeeds.\n *\n * ⚠ Individual connector shutdown failures are caught and logged inside\n * `connectorsManager.shutdown()`, so they never reach this function and never\n * poison anything. This lifecycle can only surface what that layer reports.\n */\nexport async function teardownTest(): Promise<void> {\n const registry = getTestLifecycleRegistry();\n\n if (registry.state === \"stopping\" && registry.teardownAttempt) {\n return registry.teardownAttempt;\n }\n\n if (registry.state === \"starting\" && registry.setupAttempt) {\n // A runtime that is still opening cannot be closed halfway. Wait for the\n // attempt to settle: a failed one has already unwound itself and left the\n // lifecycle idle, which the re-entry below then reads as \"nothing to do\".\n await settled(registry.setupAttempt.completion);\n\n return teardownTest();\n }\n\n if (registry.state === \"idle\") {\n return;\n }\n\n registry.state = \"stopping\";\n\n // Deferred by a microtask so the registry is fully published before the\n // attempt's first line runs — a synchronous throw would otherwise reach the\n // `finally` below, and reset the state, before this function had recorded\n // that a teardown was in flight at all.\n const attempt = Promise.resolve().then(() => runTestRuntimeShutdown());\n\n registry.teardownAttempt = attempt;\n\n return attempt;\n}\n\n/**\n * The setup attempt's hang guard, as the lifecycle consumes it.\n *\n * The bound's POLICY — the default, the config key, the message — lives in\n * `test-setup-timeout.ts`. What expiry MEANS lives here.\n *\n * @internal\n */\ntype SetupBound = {\n /**\n * Rejects with the expiry error. Never resolves: the attempt is the only side\n * of the race that can succeed.\n */\n readonly expiry: Promise<never>;\n\n /**\n * Re-aim the bound at `next` ms measured from when the ATTEMPT STARTED, once\n * config has made `tests.setupTimeout` readable.\n */\n rearm(next: number): void;\n\n /** The race is over — cancel whatever timer is still pending. */\n settle(): void;\n\n /** Whether the bound, rather than the attempt, won the race. */\n hasExpired(): boolean;\n};\n\n/**\n * Arm the hang guard for one setup attempt.\n *\n * Armed at the default BEFORE the attempt runs, because `tests.setupTimeout` is\n * not readable until the attempt has loaded config — and loading config is\n * itself one of the steps that can hang. `rearm` then narrows or widens it the\n * moment config arrives.\n */\nfunction armSetupBound(registry: TestLifecycleRegistry): SetupBound {\n const schedule = registry.scheduleTimeout ?? scheduleRealTimeout;\n const armedAt = Date.now();\n\n let handle: TestTimeoutHandle | undefined;\n let expired = false;\n let closed = false;\n let expire: (error: unknown) => void = () => undefined;\n\n const expiry = new Promise<never>((_resolve, reject) => {\n expire = reject;\n });\n\n // Only `awaitSetupWithinBound` observes this, and only until the attempt wins\n // — after that nobody is listening, and an unobserved rejection surfaces as an\n // unhandled rejection that fails an unrelated test file. Same reason as the\n // `effectiveSelection` barrier below.\n expiry.catch(() => undefined);\n\n const expireNow = (bound: number): void => {\n if (closed) {\n return;\n }\n\n closed = true;\n expired = true;\n\n handle?.cancel();\n handle = undefined;\n\n expire(new TestLifecycleError(describeExpiredSetup(bound)));\n };\n\n const armFor = (bound: number, delay: number): void => {\n if (closed) {\n return;\n }\n\n handle?.cancel();\n\n // Already overdue. A bound the attempt has ALREADY exceeded must fire now,\n // not be rescheduled for time that was spent before it was known.\n if (delay <= 0) {\n expireNow(bound);\n\n return;\n }\n\n handle = schedule(delay, () => expireNow(bound));\n };\n\n const initialBound = registry.setupTimeoutOverride ?? DEFAULT_TEST_SETUP_TIMEOUT;\n\n armFor(initialBound, initialBound);\n\n return {\n expiry,\n\n // Measured from `armedAt`, never from now: adding a configured bound to the\n // time already spent would make the real bound default-plus-configured, and\n // a project that lowered the key would get a LONGER guard than the default.\n rearm: (next) => armFor(next, next - (Date.now() - armedAt)),\n\n settle: () => {\n closed = true;\n\n handle?.cancel();\n handle = undefined;\n },\n\n hasExpired: () => expired,\n };\n}\n\n/**\n * Race one setup attempt against its bound.\n *\n * The losing attempt is NOT cancellable — nothing here can interrupt a bootstrap\n * stuck in a socket connect — so when the bound wins, the lifecycle is poisoned\n * and every later state write from that abandoned attempt is dropped. That\n * dropping happens in `runTestRuntimeStartup`, guarded on `hasExpired()`;\n * without it the still-running attempt would quietly overwrite `poisoned` with\n * `ready` or `idle` and hand the next caller a runtime nobody owns.\n */\nasync function awaitSetupWithinBound(\n attempt: Promise<void>,\n bound: SetupBound,\n registry: TestLifecycleRegistry,\n): Promise<void> {\n try {\n await Promise.race([attempt, bound.expiry]);\n } catch (error) {\n if (bound.hasExpired()) {\n registry.state = \"poisoned\";\n registry.activeSelection = undefined;\n registry.setupAttempt = undefined;\n }\n\n throw error;\n } finally {\n bound.settle();\n }\n}\n\n/**\n * Open a new setup attempt and publish it before it runs.\n */\nfunction startTestRuntime(\n requested: RequestedTestConnectors,\n fakes: TestFakesSelection | undefined,\n): Promise<void> {\n const registry = getTestLifecycleRegistry();\n\n let publishEffectiveSelection: (selection: TestConnectorsSelection) => void = () => undefined;\n let failEffectiveSelection: (error: unknown) => void = () => undefined;\n\n const effectiveSelection = new Promise<TestConnectorsSelection>((resolve, reject) => {\n publishEffectiveSelection = resolve;\n failEffectiveSelection = reject;\n });\n\n // Only the mixed explicit/config comparison ever awaits this barrier, so its\n // rejection is usually unobserved — and an unobserved rejection would surface\n // as an unhandled rejection and fail an unrelated test file.\n effectiveSelection.catch(() => undefined);\n\n const bound = armSetupBound(registry);\n\n const attempt = Promise.resolve().then(() =>\n runTestRuntimeStartup(\n requested,\n publishEffectiveSelection,\n failEffectiveSelection,\n bound,\n fakes,\n ),\n );\n\n // The attempt outlives a bound that beat it, and by then `completion` has\n // already rejected with the expiry error — so the attempt's own late rejection\n // has no observer. Same unhandled-rejection hazard as above.\n attempt.catch(() => undefined);\n\n // `teardownTest` waits on this same promise, which is how a teardown requested\n // during setup inherits the bound. There is deliberately no second timer and\n // no separate teardown deadline.\n const completion = awaitSetupWithinBound(attempt, bound, registry);\n\n registry.state = \"starting\";\n registry.setupAttempt = { requested, effectiveSelection, completion };\n\n return completion;\n}\n\n/**\n * The startup attempt itself.\n *\n * On failure: best-effort unwind, reset bookkeeping, rethrow the ORIGINAL error\n * unchanged. A cleanup failure is a secondary diagnostic and never replaces the\n * cause the caller needs to read.\n */\nasync function runTestRuntimeStartup(\n requested: RequestedTestConnectors,\n publishEffectiveSelection: (selection: TestConnectorsSelection) => void,\n failEffectiveSelection: (error: unknown) => void,\n bound: SetupBound,\n fakes: TestFakesSelection | undefined,\n): Promise<void> {\n const registry = getTestLifecycleRegistry();\n\n try {\n Application.setEnvironment(\"test\");\n\n await warlockConfigManager.load();\n await bootstrap();\n\n await filesOrchestrator.init();\n await loadConfigFiles(true);\n\n // The first point at which `tests.setupTimeout` is readable at all. An\n // invalid value throws from here and takes the normal failure path — falling\n // back to the default would silently erase what the project configured.\n const configuredTimeout = readConfiguredSetupTimeout();\n\n if (configuredTimeout !== undefined) {\n bound.rearm(configuredTimeout);\n }\n\n // Resolved here and not at call time: the config layer is only readable once\n // the four steps above have run.\n const effectiveSelection = resolveEffectiveConnectors(requested);\n\n // Every state write below is conditional on this attempt still being the one\n // the lifecycle is waiting for. Once the bound has expired the lifecycle is\n // poisoned and this attempt is abandoned — it may still be running, but it\n // no longer speaks for the runtime.\n if (!bound.hasExpired()) {\n registry.activeSelection = effectiveSelection;\n }\n\n publishEffectiveSelection(effectiveSelection);\n\n await startSelectedConnectors(effectiveSelection);\n\n // After the connectors, so the cache and storage fakes replace drivers\n // that are already configured instead of being replaced by them.\n await installTestFakes(fakes);\n\n if (!bound.hasExpired()) {\n registry.state = \"ready\";\n registry.setupAttempt = undefined;\n }\n } catch (error) {\n failEffectiveSelection(error);\n\n await unwindPartialStartup();\n\n if (!bound.hasExpired()) {\n registry.state = \"idle\";\n registry.activeSelection = undefined;\n registry.setupAttempt = undefined;\n }\n\n console.error(\"[vitest-setup] Failed to setup test environment:\", error);\n\n throw error;\n }\n}\n\n/**\n * `false` means none at all. Everything else starts something: an array starts\n * exactly those, and `true` starts the default set — all but http, which is the\n * global setup's job and shared across every worker.\n */\nasync function startSelectedConnectors(selection: TestConnectorsSelection): Promise<void> {\n if (selection === false) {\n return;\n }\n\n if (Array.isArray(selection)) {\n await connectorsManager.start(normalizeConnectorNames(selection));\n\n return;\n }\n\n await connectorsManager.startWithout([\"http\"]);\n}\n\n/**\n * Best-effort teardown of whatever a failed startup managed to start.\n *\n * Runs while an error is already in flight, so no step here may throw and every\n * step is attempted independently.\n */\nasync function unwindPartialStartup(): Promise<void> {\n // Two steps rather than one: `connectorsManager.shutdown()` runs the\n // application hooks itself, but it runs them FIRST and does not isolate their\n // rejection — so a hook that rejects would take the connector teardown down\n // with it and leave every connector up. Both calls are idempotent, so running\n // the hooks on their own first costs nothing and means neither failure can\n // skip the other step.\n await attemptCleanupStep(\"test fakes\", () => restoreTestFakes());\n await attemptCleanupStep(\"application shutdown hooks\", () => Application.runShutdownHooks());\n await attemptCleanupStep(\"connectors shutdown\", () => connectorsManager.shutdown());\n}\n\n/**\n * Run one cleanup step, isolating its failure so the next step still runs.\n */\nasync function attemptCleanupStep(step: string, run: () => Promise<void>): Promise<void> {\n try {\n await run();\n } catch (error) {\n // Reported, not thrown — it is secondary to the startup error about to be\n // rethrown, and losing that one to this would be the worse outcome.\n console.error(`[vitest-setup] Cleanup after a failed setup did not complete (${step}):`, error);\n }\n}\n\n/**\n * The teardown attempt itself.\n */\nasync function runTestRuntimeShutdown(): Promise<void> {\n const registry = getTestLifecycleRegistry();\n let shutdownSucceeded = false;\n\n try {\n // Manager-wide, and deliberately called whatever the selection was: with\n // `connectors: false` nothing was started, but bootstrap still registered\n // application shutdown hooks, and the manager runs those first (see\n // `connectors-manager.ts` — `Application.runShutdownHooks()` is its first\n // line). Skipping the call to \"match\" an empty selection would leak them.\n //\n // Fakes go first, while the real drivers they put back are still\n // connected. A fake that fails to restore is reported, never allowed to\n // keep the connectors open.\n try {\n await restoreTestFakes();\n } catch (error) {\n console.error(\"[vitest-setup] Restoring the test fakes did not complete:\", error);\n }\n\n await connectorsManager.shutdown();\n\n shutdownSucceeded = true;\n } finally {\n // In a `finally`, not after the `await`: a rejected shutdown must not leave\n // the lifecycle claiming a runtime it no longer owns. Poisoned rather than\n // idle, because a cleared flag does not prove that ports, sockets, pools or\n // timers closed.\n registry.activeSelection = undefined;\n registry.setupAttempt = undefined;\n registry.teardownAttempt = undefined;\n registry.state = shutdownSucceeded ? \"idle\" : \"poisoned\";\n }\n}\n\n/**\n * Reject when a ready runtime was set up with a different selection.\n */\nfunction assertReadySelectionMatches(\n activeSelection: TestConnectorsSelection | undefined,\n requested: RequestedTestConnectors,\n): void {\n if (activeSelection === undefined) {\n return;\n }\n\n // Config is loaded by the time anything is ready, so the config-derived layer\n // is resolvable right here.\n const requestedSelection = resolveEffectiveConnectors(requested);\n\n assertSelectionsMatch(activeSelection, requestedSelection);\n}\n\n/**\n * Reject when an in-flight setup attempt is starting a different selection.\n */\nasync function assertPendingSetupMatches(\n attempt: TestSetupAttempt,\n requested: RequestedTestConnectors,\n): Promise<void> {\n const pending = attempt.requested;\n\n // Two calls that both defer to project config resolve to the same thing by\n // construction — knowable without waiting for config to load.\n if (!pending.isExplicit && !requested.isExplicit) {\n return;\n }\n\n if (pending.isExplicit && requested.isExplicit) {\n assertSelectionsMatch(pending.selection, requested.selection);\n\n return;\n }\n\n // Mixed: one side is config-derived and config is not readable until the\n // running attempt has loaded it, so wait for that barrier first.\n const activeSelection = await attempt.effectiveSelection;\n const requestedSelection = resolveEffectiveConnectors(requested);\n\n assertSelectionsMatch(activeSelection, requestedSelection);\n}\n\n/**\n * Name both selections: a caller looking at this message has to be able to tell\n * which call to change without reading the framework's source.\n */\nfunction assertSelectionsMatch(\n activeSelection: TestConnectorsSelection,\n requestedSelection: TestConnectorsSelection,\n): void {\n if (isSameConnectorsSelection(activeSelection, requestedSelection)) {\n return;\n }\n\n throw new TestLifecycleError(\n `setupTest() is already using ${describeConnectorsSelection(activeSelection)}, and this call asked for ${describeConnectorsSelection(requestedSelection)}. One test runtime serves one connector selection — call teardownTest() before setting up a different one.`,\n );\n}\n\n/**\n * Await an attempt for its state transition only, not its result.\n */\nfunction settled(attempt: Promise<void>): Promise<void> {\n return attempt.catch(() => undefined);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region ../../packages/core/src/tooling/agents-md/agents-md-markers.d.ts
|
|
2
|
+
/** The file `warlock agents-md --write` and `warlock dev` maintain, at the project root. */
|
|
3
|
+
declare const AGENTS_MD_FILE = "AGENTS.md";
|
|
4
|
+
/** Opens the generated section. Everything outside the two markers is hand-written. */
|
|
5
|
+
declare const AGENTS_MD_START = "<!-- warlock:generated:start -->";
|
|
6
|
+
/** Closes the generated section. */
|
|
7
|
+
declare const AGENTS_MD_END = "<!-- warlock:generated:end -->";
|
|
8
|
+
/**
|
|
9
|
+
* Put `section` between the generated-section markers of `existing`.
|
|
10
|
+
*
|
|
11
|
+
* - `existing` undefined (no file): the markers and the section, alone.
|
|
12
|
+
* - no markers: the existing text unchanged, then a blank line, then the
|
|
13
|
+
* markers and the section at the end.
|
|
14
|
+
* - one start marker followed by one end marker: only the bytes between them
|
|
15
|
+
* are replaced; the markers and everything outside them stay byte-for-byte.
|
|
16
|
+
*
|
|
17
|
+
* Throws `WL7501` when the markers are malformed (one without the other, a
|
|
18
|
+
* duplicate, or the end before the start) — rather than guess which part is
|
|
19
|
+
* hand-written.
|
|
20
|
+
*
|
|
21
|
+
* @param existing The current file content, or `undefined` when the file does not exist.
|
|
22
|
+
* @param section The generated markdown, without markers.
|
|
23
|
+
*/
|
|
24
|
+
declare function upsertGeneratedSection(existing: string | undefined, section: string): string;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { AGENTS_MD_END, AGENTS_MD_FILE, AGENTS_MD_START, upsertGeneratedSection };
|
|
27
|
+
//# sourceMappingURL=agents-md-markers.d.mts.map
|