@warlock.js/core 5.26.0 → 5.27.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.
Files changed (98) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/esm/cli/commands/openapi-generator.action.mjs +43 -0
  3. package/esm/cli/commands/openapi-generator.action.mjs.map +1 -0
  4. package/esm/cli/commands/openapi-generator.command.mjs +44 -0
  5. package/esm/cli/commands/openapi-generator.command.mjs.map +1 -0
  6. package/esm/cli/framework-cli-commands.mjs +2 -0
  7. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  8. package/esm/dev-server/build-development-openapi-document.mjs +36 -0
  9. package/esm/dev-server/build-development-openapi-document.mjs.map +1 -0
  10. package/esm/dev-server/collect-development-api-routes.mjs +27 -0
  11. package/esm/dev-server/collect-development-api-routes.mjs.map +1 -0
  12. package/esm/dev-server/dev-logger.mjs +9 -1
  13. package/esm/dev-server/dev-logger.mjs.map +1 -1
  14. package/esm/dev-server/development-server.mjs +6 -3
  15. package/esm/dev-server/development-server.mjs.map +1 -1
  16. package/esm/dev-server/files-orchestrator.mjs +17 -1
  17. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  18. package/esm/dev-server/model-resource-typings.mjs +246 -0
  19. package/esm/dev-server/model-resource-typings.mjs.map +1 -0
  20. package/esm/dev-server/type-generator.mjs +46 -2
  21. package/esm/dev-server/type-generator.mjs.map +1 -1
  22. package/esm/http/index.d.mts +2 -1
  23. package/esm/http/request.d.mts +2 -2
  24. package/esm/http/request.d.mts.map +1 -1
  25. package/esm/http/serialized.d.mts +114 -0
  26. package/esm/http/serialized.d.mts.map +1 -0
  27. package/esm/index.d.mts +8 -3
  28. package/esm/index.mjs +4 -1
  29. package/esm/openapi/build-openapi-document.d.mts +17 -0
  30. package/esm/openapi/build-openapi-document.d.mts.map +1 -0
  31. package/esm/openapi/build-openapi-document.mjs +124 -0
  32. package/esm/openapi/build-openapi-document.mjs.map +1 -0
  33. package/esm/openapi/cast-schema.mjs +89 -0
  34. package/esm/openapi/cast-schema.mjs.map +1 -0
  35. package/esm/openapi/collect-openapi-document.mjs +40 -0
  36. package/esm/openapi/collect-openapi-document.mjs.map +1 -0
  37. package/esm/openapi/development-openapi-provider.d.mts +17 -0
  38. package/esm/openapi/development-openapi-provider.d.mts.map +1 -0
  39. package/esm/openapi/development-openapi-provider.mjs +21 -0
  40. package/esm/openapi/development-openapi-provider.mjs.map +1 -0
  41. package/esm/openapi/error-responses.mjs +67 -0
  42. package/esm/openapi/error-responses.mjs.map +1 -0
  43. package/esm/openapi/index.d.mts +3 -0
  44. package/esm/openapi/index.mjs +4 -0
  45. package/esm/openapi/openapi-child-output.mjs +57 -0
  46. package/esm/openapi/openapi-child-output.mjs.map +1 -0
  47. package/esm/openapi/openapi-protocol.mjs +30 -0
  48. package/esm/openapi/openapi-protocol.mjs.map +1 -0
  49. package/esm/openapi/paths.mjs +63 -0
  50. package/esm/openapi/paths.mjs.map +1 -0
  51. package/esm/openapi/request-schemas.mjs +138 -0
  52. package/esm/openapi/request-schemas.mjs.map +1 -0
  53. package/esm/openapi/response-schemas.mjs +85 -0
  54. package/esm/openapi/response-schemas.mjs.map +1 -0
  55. package/esm/openapi/schema-registry.mjs +143 -0
  56. package/esm/openapi/schema-registry.mjs.map +1 -0
  57. package/esm/openapi/security.mjs +98 -0
  58. package/esm/openapi/security.mjs.map +1 -0
  59. package/esm/openapi/types.d.mts +119 -0
  60. package/esm/openapi/types.d.mts.map +1 -0
  61. package/esm/production/registration-child-runner.mjs +139 -0
  62. package/esm/production/registration-child-runner.mjs.map +1 -0
  63. package/esm/production/route-registration-child.mjs +30 -2
  64. package/esm/production/route-registration-child.mjs.map +1 -1
  65. package/esm/production/route-registration-snapshot.mjs +22 -119
  66. package/esm/production/route-registration-snapshot.mjs.map +1 -1
  67. package/esm/resource/define-resource.d.mts +44 -14
  68. package/esm/resource/define-resource.d.mts.map +1 -1
  69. package/esm/resource/define-resource.mjs +0 -40
  70. package/esm/resource/define-resource.mjs.map +1 -1
  71. package/esm/resource/index.d.mts +1 -1
  72. package/esm/resource/resource.d.mts +4 -4
  73. package/esm/resource/resource.d.mts.map +1 -1
  74. package/esm/resource/resource.mjs.map +1 -1
  75. package/esm/resource/types.d.mts +89 -4
  76. package/esm/resource/types.d.mts.map +1 -1
  77. package/esm/restful/restful.d.mts +5 -5
  78. package/esm/restful/restful.d.mts.map +1 -1
  79. package/esm/router/index.d.mts +1 -1
  80. package/esm/router/named-api-routes-with-responses.mjs +59 -0
  81. package/esm/router/named-api-routes-with-responses.mjs.map +1 -0
  82. package/esm/router/response-schema-types.mjs +100 -0
  83. package/esm/router/response-schema-types.mjs.map +1 -0
  84. package/esm/router/router.d.mts +2 -2
  85. package/esm/router/router.d.mts.map +1 -1
  86. package/esm/router/router.mjs +38 -11
  87. package/esm/router/router.mjs.map +1 -1
  88. package/esm/router/types.d.mts +16 -1
  89. package/esm/router/types.d.mts.map +1 -1
  90. package/llms-full.txt +265 -5
  91. package/llms.txt +3 -2
  92. package/package.json +13 -13
  93. package/skills/README.md +3 -2
  94. package/skills/create-controller/SKILL.md +39 -2
  95. package/skills/define-resource/SKILL.md +100 -1
  96. package/skills/generate-openapi/SKILL.md +120 -0
  97. package/skills/index.md +3 -3
  98. package/skills/validate-input/SKILL.md +2 -2
package/CHANGELOG.md CHANGED
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  > ⚠ **Versioning: `@warlock.js/*` does not follow SemVer strictly — breaking changes may ship in a minor.** This is a deliberate decision, not an oversight: the framework is pre-adoption and the cost of a major per behaviour fix currently outweighs the benefit. **Pin an exact version or a tilde range (`~4.13.0`) if you need to opt into changes rather than receive them.** Every breaking change is marked **BREAKING** in its entry and summarised in an _Upgrading_ section at the top of the release. **This policy will change once the framework has consumers beyond its author.**
8
8
 
9
+ ## 5.27.0 - 2026-10-01
10
+
11
+ ### Added
12
+
13
+ - **Typed resource output.** `defineResource()` infers the `toJSON()` output type from the schema's cast strings (`"string?"` is `string | null`, `"date"` is the default `{ iso; format; timestamp; humanTime }` object, nested and `"self"` resources resolve to their own output). Read it with `ResourceOutput<typeof UserResource>`. When `transform`/`boot`/builders change the shape, state it: `defineResource<ProductJson>({ ... })`. Builder fields are `unknown`.
14
+ - **`Serialized<T, W>`**: the type of what the client receives after Warlock serializes `T`, for API responses (`"json"`, default) and page loader data (`"devalue"`). Dates become strings on JSON, `toJSON()` results are used, functions are dropped, and models map to their resource through the augmentable `ModelResourceRegistry`.
15
+ - **Typed API responses from `handler.responseSchema`.** A response schema body now accepts cast strings with the resource suffixes (`"number?"`, `"string[]"`, `"date[]?"`), a resource, `[resource]` for an array, and nested plain objects. `ResponseBodyOutput<B>` and `ResponseSchemaOutput<S>` give the body type per status code with the same rules as `ResourceOutput`. `warlock generate.typings` and `warlock dev` read the declared schema at runtime and hand each route's response types to the web route-types generator, so nothing is annotated by hand.
16
+ - `NamedApiRoute` has an optional `response` map (status code to a TypeScript type expression), present only on routes that declare `responseSchema`; `router.getNamedApiRoutes()` accepts a `resolveResponse` option to attach it and still returns the plain `{ name, path, method }` records otherwise. Resources are mapped back to their `*.resource.ts(x)` export by identity; a resource defined inline or outside such a file is typed `unknown` with a warning naming the route and field. The typings child and the dev server share one serializer, so both emit identical strings.
17
+ - `responseSchema` is now kept when a route is bound from an array handler (`[controller, "action"]`) or a restful resource method; the bound function used to lose it.
18
+ - **`warlock generate.openapi`** writes an OpenAPI 3.1 document (`storage/openapi/openapi.json`, or `--out`) derived from the registered routes. Options: `--title`, `--server`, `--include-pages`. Paths use `{param}` templates and `all` routes expand to every verb; `validation.schema` becomes the request body (POST/PUT/PATCH) or query parameters (GET/HEAD/DELETE) according to `validating`, and `validation.params` becomes typed path parameters. Responses come from `handler.responseSchema` (casts, `"?"` as `type: [T, "null"]`, resources as `components.schemas` `$ref`s named after their export, `[Resource]` as an array); a route with no schema documents only a `200` description. A `422` (shaped by `validation.response`) is added when a route has `validation.schema`, and a `401` when it is guarded. Fields that cannot be described statically (field builders, resolver functions) are documented as `{}` and listed as warnings; the command exits non-zero only when the app cannot be loaded.
19
+ - **Security schemes from `authMiddleware`.** Core reads the `Symbol.for("warlock.auth")` descriptor (`{ sources: ("header" | { cookie: string })[], userTypes: string[] }`) that `authMiddleware()` attaches and emits `bearerAuth` / `cookieAuth` schemes only for the ones a route uses (header plus cookie is an OR). Middleware without a descriptor is ordinary middleware and produces no security entry.
20
+ - **`buildOpenApiDocument(routes, context)`** is exported: a pure function from `router.list()` routes to a plain-JSON OpenAPI document plus a `warnings` list, so a dev tool can build the same document in-process. The CLI builds it inside the route-registration child process, so Seal instances never cross IPC; that child's fork, IPC and timeout handling is now a shared helper with no change to `warlock build` (`WARLOCK_ROUTE_REGISTRATION_TIMEOUT_MS` applies to both).
21
+ - **`getDevelopmentOpenApiDocument()`** returns the same document for the routes registered in the running dev process (`warlock dev` registers the builder at startup; outside it the call throws), reusing the CLI child's builder (resource export map, `package.json` info, `http` server, `validation.response`). `@warlock.js/devtools` calls it to serve its API docs page at `/__warlock/docs`; warnings are logged once per process.
22
+ - **Models resolve to their resource in `Serialized`.** `warlock generate.typings` and `warlock dev` now write `.warlock/typings/model-resources.d.ts`, a `ModelResourceRegistry` augmentation with one entry per model class that declares `static resource = <Identifier>`, so `Serialized<User>` is the resource output instead of the raw model data. The identifier is followed through the model file's own imports (named, aliased, default, tsconfig aliases) or its own declarations, by reading the source only; a computed expression, `lazy(...)`, a generic or unexported model is left out and keeps the raw-data type. Duplicate class names get `_2`, `_3` keys, output is sorted, and the file is rewritten only when it changes. The app's `tsconfig.json` must include `.warlock/typings/*.d.ts` (the scaffold already does).
23
+
24
+ ### Changed
25
+
26
+ - Dependency: @mongez/supportive-is 3.0 (type-predicate checkers).
27
+
9
28
  ## 5.26.0 - 2026-09-30
10
29
 
11
30
  ### Upgrading
@@ -0,0 +1,43 @@
1
+ import { rootPath } from "../../utils/paths.mjs";
2
+ import { resolveBuildConfig } from "../../production/resolve-build-config.mjs";
3
+ import { collectOpenApiDocument } from "../../openapi/collect-openapi-document.mjs";
4
+ import path from "node:path";
5
+ import { colors } from "@mongez/copper";
6
+ import { mkdir, writeFile } from "node:fs/promises";
7
+
8
+ //#region ../core/src/cli/commands/openapi-generator.action.ts
9
+ /** Where the document goes unless `--out` says otherwise. */
10
+ const DEFAULT_OPENAPI_OUTPUT = "storage/openapi/openapi.json";
11
+ function stringOption(value) {
12
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
13
+ }
14
+ /**
15
+ * Action behind `warlock generate.openapi`.
16
+ *
17
+ * Builds the OpenAPI 3.1 document in a fresh registration child (no connectors are booted),
18
+ * writes it as pretty JSON to `--out` (default `storage/openapi/openapi.json`) and prints the
19
+ * per-route gaps it found as warnings. A child failure is fatal and rejects; gaps never are.
20
+ */
21
+ async function generateOpenApiAction({ options }) {
22
+ const cwd = rootPath();
23
+ const output = path.resolve(cwd, stringOption(options.out) ?? "storage/openapi/openapi.json");
24
+ const { document, warnings } = await collectOpenApiDocument({
25
+ cwd,
26
+ timeoutMs: resolveBuildConfig().routeRegistrationTimeoutMs,
27
+ title: stringOption(options.title),
28
+ server: stringOption(options.server),
29
+ includePages: options.includePages === true
30
+ });
31
+ await mkdir(path.dirname(output), { recursive: true });
32
+ await writeFile(output, `${JSON.stringify(document, null, 2)}\n`, "utf8");
33
+ const operations = Object.values(document.paths).reduce((total, item) => total + Object.keys(item).length, 0);
34
+ console.log(colors.greenBright(`OpenAPI ${document.openapi} written to ${output} (${Object.keys(document.paths).length} paths, ${operations} operations).`));
35
+ if (warnings.length > 0) {
36
+ console.warn(colors.yellowBright(`\n${warnings.length} warning${warnings.length === 1 ? "" : "s"}:`));
37
+ for (const warning of warnings) console.warn(` - ${warning}`);
38
+ }
39
+ }
40
+
41
+ //#endregion
42
+ export { generateOpenApiAction };
43
+ //# sourceMappingURL=openapi-generator.action.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-generator.action.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/openapi-generator.action.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { colors } from \"@mongez/copper\";\nimport type { CommandActionData } from \"../../commands/types\";\nimport { collectOpenApiDocument } from \"../../openapi/collect-openapi-document\";\nimport { resolveBuildConfig } from \"../../production/resolve-build-config\";\nimport { rootPath } from \"../../utils/paths\";\n\n/** Where the document goes unless `--out` says otherwise. */\nexport const DEFAULT_OPENAPI_OUTPUT = \"storage/openapi/openapi.json\";\n\ntype OptionValue = CommandActionData[\"options\"][string];\n\nfunction stringOption(value: OptionValue | undefined): string | undefined {\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : undefined;\n}\n\n/**\n * Action behind `warlock generate.openapi`.\n *\n * Builds the OpenAPI 3.1 document in a fresh registration child (no connectors are booted),\n * writes it as pretty JSON to `--out` (default `storage/openapi/openapi.json`) and prints the\n * per-route gaps it found as warnings. A child failure is fatal and rejects; gaps never are.\n */\nexport async function generateOpenApiAction({ options }: CommandActionData): Promise<void> {\n const cwd = rootPath();\n const output = path.resolve(cwd, stringOption(options.out) ?? DEFAULT_OPENAPI_OUTPUT);\n\n const { document, warnings } = await collectOpenApiDocument({\n cwd,\n timeoutMs: resolveBuildConfig().routeRegistrationTimeoutMs,\n title: stringOption(options.title),\n server: stringOption(options.server),\n includePages: options.includePages === true,\n });\n\n await mkdir(path.dirname(output), { recursive: true });\n await writeFile(output, `${JSON.stringify(document, null, 2)}\\n`, \"utf8\");\n\n const operations = Object.values(document.paths).reduce(\n (total, item) => total + Object.keys(item).length,\n 0,\n );\n\n console.log(\n colors.greenBright(\n `OpenAPI ${document.openapi} written to ${output} (${Object.keys(document.paths).length} paths, ${operations} operations).`,\n ),\n );\n\n if (warnings.length > 0) {\n console.warn(colors.yellowBright(`\\n${warnings.length} warning${warnings.length === 1 ? \"\" : \"s\"}:`));\n\n for (const warning of warnings) {\n console.warn(` - ${warning}`);\n }\n }\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,yBAAyB;AAItC,SAAS,aAAa,OAAoD;CACxE,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS,IAAI,MAAM,KAAK,IAAI;AAC/E;;;;;;;;AASA,eAAsB,sBAAsB,EAAE,WAA6C;CACzF,MAAM,MAAM,SAAS;CACrB,MAAM,SAAS,KAAK,QAAQ,KAAK,aAAa,QAAQ,GAAG,mCAA2B;CAEpF,MAAM,EAAE,UAAU,aAAa,MAAM,uBAAuB;EAC1D;EACA,WAAW,mBAAmB,CAAC,CAAC;EAChC,OAAO,aAAa,QAAQ,KAAK;EACjC,QAAQ,aAAa,QAAQ,MAAM;EACnC,cAAc,QAAQ,iBAAiB;CACzC,CAAC;CAED,MAAM,MAAM,KAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;CACrD,MAAM,UAAU,QAAQ,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,EAAE,KAAK,MAAM;CAExE,MAAM,aAAa,OAAO,OAAO,SAAS,KAAK,CAAC,CAAC,QAC9C,OAAO,SAAS,QAAQ,OAAO,KAAK,IAAI,CAAC,CAAC,QAC3C,CACF;CAEA,QAAQ,IACN,OAAO,YACL,WAAW,SAAS,QAAQ,cAAc,OAAO,IAAI,OAAO,KAAK,SAAS,KAAK,CAAC,CAAC,OAAO,UAAU,WAAW,cAC/G,CACF;CAEA,IAAI,SAAS,SAAS,GAAG;EACvB,QAAQ,KAAK,OAAO,aAAa,KAAK,SAAS,OAAO,UAAU,SAAS,WAAW,IAAI,KAAK,IAAI,EAAE,CAAC;EAEpG,KAAK,MAAM,WAAW,UACpB,QAAQ,KAAK,OAAO,SAAS;CAEjC;AACF"}
@@ -0,0 +1,44 @@
1
+ import { command } from "../../commands/cli-command.mjs";
2
+ import { generateOpenApiAction } from "./openapi-generator.action.mjs";
3
+
4
+ //#region ../core/src/cli/commands/openapi-generator.command.ts
5
+ /**
6
+ * `warlock generate.openapi` — write an OpenAPI 3.1 document derived from the registered
7
+ * routes, handler validation, declared `responseSchema` and auth middleware.
8
+ *
9
+ * Named with a dot like `generate.typings`; the `generate` command itself is the scaffolder.
10
+ * Routes are read in the same isolated child process `warlock build` uses, so no connector
11
+ * is started and the app keeps running untouched.
12
+ */
13
+ const openApiGeneratorCommand = command({
14
+ name: "generate.openapi",
15
+ description: "Generate an OpenAPI 3.1 document (storage/openapi/openapi.json) from the routes",
16
+ action: generateOpenApiAction,
17
+ preload: { warlockConfig: true },
18
+ options: [
19
+ {
20
+ text: "--out, -o",
21
+ description: "Output file, relative to the project root (default storage/openapi/openapi.json)",
22
+ type: "string"
23
+ },
24
+ {
25
+ text: "--title",
26
+ description: "info.title (default: the name in package.json)",
27
+ type: "string"
28
+ },
29
+ {
30
+ text: "--server",
31
+ description: "servers[0].url (default: built from the http config)",
32
+ type: "string"
33
+ },
34
+ {
35
+ text: "--include-pages",
36
+ description: "Also document page (SSR) routes",
37
+ type: "boolean"
38
+ }
39
+ ]
40
+ });
41
+
42
+ //#endregion
43
+ export { openApiGeneratorCommand };
44
+ //# sourceMappingURL=openapi-generator.command.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-generator.command.mjs","names":[],"sources":["../../../../../../../../core/src/cli/commands/openapi-generator.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\nimport { generateOpenApiAction } from \"./openapi-generator.action\";\n\n/**\n * `warlock generate.openapi` — write an OpenAPI 3.1 document derived from the registered\n * routes, handler validation, declared `responseSchema` and auth middleware.\n *\n * Named with a dot like `generate.typings`; the `generate` command itself is the scaffolder.\n * Routes are read in the same isolated child process `warlock build` uses, so no connector\n * is started and the app keeps running untouched.\n */\nexport const openApiGeneratorCommand = command({\n name: \"generate.openapi\",\n description: \"Generate an OpenAPI 3.1 document (storage/openapi/openapi.json) from the routes\",\n action: generateOpenApiAction,\n preload: {\n warlockConfig: true,\n },\n options: [\n {\n text: \"--out, -o\",\n description: \"Output file, relative to the project root (default storage/openapi/openapi.json)\",\n type: \"string\",\n },\n {\n text: \"--title\",\n description: \"info.title (default: the name in package.json)\",\n type: \"string\",\n },\n {\n text: \"--server\",\n description: \"servers[0].url (default: built from the http config)\",\n type: \"string\",\n },\n {\n text: \"--include-pages\",\n description: \"Also document page (SSR) routes\",\n type: \"boolean\",\n },\n ],\n});\n"],"mappings":";;;;;;;;;;;;AAWA,MAAa,0BAA0B,QAAQ;CAC7C,MAAM;CACN,aAAa;CACb,QAAQ;CACR,SAAS,EACP,eAAe,KACjB;CACA,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;CACF;AACF,CAAC"}
@@ -6,6 +6,7 @@ import { doctorCommand } from "./commands/doctor.command.mjs";
6
6
  import { dropTablesCommand } from "./commands/drop-tables.command.mjs";
7
7
  import { generateCommand, generateControllerCommand, generateMigrationCommand, generateModelCommand, generateModuleCommand, generateRepositoryCommand, generateResourceCommand, generateServiceCommand, generateUseCaseCommand } from "./commands/generate/generate.command.mjs";
8
8
  import { migrateCommand } from "./commands/migrate.command.mjs";
9
+ import { openApiGeneratorCommand } from "./commands/openapi-generator.command.mjs";
9
10
  import { routesCommand, routesDiffCommand } from "./commands/routes.command.mjs";
10
11
  import { seedCommand } from "./commands/seed.command.mjs";
11
12
  import { startProductionCommand } from "./commands/start-production.command.mjs";
@@ -17,6 +18,7 @@ import { updateCommand } from "./commands/update.command.mjs";
17
18
  const frameworkCommands = [
18
19
  devServerCommand,
19
20
  typingsGeneratorCommand,
21
+ openApiGeneratorCommand,
20
22
  doctorCommand,
21
23
  routesCommand,
22
24
  routesDiffCommand,
@@ -1 +1 @@
1
- {"version":3,"file":"framework-cli-commands.mjs","names":[],"sources":["../../../../../../../core/src/cli/framework-cli-commands.ts"],"sourcesContent":["import { addCommand } from \"./commands/add.command\";\nimport { buildCommand } from \"./commands/build.command\";\nimport { createDatabaseCommand } from \"./commands/create-database.command\";\nimport { devServerCommand } from \"./commands/dev-server.command\";\nimport { doctorCommand } from \"./commands/doctor.command\";\nimport { dropTablesCommand } from \"./commands/drop-tables.command\";\nimport {\n generateCommand,\n generateControllerCommand,\n generateMigrationCommand,\n generateModelCommand,\n generateModuleCommand,\n generateRepositoryCommand,\n generateResourceCommand,\n generateServiceCommand,\n generateUseCaseCommand,\n} from \"./commands/generate/generate.command\";\nimport { migrateCommand } from \"./commands/migrate.command\";\nimport { routesCommand, routesDiffCommand } from \"./commands/routes.command\";\nimport { seedCommand } from \"./commands/seed.command\";\nimport { startProductionCommand } from \"./commands/start-production.command\";\nimport { storagePutCommand } from \"./commands/storage-put.command\";\nimport { typingsGeneratorCommand } from \"./commands/typings-generator.command\";\nimport { updateCommand } from \"./commands/update.command\";\n\nexport const frameworkCommands = [\n // development commands\n devServerCommand,\n typingsGeneratorCommand,\n\n // diagnostics commands\n doctorCommand,\n routesCommand,\n routesDiffCommand,\n\n // production commands\n buildCommand,\n startProductionCommand,\n\n // database commands\n migrateCommand,\n seedCommand,\n createDatabaseCommand,\n dropTablesCommand,\n\n // generation/installation commands\n addCommand,\n updateCommand,\n\n // scaffolding commands\n generateCommand,\n generateModuleCommand,\n generateControllerCommand,\n generateServiceCommand,\n generateUseCaseCommand,\n generateModelCommand,\n generateRepositoryCommand,\n generateResourceCommand,\n generateMigrationCommand,\n\n // storage commands\n storagePutCommand,\n];\n"],"mappings":";;;;;;;;;;;;;;;;AAyBA,MAAa,oBAAoB;CAE/B;CACA;CAGA;CACA;CACA;CAGA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF"}
1
+ {"version":3,"file":"framework-cli-commands.mjs","names":[],"sources":["../../../../../../../core/src/cli/framework-cli-commands.ts"],"sourcesContent":["import { addCommand } from \"./commands/add.command\";\nimport { buildCommand } from \"./commands/build.command\";\nimport { createDatabaseCommand } from \"./commands/create-database.command\";\nimport { devServerCommand } from \"./commands/dev-server.command\";\nimport { doctorCommand } from \"./commands/doctor.command\";\nimport { dropTablesCommand } from \"./commands/drop-tables.command\";\nimport {\n generateCommand,\n generateControllerCommand,\n generateMigrationCommand,\n generateModelCommand,\n generateModuleCommand,\n generateRepositoryCommand,\n generateResourceCommand,\n generateServiceCommand,\n generateUseCaseCommand,\n} from \"./commands/generate/generate.command\";\nimport { migrateCommand } from \"./commands/migrate.command\";\nimport { openApiGeneratorCommand } from \"./commands/openapi-generator.command\";\nimport { routesCommand, routesDiffCommand } from \"./commands/routes.command\";\nimport { seedCommand } from \"./commands/seed.command\";\nimport { startProductionCommand } from \"./commands/start-production.command\";\nimport { storagePutCommand } from \"./commands/storage-put.command\";\nimport { typingsGeneratorCommand } from \"./commands/typings-generator.command\";\nimport { updateCommand } from \"./commands/update.command\";\n\nexport const frameworkCommands = [\n // development commands\n devServerCommand,\n typingsGeneratorCommand,\n openApiGeneratorCommand,\n\n // diagnostics commands\n doctorCommand,\n routesCommand,\n routesDiffCommand,\n\n // production commands\n buildCommand,\n startProductionCommand,\n\n // database commands\n migrateCommand,\n seedCommand,\n createDatabaseCommand,\n dropTablesCommand,\n\n // generation/installation commands\n addCommand,\n updateCommand,\n\n // scaffolding commands\n generateCommand,\n generateModuleCommand,\n generateControllerCommand,\n generateServiceCommand,\n generateUseCaseCommand,\n generateModelCommand,\n generateRepositoryCommand,\n generateResourceCommand,\n generateMigrationCommand,\n\n // storage commands\n storagePutCommand,\n];\n"],"mappings":";;;;;;;;;;;;;;;;;AA0BA,MAAa,oBAAoB;CAE/B;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF"}
@@ -0,0 +1,36 @@
1
+ //#region ../core/src/dev-server/build-development-openapi-document.ts
2
+ const warned = /* @__PURE__ */ new Set();
3
+ /**
4
+ * Build the OpenAPI document from the routes registered in THIS process, for the dev server.
5
+ *
6
+ * `warlock dev` has already loaded every route module, so no child process is needed: this
7
+ * runs the same `buildOpenApiForChild` the `generate.openapi` child runs (same resource
8
+ * export map, `package.json` info, `http` server and `validation.response` config), with the
9
+ * dev server's file list. Each distinct warning is logged once per process, not on every
10
+ * request to the docs page.
11
+ *
12
+ * Lives under `dev-server/`, off the production entry: the dev server registers it as the
13
+ * development OpenAPI provider (`registerDevelopmentOpenApiProvider`), and devtools reads it
14
+ * through `getDevelopmentOpenApiDocument()` from the root barrel.
15
+ */
16
+ async function buildDevelopmentOpenApiDocument() {
17
+ const [{ filesOrchestrator }, { devLogWarn }, { buildOpenApiForChild }] = await Promise.all([
18
+ import("./files-orchestrator.mjs"),
19
+ import("./dev-logger.mjs"),
20
+ import("../openapi/openapi-child-output.mjs")
21
+ ]);
22
+ return buildOpenApiForChild({
23
+ cwd: process.cwd(),
24
+ files: filesOrchestrator.getFiles().values(),
25
+ request: { version: 1 },
26
+ onWarn: (message) => {
27
+ if (warned.has(message)) return;
28
+ warned.add(message);
29
+ devLogWarn(message);
30
+ }
31
+ });
32
+ }
33
+
34
+ //#endregion
35
+ export { buildDevelopmentOpenApiDocument };
36
+ //# sourceMappingURL=build-development-openapi-document.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-development-openapi-document.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/build-development-openapi-document.ts"],"sourcesContent":["import type { OpenApiBuildResult } from \"../openapi/types\";\n\nconst warned = new Set<string>();\n\n/**\n * Build the OpenAPI document from the routes registered in THIS process, for the dev server.\n *\n * `warlock dev` has already loaded every route module, so no child process is needed: this\n * runs the same `buildOpenApiForChild` the `generate.openapi` child runs (same resource\n * export map, `package.json` info, `http` server and `validation.response` config), with the\n * dev server's file list. Each distinct warning is logged once per process, not on every\n * request to the docs page.\n *\n * Lives under `dev-server/`, off the production entry: the dev server registers it as the\n * development OpenAPI provider (`registerDevelopmentOpenApiProvider`), and devtools reads it\n * through `getDevelopmentOpenApiDocument()` from the root barrel.\n */\nexport async function buildDevelopmentOpenApiDocument(): Promise<OpenApiBuildResult> {\n const [{ filesOrchestrator }, { devLogWarn }, { buildOpenApiForChild }] = await Promise.all([\n import(\"./files-orchestrator\"),\n import(\"./dev-logger\"),\n import(\"../openapi/openapi-child-output\"),\n ]);\n\n return buildOpenApiForChild({\n cwd: process.cwd(),\n files: filesOrchestrator.getFiles().values(),\n request: { version: 1 },\n onWarn: (message) => {\n if (warned.has(message)) return;\n\n warned.add(message);\n devLogWarn(message);\n },\n });\n}\n"],"mappings":";AAEA,MAAM,yBAAS,IAAI,IAAY;;;;;;;;;;;;;;AAe/B,eAAsB,kCAA+D;CACnF,MAAM,CAAC,EAAE,qBAAqB,EAAE,cAAc,EAAE,0BAA0B,MAAM,QAAQ,IAAI;EAC1F,OAAO;EACP,OAAO;EACP,OAAO;CACT,CAAC;CAED,OAAO,qBAAqB;EAC1B,KAAK,QAAQ,IAAI;EACjB,OAAO,kBAAkB,SAAS,CAAC,CAAC,OAAO;EAC3C,SAAS,EAAE,SAAS,EAAE;EACtB,SAAS,YAAY;GACnB,IAAI,OAAO,IAAI,OAAO,GAAG;GAEzB,OAAO,IAAI,OAAO;GAClB,WAAW,OAAO;EACpB;CACF,CAAC;AACH"}
@@ -0,0 +1,27 @@
1
+ import { devLogWarn } from "./dev-logger.mjs";
2
+ import { filesOrchestrator } from "./files-orchestrator.mjs";
3
+ import { collectNamedApiRoutesWithResponses } from "../router/named-api-routes-with-responses.mjs";
4
+
5
+ //#region ../core/src/dev-server/collect-development-api-routes.ts
6
+ const warned = /* @__PURE__ */ new Set();
7
+ /**
8
+ * Named API routes for the dev server's in-process route-type publisher. Uses the same
9
+ * serializer as the typings child process, so `response` strings never differ between
10
+ * `warlock dev` and `warlock generate.typings`. Each distinct warning is logged once per
11
+ * process, not on every hot reload.
12
+ */
13
+ function collectDevelopmentApiRoutes() {
14
+ return collectNamedApiRoutesWithResponses({
15
+ appRoot: process.cwd(),
16
+ files: filesOrchestrator.getFiles().values(),
17
+ onWarn: (message) => {
18
+ if (warned.has(message)) return;
19
+ warned.add(message);
20
+ devLogWarn(message);
21
+ }
22
+ });
23
+ }
24
+
25
+ //#endregion
26
+ export { collectDevelopmentApiRoutes };
27
+ //# sourceMappingURL=collect-development-api-routes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-development-api-routes.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/collect-development-api-routes.ts"],"sourcesContent":["import { collectNamedApiRoutesWithResponses } from \"../router/named-api-routes-with-responses\";\nimport type { NamedApiRoute } from \"../router/types\";\nimport { devLogWarn } from \"./dev-logger\";\nimport { filesOrchestrator } from \"./files-orchestrator\";\n\nconst warned = new Set<string>();\n\n/**\n * Named API routes for the dev server's in-process route-type publisher. Uses the same\n * serializer as the typings child process, so `response` strings never differ between\n * `warlock dev` and `warlock generate.typings`. Each distinct warning is logged once per\n * process, not on every hot reload.\n */\nexport function collectDevelopmentApiRoutes(): Promise<readonly NamedApiRoute[]> {\n return collectNamedApiRoutesWithResponses({\n appRoot: process.cwd(),\n files: filesOrchestrator.getFiles().values(),\n onWarn: (message) => {\n if (warned.has(message)) return;\n\n warned.add(message);\n devLogWarn(message);\n },\n });\n}\n"],"mappings":";;;;;AAKA,MAAM,yBAAS,IAAI,IAAY;;;;;;;AAQ/B,SAAgB,8BAAiE;CAC/E,OAAO,mCAAmC;EACxC,SAAS,QAAQ,IAAI;EACrB,OAAO,kBAAkB,SAAS,CAAC,CAAC,OAAO;EAC3C,SAAS,YAAY;GACnB,IAAI,OAAO,IAAI,OAAO,GAAG;GAEzB,OAAO,IAAI,OAAO;GAClB,WAAW,OAAO;EACpB;CACF,CAAC;AACH"}
@@ -82,6 +82,14 @@ function devLogTimings(timings) {
82
82
  ].join(colors.dim(" · "));
83
83
  console.log(`${timestamp()} ${colors.dim("⏱")} ${line}`);
84
84
  }
85
+ function devLogConfig(file, connectors) {
86
+ const relativePath = Path.toRelative(file);
87
+ const connectorInfo = connectors && connectors.length > 0 ? colors.dim(` → restarting ${connectors.join(", ")}`) : "";
88
+ console.log(`${timestamp()} ${colors.cyan("config reload")} ${colors.dim(relativePath)}${connectorInfo}`);
89
+ }
90
+ function devLogReady(message) {
91
+ console.log(`\n${timestamp()} ${colors.green("➜")} ${colors.bold(message)}`);
92
+ }
85
93
  function devLogSection(title) {
86
94
  console.log(`\n${timestamp()} ${colors.bold(colors.cyan(title))}`);
87
95
  }
@@ -117,5 +125,5 @@ function formatModuleNotFoundError(error, suggestions) {
117
125
  const devServeLog = devLog;
118
126
 
119
127
  //#endregion
120
- export { devLogDim, devLogError, devLogHMR, devLogInfo, devLogSection, devLogSuccess, devLogTimings, devLogWarn, devServeLog, formatModuleNotFoundError };
128
+ export { devLog, devLogConfig, devLogDim, devLogError, devLogHMR, devLogInfo, devLogReady, devLogSection, devLogSuccess, devLogTimings, devLogWarn, devServeLog, formatErrorStack, formatModuleNotFoundError };
121
129
  //# sourceMappingURL=dev-logger.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev-logger.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/dev-logger.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport dayjs from \"dayjs\";\nimport { Path } from \"../utils/normalized-path\";\n\n/**\n * Dev server logger — Vite-style formatting helpers.\n */\n\nfunction timestamp(): string {\n return colors.dim(`${dayjs().format(\"HH:mm:ss A\")}`);\n}\n\nexport function devLog(message: string) {\n console.log(`${timestamp()} ${message}`);\n}\n\nexport function devLogSuccess(message: string) {\n console.log(`${timestamp()} ${colors.green(\"✓\")} ${colors.green(message)}`);\n}\n\n/**\n * Colourise a stack trace so the eye lands on *your* code.\n *\n * - Error header (`ReferenceError: x is not defined`) → bold red.\n * - Frames in project `src/` → highlighted: a green `›` pointer, yellow\n * function name, cyan relative path, dim `:line:col`. These are almost\n * always where the bug is.\n * - Framework (`@warlock.js`), `node_modules`, and `node:` internal frames\n * → dimmed and relativised. Still there for context, just out of the way.\n *\n * Source maps are enabled in dev, so the paths/lines here are already the\n * original `.ts` locations — this only changes how they're painted.\n */\nexport function formatErrorStack(stack: string): string {\n const frame = /^(\\s*)at (.+?) \\((.+):(\\d+):(\\d+)\\)$/;\n const bareFrame = /^(\\s*)at (.+):(\\d+):(\\d+)$/;\n let headerDone = false;\n\n return stack\n .split(\"\\n\")\n .map((line) => {\n const withFn = line.match(frame);\n const bare = line.match(bareFrame);\n\n if (!withFn && !bare) {\n // Header line(s) before the first frame.\n const painted = headerDone ? colors.dim(line) : colors.bold(colors.red(line));\n return painted;\n }\n\n headerDone = true;\n\n // Every read here is defaulted rather than asserted, and the reason is\n // what this function IS: a stack-trace formatter. If it throws while\n // formatting, it throws INSIDE error reporting — and what the developer\n // then sees is this function's failure instead of the error they were\n // actually chasing. An unreadable frame should degrade to a blank field,\n // never take the report down with it — every fatal needs an\n // unconditional floor.\n const fn = (withFn ? withFn[2] : \"\") ?? \"\";\n const file = (withFn ? withFn[3] : bare?.[2]) ?? \"\";\n const lineNo = (withFn ? withFn[4] : bare?.[3]) ?? \"\";\n const col = (withFn ? withFn[5] : bare?.[4]) ?? \"\";\n\n const isNodeInternal = file.startsWith(\"node:\");\n const isDep = file.includes(\"node_modules\");\n const isFramework = /[\\\\/]@warlock\\.js[\\\\/]/.test(file);\n const isUserCode = !isNodeInternal && !isDep && !isFramework;\n\n const rel = isNodeInternal ? file : Path.toRelative(file);\n const loc = `:${lineNo}:${col}`;\n\n if (isUserCode) {\n return (\n ` ${colors.green(\"›\")} ${colors.dim(\"at\")} ` +\n `${colors.yellow(fn || \"<anonymous>\")} ` +\n `${colors.cyan(rel)}${colors.dim(loc)}`\n );\n }\n\n const label = fn ? `at ${fn} ${rel}${loc}` : `at ${rel}${loc}`;\n return ` ${colors.dim(label)}`;\n })\n .join(\"\\n\");\n}\n\nexport function devLogError(message: string, error?: any) {\n console.log(`${timestamp()} ${colors.red(\"✗\")} ${colors.red(message)}`);\n if (error?.stack) console.log(formatErrorStack(error.stack));\n}\n\nexport function devLogWarn(message: string) {\n console.log(`${timestamp()} ${colors.yellow(\"⚠\")} ${colors.yellow(message)}`);\n}\n\nexport function devLogInfo(message: string) {\n console.log(`${timestamp()} ${colors.cyan(message)}`);\n}\n\nexport function devLogDim(message: string) {\n console.log(`${timestamp()} ${colors.dim(message)}`);\n}\n\nexport function devLogHMR(file: string, dependents?: number, durationMs?: number) {\n const relativePath = Path.toRelative(file);\n const depInfo = dependents\n ? colors.dim(` +${dependents} module${dependents > 1 ? \"s\" : \"\"}`)\n : \"\";\n const durationInfo = durationMs !== undefined ? colors.dim(` (${durationMs}ms)`) : \"\";\n console.log(\n `${timestamp()} 🔥 ${colors.green(\"hmr update\")} ${colors.dim(relativePath)}${depInfo}${durationInfo}`,\n );\n}\n\n/**\n * Per-phase breakdown for one hot reload, printed only when\n * `devServer.timings` is on. Phases run in this order: the watcher's\n * `awaitWriteFinish` settle wait, this handler's own debounce wait,\n * module-graph invalidation (version bumps), re-import of the changed\n * modules, and connector restart/rebind.\n */\nexport type ReloadPhaseTimings = {\n watcherSettleMs: number;\n debounceWaitMs: number;\n moduleGraphInvalidationMs: number;\n reimportMs: number;\n connectorRestartMs: number;\n};\n\nexport function devLogTimings(timings: ReloadPhaseTimings) {\n const phase = (label: string, ms: number) => `${colors.dim(label)} ${colors.dim(`${Math.round(ms)}ms`)}`;\n const line = [\n phase(\"watcher\", timings.watcherSettleMs),\n phase(\"debounce\", timings.debounceWaitMs),\n phase(\"graph\", timings.moduleGraphInvalidationMs),\n phase(\"reimport\", timings.reimportMs),\n phase(\"connectors\", timings.connectorRestartMs),\n ].join(colors.dim(\" · \"));\n console.log(`${timestamp()} ${colors.dim(\"⏱\")} ${line}`);\n}\n\nexport function devLogConfig(file: string, connectors?: string[]) {\n const relativePath = Path.toRelative(file);\n const connectorInfo =\n connectors && connectors.length > 0 ? colors.dim(` → restarting ${connectors.join(\", \")}`) : \"\";\n console.log(\n `${timestamp()} ${colors.cyan(\"config reload\")} ${colors.dim(relativePath)}${connectorInfo}`,\n );\n}\n\nexport function devLogReady(message: string) {\n console.log(`\\n${timestamp()} ${colors.green(\"➜\")} ${colors.bold(message)}`);\n}\n\nexport function devLogSection(title: string) {\n console.log(`\\n${timestamp()} ${colors.bold(colors.cyan(title))}`);\n}\n\n/**\n * Format ERR_MODULE_NOT_FOUND so the displayed paths are relative to the\n * project root. The loader hook keeps source paths in the URL so we only\n * need to strip the absolute prefix — no cache-path translation any more.\n */\nexport function formatModuleNotFoundError(error: Error, suggestions?: string[]): string {\n const match = error.message.match(/Cannot find module '([^']+)' imported from '([^']+)'/);\n if (!match) return error.message;\n\n // Same rule as the frame formatter above: this renders a MODULE NOT FOUND\n // report, so a missing capture must degrade the message, not replace the\n // user's error with a crash inside the reporter.\n const [, rawModulePath, rawImporterPath] = match;\n const modulePath = rawModulePath ?? \"\";\n const importerPath = rawImporterPath ?? \"\";\n const lines: string[] = [\n \"\",\n `${colors.red(\"❌ MODULE NOT FOUND\")}`,\n \"\",\n `${colors.dim(\"Cannot find:\")} ${colors.cyan(Path.toRelative(modulePath))}`,\n \"\",\n `${colors.dim(\"Imported by:\")}`,\n ` ${colors.yellow(\"→\")} ${colors.white(Path.toRelative(importerPath))}`,\n ];\n\n if (suggestions && suggestions.length > 0) {\n lines.push(\"\");\n lines.push(`${colors.dim(\"Did you mean?\")}`);\n suggestions.forEach((s) => lines.push(` ${colors.cyan(\"→\")} ${colors.green(s)}`));\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** @deprecated alias retained for older callers. Use `devLog` directly. */\nexport const devServeLog = devLog;\n"],"mappings":"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"}
1
+ {"version":3,"file":"dev-logger.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/dev-logger.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport dayjs from \"dayjs\";\nimport { Path } from \"../utils/normalized-path\";\n\n/**\n * Dev server logger — Vite-style formatting helpers.\n */\n\nfunction timestamp(): string {\n return colors.dim(`${dayjs().format(\"HH:mm:ss A\")}`);\n}\n\nexport function devLog(message: string) {\n console.log(`${timestamp()} ${message}`);\n}\n\nexport function devLogSuccess(message: string) {\n console.log(`${timestamp()} ${colors.green(\"✓\")} ${colors.green(message)}`);\n}\n\n/**\n * Colourise a stack trace so the eye lands on *your* code.\n *\n * - Error header (`ReferenceError: x is not defined`) → bold red.\n * - Frames in project `src/` → highlighted: a green `›` pointer, yellow\n * function name, cyan relative path, dim `:line:col`. These are almost\n * always where the bug is.\n * - Framework (`@warlock.js`), `node_modules`, and `node:` internal frames\n * → dimmed and relativised. Still there for context, just out of the way.\n *\n * Source maps are enabled in dev, so the paths/lines here are already the\n * original `.ts` locations — this only changes how they're painted.\n */\nexport function formatErrorStack(stack: string): string {\n const frame = /^(\\s*)at (.+?) \\((.+):(\\d+):(\\d+)\\)$/;\n const bareFrame = /^(\\s*)at (.+):(\\d+):(\\d+)$/;\n let headerDone = false;\n\n return stack\n .split(\"\\n\")\n .map((line) => {\n const withFn = line.match(frame);\n const bare = line.match(bareFrame);\n\n if (!withFn && !bare) {\n // Header line(s) before the first frame.\n const painted = headerDone ? colors.dim(line) : colors.bold(colors.red(line));\n return painted;\n }\n\n headerDone = true;\n\n // Every read here is defaulted rather than asserted, and the reason is\n // what this function IS: a stack-trace formatter. If it throws while\n // formatting, it throws INSIDE error reporting — and what the developer\n // then sees is this function's failure instead of the error they were\n // actually chasing. An unreadable frame should degrade to a blank field,\n // never take the report down with it — every fatal needs an\n // unconditional floor.\n const fn = (withFn ? withFn[2] : \"\") ?? \"\";\n const file = (withFn ? withFn[3] : bare?.[2]) ?? \"\";\n const lineNo = (withFn ? withFn[4] : bare?.[3]) ?? \"\";\n const col = (withFn ? withFn[5] : bare?.[4]) ?? \"\";\n\n const isNodeInternal = file.startsWith(\"node:\");\n const isDep = file.includes(\"node_modules\");\n const isFramework = /[\\\\/]@warlock\\.js[\\\\/]/.test(file);\n const isUserCode = !isNodeInternal && !isDep && !isFramework;\n\n const rel = isNodeInternal ? file : Path.toRelative(file);\n const loc = `:${lineNo}:${col}`;\n\n if (isUserCode) {\n return (\n ` ${colors.green(\"›\")} ${colors.dim(\"at\")} ` +\n `${colors.yellow(fn || \"<anonymous>\")} ` +\n `${colors.cyan(rel)}${colors.dim(loc)}`\n );\n }\n\n const label = fn ? `at ${fn} ${rel}${loc}` : `at ${rel}${loc}`;\n return ` ${colors.dim(label)}`;\n })\n .join(\"\\n\");\n}\n\nexport function devLogError(message: string, error?: any) {\n console.log(`${timestamp()} ${colors.red(\"✗\")} ${colors.red(message)}`);\n if (error?.stack) console.log(formatErrorStack(error.stack));\n}\n\nexport function devLogWarn(message: string) {\n console.log(`${timestamp()} ${colors.yellow(\"⚠\")} ${colors.yellow(message)}`);\n}\n\nexport function devLogInfo(message: string) {\n console.log(`${timestamp()} ${colors.cyan(message)}`);\n}\n\nexport function devLogDim(message: string) {\n console.log(`${timestamp()} ${colors.dim(message)}`);\n}\n\nexport function devLogHMR(file: string, dependents?: number, durationMs?: number) {\n const relativePath = Path.toRelative(file);\n const depInfo = dependents\n ? colors.dim(` +${dependents} module${dependents > 1 ? \"s\" : \"\"}`)\n : \"\";\n const durationInfo = durationMs !== undefined ? colors.dim(` (${durationMs}ms)`) : \"\";\n console.log(\n `${timestamp()} 🔥 ${colors.green(\"hmr update\")} ${colors.dim(relativePath)}${depInfo}${durationInfo}`,\n );\n}\n\n/**\n * Per-phase breakdown for one hot reload, printed only when\n * `devServer.timings` is on. Phases run in this order: the watcher's\n * `awaitWriteFinish` settle wait, this handler's own debounce wait,\n * module-graph invalidation (version bumps), re-import of the changed\n * modules, and connector restart/rebind.\n */\nexport type ReloadPhaseTimings = {\n watcherSettleMs: number;\n debounceWaitMs: number;\n moduleGraphInvalidationMs: number;\n reimportMs: number;\n connectorRestartMs: number;\n};\n\nexport function devLogTimings(timings: ReloadPhaseTimings) {\n const phase = (label: string, ms: number) => `${colors.dim(label)} ${colors.dim(`${Math.round(ms)}ms`)}`;\n const line = [\n phase(\"watcher\", timings.watcherSettleMs),\n phase(\"debounce\", timings.debounceWaitMs),\n phase(\"graph\", timings.moduleGraphInvalidationMs),\n phase(\"reimport\", timings.reimportMs),\n phase(\"connectors\", timings.connectorRestartMs),\n ].join(colors.dim(\" · \"));\n console.log(`${timestamp()} ${colors.dim(\"⏱\")} ${line}`);\n}\n\nexport function devLogConfig(file: string, connectors?: string[]) {\n const relativePath = Path.toRelative(file);\n const connectorInfo =\n connectors && connectors.length > 0 ? colors.dim(` → restarting ${connectors.join(\", \")}`) : \"\";\n console.log(\n `${timestamp()} ${colors.cyan(\"config reload\")} ${colors.dim(relativePath)}${connectorInfo}`,\n );\n}\n\nexport function devLogReady(message: string) {\n console.log(`\\n${timestamp()} ${colors.green(\"➜\")} ${colors.bold(message)}`);\n}\n\nexport function devLogSection(title: string) {\n console.log(`\\n${timestamp()} ${colors.bold(colors.cyan(title))}`);\n}\n\n/**\n * Format ERR_MODULE_NOT_FOUND so the displayed paths are relative to the\n * project root. The loader hook keeps source paths in the URL so we only\n * need to strip the absolute prefix — no cache-path translation any more.\n */\nexport function formatModuleNotFoundError(error: Error, suggestions?: string[]): string {\n const match = error.message.match(/Cannot find module '([^']+)' imported from '([^']+)'/);\n if (!match) return error.message;\n\n // Same rule as the frame formatter above: this renders a MODULE NOT FOUND\n // report, so a missing capture must degrade the message, not replace the\n // user's error with a crash inside the reporter.\n const [, rawModulePath, rawImporterPath] = match;\n const modulePath = rawModulePath ?? \"\";\n const importerPath = rawImporterPath ?? \"\";\n const lines: string[] = [\n \"\",\n `${colors.red(\"❌ MODULE NOT FOUND\")}`,\n \"\",\n `${colors.dim(\"Cannot find:\")} ${colors.cyan(Path.toRelative(modulePath))}`,\n \"\",\n `${colors.dim(\"Imported by:\")}`,\n ` ${colors.yellow(\"→\")} ${colors.white(Path.toRelative(importerPath))}`,\n ];\n\n if (suggestions && suggestions.length > 0) {\n lines.push(\"\");\n lines.push(`${colors.dim(\"Did you mean?\")}`);\n suggestions.forEach((s) => lines.push(` ${colors.cyan(\"→\")} ${colors.green(s)}`));\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** @deprecated alias retained for older callers. Use `devLog` directly. */\nexport const devServeLog = devLog;\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import { Application } from "../application/application.mjs";
2
- import { router } from "../router/router.mjs";
3
2
  import "../application/index.mjs";
4
3
  import { ConnectorLifecyclePhase } from "../connectors/types.mjs";
5
4
  import { connectorsManager } from "../connectors/connectors-manager.mjs";
6
5
  import { warlockConfigManager } from "../warlock-config/warlock-config.manager.mjs";
6
+ import { registerDevelopmentOpenApiProvider } from "../openapi/development-openapi-provider.mjs";
7
7
  import "../warlock-config/index.mjs";
8
8
  import { devLogInfo, devLogSection, devLogWarn, devServeLog } from "./dev-logger.mjs";
9
9
  import { MANIFEST_PATH } from "./flags.mjs";
@@ -12,9 +12,11 @@ import { publishCurrentRouteTypes } from "./route-types-publisher.mjs";
12
12
  import { restartDevServer } from "./restart-dev-server.mjs";
13
13
  import { devServerShortcuts } from "./shortcuts.mjs";
14
14
  import { BootPreconditionError } from "./boot-precondition-error.mjs";
15
+ import { buildDevelopmentOpenApiDocument } from "./build-development-openapi-document.mjs";
15
16
  import { LayerExecutor } from "./layer-executor.mjs";
16
17
  import { printReadyBlock } from "./ready-block.mjs";
17
18
  import { registerInstalledDevtools } from "./register-installed-devtools.mjs";
19
+ import { collectDevelopmentApiRoutes } from "./collect-development-api-routes.mjs";
18
20
  import { typeGenerator } from "./type-generator.mjs";
19
21
  import { colors } from "@mongez/copper";
20
22
  import events from "@mongez/events";
@@ -38,6 +40,7 @@ var DevelopmentServer = class {
38
40
  try {
39
41
  this.connectorsStarted = true;
40
42
  const startedAt = performance.now();
43
+ registerDevelopmentOpenApiProvider(buildDevelopmentOpenApiDocument);
41
44
  if (this.options.fresh && await fileExistsAsync(MANIFEST_PATH)) {
42
45
  await unlinkAsync(MANIFEST_PATH);
43
46
  devServeLog(colors.cyanBright("Cleared manifest (--fresh)"));
@@ -56,7 +59,7 @@ var DevelopmentServer = class {
56
59
  }
57
60
  await registerInstalledDevtools();
58
61
  await connectorsManager.startPhase("late");
59
- await publishCurrentRouteTypes(process.cwd(), router.getNamedApiRoutes());
62
+ await publishCurrentRouteTypes(process.cwd(), await collectDevelopmentApiRoutes());
60
63
  this.layerExecutor = new LayerExecutor(filesOrchestrator.getDependencyGraph(), filesOrchestrator.specialFilesCollector, filesOrchestrator.moduleLoader, (absolutePath) => filesOrchestrator.bumpVersion(absolutePath), () => filesOrchestrator.flushVersionBumps());
61
64
  this.running = true;
62
65
  const duration = performance.now() - startedAt;
@@ -99,7 +102,7 @@ var DevelopmentServer = class {
99
102
  const codeFiles = [...batch.added, ...batch.changed].filter((p) => !isEnvPath(p) && p !== "warlock.config.ts" && !batch.restartTriggers?.includes(p));
100
103
  try {
101
104
  await this.layerExecutor.executeBatchReload(codeFiles, filesOrchestrator.getFiles(), batch.deleted, batch.changed, batch.timings);
102
- await publishCurrentRouteTypes(process.cwd(), router.getNamedApiRoutes());
105
+ await publishCurrentRouteTypes(process.cwd(), await collectDevelopmentApiRoutes());
103
106
  typeGenerator.executeTypingsGenerator([
104
107
  ...batch.added,
105
108
  ...batch.changed,
@@ -1 +1 @@
1
- {"version":3,"file":"development-server.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/development-server.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport events from \"@mongez/events\";\nimport { fileExistsAsync, unlinkAsync } from \"@warlock.js/fs\";\nimport { Application } from \"../application\";\nimport { connectorsManager } from \"../connectors/connectors-manager\";\nimport { ConnectorLifecyclePhase } from \"../connectors/types\";\nimport { router } from \"../router/router\";\nimport { warlockConfigManager } from \"../warlock-config\";\nimport { BootPreconditionError } from \"./boot-precondition-error\";\nimport { devLogInfo, devLogSection, devLogWarn, devServeLog } from \"./dev-logger\";\nimport { filesOrchestrator } from \"./files-orchestrator\";\nimport { MANIFEST_PATH } from \"./flags\";\nimport type { IncomingReloadTimings } from \"./layer-executor\";\nimport { LayerExecutor } from \"./layer-executor\";\nimport { printReadyBlock } from \"./ready-block\";\nimport { registerInstalledDevtools } from \"./register-installed-devtools\";\nimport { restartDevServer } from \"./restart-dev-server\";\nimport { publishCurrentRouteTypes } from \"./route-types-publisher\";\nimport { devServerShortcuts } from \"./shortcuts\";\nimport type { StartDevServerOptions } from \"./start-development-server\";\nimport { typeGenerator } from \"./type-generator\";\n\ntype Batch = {\n added: string[];\n changed: string[];\n deleted: string[];\n /** Non-code `watch.include` files that changed; only a restart applies them. */\n restartTriggers?: string[];\n timings?: IncomingReloadTimings;\n};\n\n/**\n * Top-level coordinator for `warlock dev`. Wires the file orchestrator, the\n * connectors, and the layer executor together, and listens for the watcher's\n * batched events to drive HMR.\n */\nexport class DevelopmentServer {\n private layerExecutor?: LayerExecutor;\n private running = false;\n\n /** Set once boot begins, so a failed boot still closes connectors that already started. */\n private connectorsStarted = false;\n private readonly options: StartDevServerOptions;\n\n public constructor(options: StartDevServerOptions = {}) {\n this.options = options;\n devLogSection(\"Starting Development Server...\");\n }\n\n public async start(): Promise<void> {\n try {\n this.connectorsStarted = true;\n const startedAt = performance.now();\n\n // --fresh deletes the manifest so reconciliation re-parses every file\n // from disk. Transpile caching is owned by the loader hook (in-memory).\n if (this.options.fresh && (await fileExistsAsync(MANIFEST_PATH))) {\n await unlinkAsync(MANIFEST_PATH);\n devServeLog(colors.cyanBright(\"Cleared manifest (--fresh)\"));\n }\n\n await filesOrchestrator.init();\n await filesOrchestrator.initializeAll();\n await filesOrchestrator.watchFiles();\n\n filesOrchestrator.specialFilesCollector.collect(filesOrchestrator.getFiles());\n\n this.setupEventListeners();\n\n // Decorator-driven registries (models, etc.) must be populated before\n // routes/services can resolve symbols by name.\n await this.autoDiscoverFiles();\n\n await filesOrchestrator.moduleLoader.loadAll();\n\n // A rejecting validator (Application.onValidateBoot) must abort boot\n // before late-phase connectors bind a port — this is what makes the\n // hook actually prevent serving instead of merely being defined.\n //\n // Wrapped and re-thrown as a BootPreconditionError: a rejected\n // validator is, by definition, a precondition whose cause cannot\n // change because the worker tried again — the supervisor must not\n // restart into the same rejection. See boot-precondition-error.ts.\n try {\n await Application.runStartupValidators();\n } catch (error) {\n throw new BootPreconditionError((error as Error).message, { cause: error });\n }\n\n // `@warlock.js/devtools` is a dev dependency the config never names;\n // when installed it joins the late phase here, and only here.\n await registerInstalledDevtools();\n\n // Late-phase connectors (http, socket) bind after app code has\n // registered routes/listeners.\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Late);\n\n // Web installs its page table from its late connector. Generate only\n // after that installation so `href()` and the declaration table agree.\n await publishCurrentRouteTypes(process.cwd(), router.getNamedApiRoutes());\n\n this.layerExecutor = new LayerExecutor(\n filesOrchestrator.getDependencyGraph(),\n filesOrchestrator.specialFilesCollector,\n filesOrchestrator.moduleLoader,\n (absolutePath) => filesOrchestrator.bumpVersion(absolutePath),\n () => filesOrchestrator.flushVersionBumps(),\n );\n\n this.running = true;\n\n const duration = performance.now() - startedAt;\n\n // App modules are loaded and both connector phases are active — signal a\n // complete boot so `Application.onceBooted(...)` listeners fire.\n Application.markBooted({\n environment: Application.environment,\n runtimeStrategy: Application.runtimeStrategy,\n bootDurationMs: duration,\n });\n\n // ONE block, AFTER the boot it summarises, replacing the scattered\n // \"N route(s) registered\" / \"Server ready at …\" / \"ready in …\" lines that\n // used to arrive in completion order. Warnings logged during boot sit\n // above it and stay there — see `ready-block.ts`.\n printReadyBlock(duration);\n\n // Tell the supervisor (when there is one) that boot completed, so its\n // healthy-uptime window counts from here rather than from spawn.\n process.send?.(\"warlock:booted\");\n\n // Precedence: explicit CLI option > devServer.* config > default.\n const devServerConfig = await warlockConfigManager.get(\"devServer\");\n const generateTypings =\n this.options.generateTypings ?? devServerConfig?.generateTypings ?? true;\n const healthCheckers = this.options.healthCheckers ?? devServerConfig?.healthCheckers ?? true;\n\n if (generateTypings) await typeGenerator.executeGenerateAllCommand();\n\n if (healthCheckers) {\n filesOrchestrator.startCheckingHealth(healthCheckers === true ? undefined : healthCheckers);\n }\n } catch (error) {\n devServeLog(colors.redBright(`Failed to start Development Server: ${error}`));\n await this.shutdown();\n throw error;\n }\n }\n\n /**\n * Eagerly import files whose decorators populate global registries so any\n * symbol-by-name resolution later in boot finds them.\n */\n private async autoDiscoverFiles(): Promise<void> {\n const discoveryTypes = [\"model\"] as const;\n for (const file of filesOrchestrator.files.values()) {\n if (file.type && (discoveryTypes as readonly string[]).includes(file.type)) {\n await filesOrchestrator.moduleLoader.loadModule(file, file.type);\n }\n }\n }\n\n /** Tail of the batch-reload chain (see `setupEventListeners`). */\n private reloadQueue: Promise<void> = Promise.resolve();\n\n private setupEventListeners(): void {\n // Reloads run one at a time: a second batch waits for the first to finish\n // so route re-registration and connector restarts never interleave.\n events.on(\"dev-server:batch-complete\", (batch: Batch) => {\n this.reloadQueue = this.reloadQueue\n .then(() => this.handleBatchComplete(batch))\n .catch((error) => devServeLog(colors.redBright(`Batch reload failed: ${error}`)));\n });\n }\n\n private async handleBatchComplete(batch: Batch): Promise<void> {\n if (!this.running || !this.layerExecutor) return;\n\n // warlock.config.ts holds settings read at boot (CLI commands, build\n // options, watch patterns, scheduled jobs) and .env feeds every config\n // that reads it. Neither can be hot-reloaded without leaving running\n // services configured with stale values, so the only honest response is\n // a full restart.\n const restartTrigger = this.findRestartTrigger(batch);\n\n if (restartTrigger) {\n // On success this never comes back — the process exits and the\n // supervisor replaces it. Reaching the next line means the restart was\n // declined by config or wasn't possible, so we fall through and still\n // hot-reload whatever ordinary code shared the batch.\n await this.restartForConfigChange(restartTrigger);\n }\n\n // No-op saves (an editor that fsyncs without a content write) are already\n // filtered upstream by the file hash in FileEventHandler, so batch.changed\n // only contains genuinely-changed paths by the time it reaches here.\n\n const total = batch.added.length + batch.changed.length + batch.deleted.length;\n if (total === 0) return;\n\n // Boot-time files still ride along in `changed` when a restart was\n // declined (`restartOnConfigChange: false`) or wasn't possible — they are\n // never hot-reloaded, so keep them out of the reload set either way.\n const codeFiles = [...batch.added, ...batch.changed].filter(\n (p) => !isEnvPath(p) && p !== \"warlock.config.ts\" && !batch.restartTriggers?.includes(p),\n );\n\n try {\n await this.layerExecutor.executeBatchReload(\n codeFiles,\n filesOrchestrator.getFiles(),\n batch.deleted,\n batch.changed,\n batch.timings,\n );\n\n // `executeBatchReload` includes route re-imports and affected connector\n // restarts. Publishing earlier could freeze a partial page/API view.\n // This also refreshes deletion-only batches after their cleanup/restart.\n await publishCurrentRouteTypes(process.cwd(), router.getNamedApiRoutes());\n\n typeGenerator.executeTypingsGenerator([...batch.added, ...batch.changed, ...batch.deleted]);\n\n filesOrchestrator.checkHealth(batch);\n } catch (error) {\n devServeLog(colors.redBright(`Failed to execute batch reload: ${error}`));\n }\n }\n\n /**\n * The boot-time file in this batch that can only be applied by restarting,\n * or `undefined` when the batch is ordinary application code.\n */\n private findRestartTrigger(batch: Batch): string | undefined {\n if (batch.changed.includes(\"warlock.config.ts\")) {\n return \"warlock.config.ts\";\n }\n\n return [...batch.added, ...batch.changed].find(isEnvPath) ?? batch.restartTriggers?.[0];\n }\n\n /**\n * Restart so a changed boot-time file actually takes effect.\n *\n * Opt out with `devServer.restartOnConfigChange: false` and the old\n * behaviour returns: a warning telling you to restart yourself. If the\n * restart can't be performed at all (no supervisor, a shutdown that threw),\n * `restartDevServer` reports it and we fall back to the same warning rather\n * than leaving the server running against config it isn't using.\n */\n private async restartForConfigChange(file: string): Promise<void> {\n const devServerConfig = await warlockConfigManager.get(\"devServer\");\n\n if (devServerConfig?.restartOnConfigChange === false) {\n devLogWarn(`${file} changed — restart the dev server to apply.`);\n return;\n }\n\n devLogInfo(`${file} changed — restarting to apply.`);\n\n const restarted = await restartDevServer(this);\n\n if (!restarted) {\n devLogWarn(`${file} changed — restart the dev server to apply.`);\n }\n }\n\n public async shutdown(): Promise<void> {\n // Always hand the terminal back, even on a repeat/no-op shutdown — a\n // process that exits while stdin is still in raw mode leaves the user's\n // shell without line editing or Ctrl+C.\n devServerShortcuts.release();\n\n if (!this.running && !this.connectorsStarted) return;\n devServeLog(colors.redBright(\"Shutting down Development Server...\"));\n this.running = false;\n this.connectorsStarted = false;\n await connectorsManager.shutdown();\n devServeLog(colors.greenBright(\"Development Server stopped\"));\n }\n\n public isRunning(): boolean {\n return this.running;\n }\n}\n\nfunction isEnvPath(path: string): boolean {\n const basename = path.split(\"/\").pop() ?? path;\n return basename === \".env\" || basename.startsWith(\".env.\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"development-server.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/development-server.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport events from \"@mongez/events\";\nimport { fileExistsAsync, unlinkAsync } from \"@warlock.js/fs\";\nimport { Application } from \"../application\";\nimport { connectorsManager } from \"../connectors/connectors-manager\";\nimport { ConnectorLifecyclePhase } from \"../connectors/types\";\nimport { warlockConfigManager } from \"../warlock-config\";\nimport { registerDevelopmentOpenApiProvider } from \"../openapi/development-openapi-provider\";\nimport { BootPreconditionError } from \"./boot-precondition-error\";\nimport { buildDevelopmentOpenApiDocument } from \"./build-development-openapi-document\";\nimport { devLogInfo, devLogSection, devLogWarn, devServeLog } from \"./dev-logger\";\nimport { filesOrchestrator } from \"./files-orchestrator\";\nimport { MANIFEST_PATH } from \"./flags\";\nimport type { IncomingReloadTimings } from \"./layer-executor\";\nimport { LayerExecutor } from \"./layer-executor\";\nimport { printReadyBlock } from \"./ready-block\";\nimport { registerInstalledDevtools } from \"./register-installed-devtools\";\nimport { restartDevServer } from \"./restart-dev-server\";\nimport { collectDevelopmentApiRoutes } from \"./collect-development-api-routes\";\nimport { publishCurrentRouteTypes } from \"./route-types-publisher\";\nimport { devServerShortcuts } from \"./shortcuts\";\nimport type { StartDevServerOptions } from \"./start-development-server\";\nimport { typeGenerator } from \"./type-generator\";\n\ntype Batch = {\n added: string[];\n changed: string[];\n deleted: string[];\n /** Non-code `watch.include` files that changed; only a restart applies them. */\n restartTriggers?: string[];\n timings?: IncomingReloadTimings;\n};\n\n/**\n * Top-level coordinator for `warlock dev`. Wires the file orchestrator, the\n * connectors, and the layer executor together, and listens for the watcher's\n * batched events to drive HMR.\n */\nexport class DevelopmentServer {\n private layerExecutor?: LayerExecutor;\n private running = false;\n\n /** Set once boot begins, so a failed boot still closes connectors that already started. */\n private connectorsStarted = false;\n private readonly options: StartDevServerOptions;\n\n public constructor(options: StartDevServerOptions = {}) {\n this.options = options;\n devLogSection(\"Starting Development Server...\");\n }\n\n public async start(): Promise<void> {\n try {\n this.connectorsStarted = true;\n const startedAt = performance.now();\n\n // Devtools reads this through the root barrel, which must not reach dev-server code.\n registerDevelopmentOpenApiProvider(buildDevelopmentOpenApiDocument);\n\n // --fresh deletes the manifest so reconciliation re-parses every file\n // from disk. Transpile caching is owned by the loader hook (in-memory).\n if (this.options.fresh && (await fileExistsAsync(MANIFEST_PATH))) {\n await unlinkAsync(MANIFEST_PATH);\n devServeLog(colors.cyanBright(\"Cleared manifest (--fresh)\"));\n }\n\n await filesOrchestrator.init();\n await filesOrchestrator.initializeAll();\n await filesOrchestrator.watchFiles();\n\n filesOrchestrator.specialFilesCollector.collect(filesOrchestrator.getFiles());\n\n this.setupEventListeners();\n\n // Decorator-driven registries (models, etc.) must be populated before\n // routes/services can resolve symbols by name.\n await this.autoDiscoverFiles();\n\n await filesOrchestrator.moduleLoader.loadAll();\n\n // A rejecting validator (Application.onValidateBoot) must abort boot\n // before late-phase connectors bind a port — this is what makes the\n // hook actually prevent serving instead of merely being defined.\n //\n // Wrapped and re-thrown as a BootPreconditionError: a rejected\n // validator is, by definition, a precondition whose cause cannot\n // change because the worker tried again — the supervisor must not\n // restart into the same rejection. See boot-precondition-error.ts.\n try {\n await Application.runStartupValidators();\n } catch (error) {\n throw new BootPreconditionError((error as Error).message, { cause: error });\n }\n\n // `@warlock.js/devtools` is a dev dependency the config never names;\n // when installed it joins the late phase here, and only here.\n await registerInstalledDevtools();\n\n // Late-phase connectors (http, socket) bind after app code has\n // registered routes/listeners.\n await connectorsManager.startPhase(ConnectorLifecyclePhase.Late);\n\n // Web installs its page table from its late connector. Generate only\n // after that installation so `href()` and the declaration table agree.\n await publishCurrentRouteTypes(process.cwd(), await collectDevelopmentApiRoutes());\n\n this.layerExecutor = new LayerExecutor(\n filesOrchestrator.getDependencyGraph(),\n filesOrchestrator.specialFilesCollector,\n filesOrchestrator.moduleLoader,\n (absolutePath) => filesOrchestrator.bumpVersion(absolutePath),\n () => filesOrchestrator.flushVersionBumps(),\n );\n\n this.running = true;\n\n const duration = performance.now() - startedAt;\n\n // App modules are loaded and both connector phases are active — signal a\n // complete boot so `Application.onceBooted(...)` listeners fire.\n Application.markBooted({\n environment: Application.environment,\n runtimeStrategy: Application.runtimeStrategy,\n bootDurationMs: duration,\n });\n\n // ONE block, AFTER the boot it summarises, replacing the scattered\n // \"N route(s) registered\" / \"Server ready at …\" / \"ready in …\" lines that\n // used to arrive in completion order. Warnings logged during boot sit\n // above it and stay there — see `ready-block.ts`.\n printReadyBlock(duration);\n\n // Tell the supervisor (when there is one) that boot completed, so its\n // healthy-uptime window counts from here rather than from spawn.\n process.send?.(\"warlock:booted\");\n\n // Precedence: explicit CLI option > devServer.* config > default.\n const devServerConfig = await warlockConfigManager.get(\"devServer\");\n const generateTypings =\n this.options.generateTypings ?? devServerConfig?.generateTypings ?? true;\n const healthCheckers = this.options.healthCheckers ?? devServerConfig?.healthCheckers ?? true;\n\n if (generateTypings) await typeGenerator.executeGenerateAllCommand();\n\n if (healthCheckers) {\n filesOrchestrator.startCheckingHealth(healthCheckers === true ? undefined : healthCheckers);\n }\n } catch (error) {\n devServeLog(colors.redBright(`Failed to start Development Server: ${error}`));\n await this.shutdown();\n throw error;\n }\n }\n\n /**\n * Eagerly import files whose decorators populate global registries so any\n * symbol-by-name resolution later in boot finds them.\n */\n private async autoDiscoverFiles(): Promise<void> {\n const discoveryTypes = [\"model\"] as const;\n for (const file of filesOrchestrator.files.values()) {\n if (file.type && (discoveryTypes as readonly string[]).includes(file.type)) {\n await filesOrchestrator.moduleLoader.loadModule(file, file.type);\n }\n }\n }\n\n /** Tail of the batch-reload chain (see `setupEventListeners`). */\n private reloadQueue: Promise<void> = Promise.resolve();\n\n private setupEventListeners(): void {\n // Reloads run one at a time: a second batch waits for the first to finish\n // so route re-registration and connector restarts never interleave.\n events.on(\"dev-server:batch-complete\", (batch: Batch) => {\n this.reloadQueue = this.reloadQueue\n .then(() => this.handleBatchComplete(batch))\n .catch((error) => devServeLog(colors.redBright(`Batch reload failed: ${error}`)));\n });\n }\n\n private async handleBatchComplete(batch: Batch): Promise<void> {\n if (!this.running || !this.layerExecutor) return;\n\n // warlock.config.ts holds settings read at boot (CLI commands, build\n // options, watch patterns, scheduled jobs) and .env feeds every config\n // that reads it. Neither can be hot-reloaded without leaving running\n // services configured with stale values, so the only honest response is\n // a full restart.\n const restartTrigger = this.findRestartTrigger(batch);\n\n if (restartTrigger) {\n // On success this never comes back — the process exits and the\n // supervisor replaces it. Reaching the next line means the restart was\n // declined by config or wasn't possible, so we fall through and still\n // hot-reload whatever ordinary code shared the batch.\n await this.restartForConfigChange(restartTrigger);\n }\n\n // No-op saves (an editor that fsyncs without a content write) are already\n // filtered upstream by the file hash in FileEventHandler, so batch.changed\n // only contains genuinely-changed paths by the time it reaches here.\n\n const total = batch.added.length + batch.changed.length + batch.deleted.length;\n if (total === 0) return;\n\n // Boot-time files still ride along in `changed` when a restart was\n // declined (`restartOnConfigChange: false`) or wasn't possible — they are\n // never hot-reloaded, so keep them out of the reload set either way.\n const codeFiles = [...batch.added, ...batch.changed].filter(\n (p) => !isEnvPath(p) && p !== \"warlock.config.ts\" && !batch.restartTriggers?.includes(p),\n );\n\n try {\n await this.layerExecutor.executeBatchReload(\n codeFiles,\n filesOrchestrator.getFiles(),\n batch.deleted,\n batch.changed,\n batch.timings,\n );\n\n // `executeBatchReload` includes route re-imports and affected connector\n // restarts. Publishing earlier could freeze a partial page/API view.\n // This also refreshes deletion-only batches after their cleanup/restart.\n await publishCurrentRouteTypes(process.cwd(), await collectDevelopmentApiRoutes());\n\n typeGenerator.executeTypingsGenerator([...batch.added, ...batch.changed, ...batch.deleted]);\n\n filesOrchestrator.checkHealth(batch);\n } catch (error) {\n devServeLog(colors.redBright(`Failed to execute batch reload: ${error}`));\n }\n }\n\n /**\n * The boot-time file in this batch that can only be applied by restarting,\n * or `undefined` when the batch is ordinary application code.\n */\n private findRestartTrigger(batch: Batch): string | undefined {\n if (batch.changed.includes(\"warlock.config.ts\")) {\n return \"warlock.config.ts\";\n }\n\n return [...batch.added, ...batch.changed].find(isEnvPath) ?? batch.restartTriggers?.[0];\n }\n\n /**\n * Restart so a changed boot-time file actually takes effect.\n *\n * Opt out with `devServer.restartOnConfigChange: false` and the old\n * behaviour returns: a warning telling you to restart yourself. If the\n * restart can't be performed at all (no supervisor, a shutdown that threw),\n * `restartDevServer` reports it and we fall back to the same warning rather\n * than leaving the server running against config it isn't using.\n */\n private async restartForConfigChange(file: string): Promise<void> {\n const devServerConfig = await warlockConfigManager.get(\"devServer\");\n\n if (devServerConfig?.restartOnConfigChange === false) {\n devLogWarn(`${file} changed — restart the dev server to apply.`);\n return;\n }\n\n devLogInfo(`${file} changed — restarting to apply.`);\n\n const restarted = await restartDevServer(this);\n\n if (!restarted) {\n devLogWarn(`${file} changed — restart the dev server to apply.`);\n }\n }\n\n public async shutdown(): Promise<void> {\n // Always hand the terminal back, even on a repeat/no-op shutdown — a\n // process that exits while stdin is still in raw mode leaves the user's\n // shell without line editing or Ctrl+C.\n devServerShortcuts.release();\n\n if (!this.running && !this.connectorsStarted) return;\n devServeLog(colors.redBright(\"Shutting down Development Server...\"));\n this.running = false;\n this.connectorsStarted = false;\n await connectorsManager.shutdown();\n devServeLog(colors.greenBright(\"Development Server stopped\"));\n }\n\n public isRunning(): boolean {\n return this.running;\n }\n}\n\nfunction isEnvPath(path: string): boolean {\n const basename = path.split(\"/\").pop() ?? path;\n return basename === \".env\" || basename.startsWith(\".env.\");\n}\n"],"mappings":"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"}
@@ -24,6 +24,22 @@ import { fileURLToPath } from "node:url";
24
24
 
25
25
  //#region ../core/src/dev-server/files-orchestrator.ts
26
26
  /**
27
+ * Register a cleanup callback that fires before the current module is
28
+ * unloaded by HMR. Stack-inspects the caller so user code doesn't need to
29
+ * thread the FileManager through.
30
+ */
31
+ function onCleanup(callback) {
32
+ if (globalThis.__currentModuleFile) {
33
+ globalThis.__currentModuleFile.addCleanup(callback);
34
+ return;
35
+ }
36
+ const callerLine = (/* @__PURE__ */ new Error()).stack?.split("\n")[2];
37
+ const rawCaller = (callerLine?.match(/\((.+?):\d+:\d+\)/) || callerLine?.match(/at (.+?):\d+:\d+/))?.[1];
38
+ const callerFile = rawCaller?.startsWith("file://") ? fileURLToPath(rawCaller.replace(/\?.*$/, "")) : rawCaller;
39
+ if (!callerFile) return;
40
+ filesOrchestrator.files.get(Path.toRelative(callerFile))?.addCleanup(callback);
41
+ }
42
+ /**
27
43
  * Top-level coordinator for the dev server's file system.
28
44
  *
29
45
  * Owns: file discovery, the dependency graph, the manifest, the special-files
@@ -185,5 +201,5 @@ async function runInBatches(items, size, fn) {
185
201
  const filesOrchestrator = new FilesOrchestrator();
186
202
 
187
203
  //#endregion
188
- export { filesOrchestrator };
204
+ export { FilesOrchestrator, filesOrchestrator, onCleanup };
189
205
  //# sourceMappingURL=files-orchestrator.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"files-orchestrator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/files-orchestrator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { init } from \"es-module-lexer\";\nimport { fileURLToPath } from \"node:url\";\nimport type { MessagePort } from \"node:worker_threads\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { DependencyGraph } from \"./dependency-graph\";\nimport { devLogDim, devLogSuccess } from \"./dev-logger\";\nimport { FileEventHandler } from \"./file-event-handler\";\nimport { FileManager } from \"./file-manager\";\nimport { FileOperations } from \"./file-operations\";\nimport { FilesWatcher, createIgnoredMatcher } from \"./files-watcher\";\nimport { FILE_PROCESSING_BATCH_SIZE } from \"./flags\";\nimport { EslintHealthChecker } from \"./health-checker/checkers/eslint-health-checker\";\nimport { TypescriptHealthChecker } from \"./health-checker/checkers/typescript-health-checker\";\nimport type { FileHealthCheckerContract } from \"./health-checker/file-health-checker.contract\";\nimport { FilesHealthcareManager } from \"./health-checker/files-healthcare.manager\";\nimport { buildTranspileInit } from \"./loader/build-transpile-init.js\";\nimport { registerLoader } from \"./loader/register-loader.js\";\nimport { ManifestManager } from \"./manifest-manager\";\nimport { ModuleLoader } from \"./module-loader\";\nimport { packageJsonManager } from \"./package-json-manager\";\nimport { Path } from \"../utils/normalized-path\";\nimport { SpecialFilesCollector } from \"./special-files-collector\";\nimport { tsconfigManager } from \"./tsconfig-manager\";\nimport { ensureWarlockDirectory, getFilesFromDirectory } from \"./utils\";\n\n/**\n * Register a cleanup callback that fires before the current module is\n * unloaded by HMR. Stack-inspects the caller so user code doesn't need to\n * thread the FileManager through.\n */\nexport function onCleanup(callback: () => any) {\n if (globalThis.__currentModuleFile) {\n globalThis.__currentModuleFile.addCleanup(callback);\n return;\n }\n\n const stack = new Error().stack;\n const callerLine = stack?.split(\"\\n\")[2];\n const match = callerLine?.match(/\\((.+?):\\d+:\\d+\\)/) || callerLine?.match(/at (.+?):\\d+:\\d+/);\n const rawCaller = match?.[1];\n\n // Loaded modules report `file:///…?v=N`; turn that back into a plain path.\n const callerFile = rawCaller?.startsWith(\"file://\")\n ? fileURLToPath(rawCaller.replace(/\\?.*$/, \"\"))\n : rawCaller;\n\n if (!callerFile) return;\n const fileManager = filesOrchestrator.files.get(Path.toRelative(callerFile));\n fileManager?.addCleanup(callback);\n}\n\n/**\n * Top-level coordinator for the dev server's file system.\n *\n * Owns: file discovery, the dependency graph, the manifest, the special-files\n * index, the file watcher, and the lifecycle of the ESM loader hook (which\n * provides cache-busting via `?v=N` version tokens).\n */\nexport class FilesOrchestrator {\n public readonly filesWatcher = new FilesWatcher();\n public readonly files = new Map<string, FileManager>();\n private readonly manifest = new ManifestManager(this.files);\n private readonly dependencyGraph = new DependencyGraph();\n private readonly healthCheckerManager = new FilesHealthcareManager(this.files);\n public readonly specialFilesCollector = new SpecialFilesCollector();\n public readonly moduleLoader = new ModuleLoader(this.specialFilesCollector);\n public readonly fileOperations: FileOperations;\n private readonly eventHandler: FileEventHandler;\n\n /** Main-thread end of the MessageChannel to the loader hook worker. */\n private loaderPort: MessagePort | null = null;\n\n /** Resolve callbacks for pending `flushVersionBumps()` calls. */\n private readonly pendingFlushes: Array<() => void> = [];\n\n public isInitialized = false;\n\n public constructor() {\n this.fileOperations = new FileOperations(\n this.files,\n this.dependencyGraph,\n this.manifest,\n this.specialFilesCollector,\n );\n\n this.eventHandler = new FileEventHandler(\n this.fileOperations,\n this.manifest,\n this.dependencyGraph,\n this.files,\n );\n }\n\n public async add(relativePath: string): Promise<FileManager> {\n return this.fileOperations.addFile(relativePath);\n }\n\n public async load<T>(relativePath: string, type = \"other\") {\n // Guarantee the ESM loader hook is registered before we import anything.\n // `load()` is the dynamic-import entrypoint, so it is the one place that\n // must satisfy `init()`'s documented precondition (\"Must be called before\n // any user src/ module is dynamically imported\") on its own — it cannot\n // assume a caller ran `init()` first.\n //\n // The CLI command-resolution path proves why: `cliCommandsLoader.load()`\n // imports a project command module during command *lookup* (before the\n // command's own `execute()` runs), whereas `loadPreloaders()` only calls\n // `init()` from *inside* `execute()`. So a `warlock <command>` whose\n // command file imports an `app/*` alias or a `.ts` sibling was imported\n // with no hook registered and died on `ERR_MODULE_NOT_FOUND` (f9d8f99f).\n //\n // `init()` is idempotent (guarded by `isInitialized`), so this is a no-op\n // on the dev-server path, which already calls it explicitly at startup.\n await this.init();\n\n const fileManager = await this.add(relativePath);\n return this.moduleLoader.loadModule<T>(fileManager, fileManager.type || type);\n }\n\n public getDependencyGraph(): DependencyGraph {\n return this.dependencyGraph;\n }\n\n public getInvalidationChain(file: string): string[] {\n return this.dependencyGraph.getInvalidationChain(file);\n }\n\n public getFiles(): Map<string, FileManager> {\n return this.files;\n }\n\n public getHealthCheckerManager(): FilesHealthcareManager {\n return this.healthCheckerManager;\n }\n\n /**\n * Initialise managers and register the ESM loader hook. Must be called\n * before any user `src/` module is dynamically imported.\n */\n public async init() {\n if (this.isInitialized) return;\n this.isInitialized = true;\n\n await init;\n await Promise.all([tsconfigManager.init(), packageJsonManager.init()]);\n\n // The loader hook writes itself into .warlock/, so ensure the dir exists.\n await ensureWarlockDirectory();\n\n const devServerConfig = await warlockConfigManager.lazyGet(\"devServer\");\n const transpileInit = buildTranspileInit(\n tsconfigManager.tsconfig?.compilerOptions,\n devServerConfig?.transpileCacheDebug === true,\n );\n\n this.loaderPort = await registerLoader(transpileInit);\n\n this.loaderPort.on(\"message\", (msg: { type: string }) => {\n if (msg.type === \"sync-ack\") {\n this.pendingFlushes.shift()?.();\n }\n });\n }\n\n /**\n * Tell the hook worker a file changed. The next `import()` of it will get\n * a fresh `?v=N` URL → Node cache miss → fresh content.\n */\n public bumpVersion(absolutePath: string): void {\n this.loaderPort?.postMessage({ type: \"bump\", absolutePath });\n }\n\n /**\n * Wait until the hook worker has processed every pending bump.\n *\n * `postMessage` is async — without this, a follow-up `import()` may\n * resolve before the worker increments the counter and Node will return\n * the cached old module.\n */\n public flushVersionBumps(): Promise<void> {\n if (!this.loaderPort) return Promise.resolve();\n\n return new Promise<void>((resolve) => {\n this.pendingFlushes.push(resolve);\n this.loaderPort!.postMessage({ type: \"sync\" });\n });\n }\n\n /**\n * Discover files on disk, reconcile against the manifest, build the dep\n * graph, and persist the new manifest. Idempotent.\n */\n public async initializeAll() {\n const [filesInFilesystem, manifestExists] = await Promise.all([\n this.getAllFilesFromFilesystem(),\n this.manifest.init(),\n ]);\n\n if (!manifestExists) {\n await this.processFiles(filesInFilesystem);\n } else {\n await this.reconcileFiles(filesInFilesystem);\n }\n\n this.dependencyGraph.build(this.files);\n this.fileOperations.updateFileDependents();\n this.fileOperations.syncFilesToManifest();\n await this.manifest.save();\n }\n\n public async checkHealth(files: { added: string[]; changed: string[]; deleted: string[] }) {\n const filesToCheck = [...files.added, ...files.changed]\n .map((file) => this.files.get(Path.toRelative(file)))\n .filter((file): file is FileManager => !!file);\n\n const filesToDelete = files.deleted\n .map((file) => this.files.get(Path.toRelative(file)))\n .filter((file): file is FileManager => !!file);\n\n await this.healthCheckerManager.onFileChanges(filesToCheck);\n this.healthCheckerManager.removeFiles(filesToDelete);\n this.healthCheckerManager.checkFiles(filesToCheck);\n }\n\n public async startCheckingHealth(healthCheckers?: FileHealthCheckerContract[]): Promise<void> {\n devLogDim(\"Started File Health Checks in the background.\");\n\n const checkers = healthCheckers ?? [new TypescriptHealthChecker(), new EslintHealthChecker()];\n await this.healthCheckerManager.setHealthCheckers(checkers).initialize();\n await this.healthCheckerManager.validateAllFiles();\n }\n\n /**\n * Glob the src directory, returning relative paths.\n */\n public async getAllFilesFromFilesystem(): Promise<string[]> {\n const absolutePaths = await getFilesFromDirectory();\n return absolutePaths.map((absPath) => Path.toRelative(absPath));\n }\n\n /**\n * Process every file from scratch — used when no manifest exists.\n */\n private async processFiles(filePaths: string[]) {\n devLogDim(`processing ${filePaths.length} files...`);\n await runInBatches(filePaths, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\n const fileManager = new FileManager(\n Path.toAbsolute(relativePath),\n this.files,\n this.fileOperations,\n );\n this.files.set(relativePath, fileManager);\n await fileManager.process();\n });\n devLogSuccess(`processed ${filePaths.length} files`);\n }\n\n private async reconcileFiles(filesInFilesystem: string[]) {\n const filesInManifest = new Set(this.manifest.getAllFilePaths());\n const filesInFilesystemSet = new Set(filesInFilesystem);\n\n const newFiles = filesInFilesystem.filter((f) => !filesInManifest.has(f));\n const deletedFiles = Array.from(filesInManifest).filter((f) => !filesInFilesystemSet.has(f));\n const existingFiles = filesInFilesystem.filter((f) => filesInManifest.has(f));\n\n if (newFiles.length > 0 || deletedFiles.length > 0) {\n devLogDim(\n `reconciling: ${colors.green(newFiles.length)} new, ${colors.red(deletedFiles.length)} deleted, ${colors.blue(existingFiles.length)} existing`,\n );\n }\n\n await this.processFiles(newFiles);\n\n for (const relativePath of deletedFiles) {\n this.manifest.removeFile(relativePath);\n this.files.delete(relativePath);\n }\n\n await runInBatches(existingFiles, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\n const fileManager = new FileManager(\n Path.toAbsolute(relativePath),\n this.files,\n this.fileOperations,\n );\n this.files.set(relativePath, fileManager);\n await fileManager.init(this.manifest.getFile(relativePath));\n });\n }\n\n public async watchFiles() {\n devLogSuccess(\"watching for file changes (not serving yet)\");\n\n this.filesWatcher.onFileChange((p, settleMs) => this.eventHandler.handleFileChange(p, settleMs));\n this.filesWatcher.onFileAdd((p, settleMs) => this.eventHandler.handleFileAdd(p, settleMs));\n this.filesWatcher.onFileDelete((p, settleMs) => this.eventHandler.handleFileDelete(p, settleMs));\n\n const watchConfig = warlockConfigManager.get(\"devServer\")?.watch;\n if (watchConfig?.include?.length) {\n this.eventHandler.setIncludeMatcher(createIgnoredMatcher(watchConfig.include));\n }\n\n await this.filesWatcher.watch(watchConfig);\n }\n}\n\nasync function runInBatches<T>(\n items: T[],\n size: number,\n fn: (item: T) => Promise<unknown>,\n): Promise<void> {\n if (items.length === 0) return;\n for (let i = 0; i < items.length; i += size) {\n await Promise.all(items.slice(i, i + size).map(fn));\n }\n}\n\nexport const filesOrchestrator = new FilesOrchestrator();\n"],"mappings":"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"}
1
+ {"version":3,"file":"files-orchestrator.mjs","names":[],"sources":["../../../../../../../core/src/dev-server/files-orchestrator.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { init } from \"es-module-lexer\";\nimport { fileURLToPath } from \"node:url\";\nimport type { MessagePort } from \"node:worker_threads\";\nimport { warlockConfigManager } from \"../warlock-config/warlock-config.manager\";\nimport { DependencyGraph } from \"./dependency-graph\";\nimport { devLogDim, devLogSuccess } from \"./dev-logger\";\nimport { FileEventHandler } from \"./file-event-handler\";\nimport { FileManager } from \"./file-manager\";\nimport { FileOperations } from \"./file-operations\";\nimport { FilesWatcher, createIgnoredMatcher } from \"./files-watcher\";\nimport { FILE_PROCESSING_BATCH_SIZE } from \"./flags\";\nimport { EslintHealthChecker } from \"./health-checker/checkers/eslint-health-checker\";\nimport { TypescriptHealthChecker } from \"./health-checker/checkers/typescript-health-checker\";\nimport type { FileHealthCheckerContract } from \"./health-checker/file-health-checker.contract\";\nimport { FilesHealthcareManager } from \"./health-checker/files-healthcare.manager\";\nimport { buildTranspileInit } from \"./loader/build-transpile-init.js\";\nimport { registerLoader } from \"./loader/register-loader.js\";\nimport { ManifestManager } from \"./manifest-manager\";\nimport { ModuleLoader } from \"./module-loader\";\nimport { packageJsonManager } from \"./package-json-manager\";\nimport { Path } from \"../utils/normalized-path\";\nimport { SpecialFilesCollector } from \"./special-files-collector\";\nimport { tsconfigManager } from \"./tsconfig-manager\";\nimport { ensureWarlockDirectory, getFilesFromDirectory } from \"./utils\";\n\n/**\n * Register a cleanup callback that fires before the current module is\n * unloaded by HMR. Stack-inspects the caller so user code doesn't need to\n * thread the FileManager through.\n */\nexport function onCleanup(callback: () => any) {\n if (globalThis.__currentModuleFile) {\n globalThis.__currentModuleFile.addCleanup(callback);\n return;\n }\n\n const stack = new Error().stack;\n const callerLine = stack?.split(\"\\n\")[2];\n const match = callerLine?.match(/\\((.+?):\\d+:\\d+\\)/) || callerLine?.match(/at (.+?):\\d+:\\d+/);\n const rawCaller = match?.[1];\n\n // Loaded modules report `file:///…?v=N`; turn that back into a plain path.\n const callerFile = rawCaller?.startsWith(\"file://\")\n ? fileURLToPath(rawCaller.replace(/\\?.*$/, \"\"))\n : rawCaller;\n\n if (!callerFile) return;\n const fileManager = filesOrchestrator.files.get(Path.toRelative(callerFile));\n fileManager?.addCleanup(callback);\n}\n\n/**\n * Top-level coordinator for the dev server's file system.\n *\n * Owns: file discovery, the dependency graph, the manifest, the special-files\n * index, the file watcher, and the lifecycle of the ESM loader hook (which\n * provides cache-busting via `?v=N` version tokens).\n */\nexport class FilesOrchestrator {\n public readonly filesWatcher = new FilesWatcher();\n public readonly files = new Map<string, FileManager>();\n private readonly manifest = new ManifestManager(this.files);\n private readonly dependencyGraph = new DependencyGraph();\n private readonly healthCheckerManager = new FilesHealthcareManager(this.files);\n public readonly specialFilesCollector = new SpecialFilesCollector();\n public readonly moduleLoader = new ModuleLoader(this.specialFilesCollector);\n public readonly fileOperations: FileOperations;\n private readonly eventHandler: FileEventHandler;\n\n /** Main-thread end of the MessageChannel to the loader hook worker. */\n private loaderPort: MessagePort | null = null;\n\n /** Resolve callbacks for pending `flushVersionBumps()` calls. */\n private readonly pendingFlushes: Array<() => void> = [];\n\n public isInitialized = false;\n\n public constructor() {\n this.fileOperations = new FileOperations(\n this.files,\n this.dependencyGraph,\n this.manifest,\n this.specialFilesCollector,\n );\n\n this.eventHandler = new FileEventHandler(\n this.fileOperations,\n this.manifest,\n this.dependencyGraph,\n this.files,\n );\n }\n\n public async add(relativePath: string): Promise<FileManager> {\n return this.fileOperations.addFile(relativePath);\n }\n\n public async load<T>(relativePath: string, type = \"other\") {\n // Guarantee the ESM loader hook is registered before we import anything.\n // `load()` is the dynamic-import entrypoint, so it is the one place that\n // must satisfy `init()`'s documented precondition (\"Must be called before\n // any user src/ module is dynamically imported\") on its own — it cannot\n // assume a caller ran `init()` first.\n //\n // The CLI command-resolution path proves why: `cliCommandsLoader.load()`\n // imports a project command module during command *lookup* (before the\n // command's own `execute()` runs), whereas `loadPreloaders()` only calls\n // `init()` from *inside* `execute()`. So a `warlock <command>` whose\n // command file imports an `app/*` alias or a `.ts` sibling was imported\n // with no hook registered and died on `ERR_MODULE_NOT_FOUND` (f9d8f99f).\n //\n // `init()` is idempotent (guarded by `isInitialized`), so this is a no-op\n // on the dev-server path, which already calls it explicitly at startup.\n await this.init();\n\n const fileManager = await this.add(relativePath);\n return this.moduleLoader.loadModule<T>(fileManager, fileManager.type || type);\n }\n\n public getDependencyGraph(): DependencyGraph {\n return this.dependencyGraph;\n }\n\n public getInvalidationChain(file: string): string[] {\n return this.dependencyGraph.getInvalidationChain(file);\n }\n\n public getFiles(): Map<string, FileManager> {\n return this.files;\n }\n\n public getHealthCheckerManager(): FilesHealthcareManager {\n return this.healthCheckerManager;\n }\n\n /**\n * Initialise managers and register the ESM loader hook. Must be called\n * before any user `src/` module is dynamically imported.\n */\n public async init() {\n if (this.isInitialized) return;\n this.isInitialized = true;\n\n await init;\n await Promise.all([tsconfigManager.init(), packageJsonManager.init()]);\n\n // The loader hook writes itself into .warlock/, so ensure the dir exists.\n await ensureWarlockDirectory();\n\n const devServerConfig = await warlockConfigManager.lazyGet(\"devServer\");\n const transpileInit = buildTranspileInit(\n tsconfigManager.tsconfig?.compilerOptions,\n devServerConfig?.transpileCacheDebug === true,\n );\n\n this.loaderPort = await registerLoader(transpileInit);\n\n this.loaderPort.on(\"message\", (msg: { type: string }) => {\n if (msg.type === \"sync-ack\") {\n this.pendingFlushes.shift()?.();\n }\n });\n }\n\n /**\n * Tell the hook worker a file changed. The next `import()` of it will get\n * a fresh `?v=N` URL → Node cache miss → fresh content.\n */\n public bumpVersion(absolutePath: string): void {\n this.loaderPort?.postMessage({ type: \"bump\", absolutePath });\n }\n\n /**\n * Wait until the hook worker has processed every pending bump.\n *\n * `postMessage` is async — without this, a follow-up `import()` may\n * resolve before the worker increments the counter and Node will return\n * the cached old module.\n */\n public flushVersionBumps(): Promise<void> {\n if (!this.loaderPort) return Promise.resolve();\n\n return new Promise<void>((resolve) => {\n this.pendingFlushes.push(resolve);\n this.loaderPort!.postMessage({ type: \"sync\" });\n });\n }\n\n /**\n * Discover files on disk, reconcile against the manifest, build the dep\n * graph, and persist the new manifest. Idempotent.\n */\n public async initializeAll() {\n const [filesInFilesystem, manifestExists] = await Promise.all([\n this.getAllFilesFromFilesystem(),\n this.manifest.init(),\n ]);\n\n if (!manifestExists) {\n await this.processFiles(filesInFilesystem);\n } else {\n await this.reconcileFiles(filesInFilesystem);\n }\n\n this.dependencyGraph.build(this.files);\n this.fileOperations.updateFileDependents();\n this.fileOperations.syncFilesToManifest();\n await this.manifest.save();\n }\n\n public async checkHealth(files: { added: string[]; changed: string[]; deleted: string[] }) {\n const filesToCheck = [...files.added, ...files.changed]\n .map((file) => this.files.get(Path.toRelative(file)))\n .filter((file): file is FileManager => !!file);\n\n const filesToDelete = files.deleted\n .map((file) => this.files.get(Path.toRelative(file)))\n .filter((file): file is FileManager => !!file);\n\n await this.healthCheckerManager.onFileChanges(filesToCheck);\n this.healthCheckerManager.removeFiles(filesToDelete);\n this.healthCheckerManager.checkFiles(filesToCheck);\n }\n\n public async startCheckingHealth(healthCheckers?: FileHealthCheckerContract[]): Promise<void> {\n devLogDim(\"Started File Health Checks in the background.\");\n\n const checkers = healthCheckers ?? [new TypescriptHealthChecker(), new EslintHealthChecker()];\n await this.healthCheckerManager.setHealthCheckers(checkers).initialize();\n await this.healthCheckerManager.validateAllFiles();\n }\n\n /**\n * Glob the src directory, returning relative paths.\n */\n public async getAllFilesFromFilesystem(): Promise<string[]> {\n const absolutePaths = await getFilesFromDirectory();\n return absolutePaths.map((absPath) => Path.toRelative(absPath));\n }\n\n /**\n * Process every file from scratch — used when no manifest exists.\n */\n private async processFiles(filePaths: string[]) {\n devLogDim(`processing ${filePaths.length} files...`);\n await runInBatches(filePaths, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\n const fileManager = new FileManager(\n Path.toAbsolute(relativePath),\n this.files,\n this.fileOperations,\n );\n this.files.set(relativePath, fileManager);\n await fileManager.process();\n });\n devLogSuccess(`processed ${filePaths.length} files`);\n }\n\n private async reconcileFiles(filesInFilesystem: string[]) {\n const filesInManifest = new Set(this.manifest.getAllFilePaths());\n const filesInFilesystemSet = new Set(filesInFilesystem);\n\n const newFiles = filesInFilesystem.filter((f) => !filesInManifest.has(f));\n const deletedFiles = Array.from(filesInManifest).filter((f) => !filesInFilesystemSet.has(f));\n const existingFiles = filesInFilesystem.filter((f) => filesInManifest.has(f));\n\n if (newFiles.length > 0 || deletedFiles.length > 0) {\n devLogDim(\n `reconciling: ${colors.green(newFiles.length)} new, ${colors.red(deletedFiles.length)} deleted, ${colors.blue(existingFiles.length)} existing`,\n );\n }\n\n await this.processFiles(newFiles);\n\n for (const relativePath of deletedFiles) {\n this.manifest.removeFile(relativePath);\n this.files.delete(relativePath);\n }\n\n await runInBatches(existingFiles, FILE_PROCESSING_BATCH_SIZE, async (relativePath) => {\n const fileManager = new FileManager(\n Path.toAbsolute(relativePath),\n this.files,\n this.fileOperations,\n );\n this.files.set(relativePath, fileManager);\n await fileManager.init(this.manifest.getFile(relativePath));\n });\n }\n\n public async watchFiles() {\n devLogSuccess(\"watching for file changes (not serving yet)\");\n\n this.filesWatcher.onFileChange((p, settleMs) => this.eventHandler.handleFileChange(p, settleMs));\n this.filesWatcher.onFileAdd((p, settleMs) => this.eventHandler.handleFileAdd(p, settleMs));\n this.filesWatcher.onFileDelete((p, settleMs) => this.eventHandler.handleFileDelete(p, settleMs));\n\n const watchConfig = warlockConfigManager.get(\"devServer\")?.watch;\n if (watchConfig?.include?.length) {\n this.eventHandler.setIncludeMatcher(createIgnoredMatcher(watchConfig.include));\n }\n\n await this.filesWatcher.watch(watchConfig);\n }\n}\n\nasync function runInBatches<T>(\n items: T[],\n size: number,\n fn: (item: T) => Promise<unknown>,\n): Promise<void> {\n if (items.length === 0) return;\n for (let i = 0; i < items.length; i += size) {\n await Promise.all(items.slice(i, i + size).map(fn));\n }\n}\n\nexport const filesOrchestrator = new FilesOrchestrator();\n"],"mappings":"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"}