@warlock.js/core 5.32.0 → 5.33.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 (125) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/esm/application/application-config-types.d.mts +8 -0
  3. package/esm/application/application-config-types.d.mts.map +1 -1
  4. package/esm/application/boot-signal.d.mts +8 -8
  5. package/esm/application/boot-signal.d.mts.map +1 -1
  6. package/esm/application/boot-signal.mjs +15 -9
  7. package/esm/application/boot-signal.mjs.map +1 -1
  8. package/esm/cli/commands/ai/ai-command.action.mjs +140 -0
  9. package/esm/cli/commands/ai/ai-command.action.mjs.map +1 -0
  10. package/esm/cli/commands/ai.command.mjs +50 -0
  11. package/esm/cli/commands/ai.command.mjs.map +1 -0
  12. package/esm/cli/commands/check/check-command.action.mjs +3 -0
  13. package/esm/cli/commands/check/check-command.action.mjs.map +1 -1
  14. package/esm/cli/commands/check.command.mjs +6 -1
  15. package/esm/cli/commands/check.command.mjs.map +1 -1
  16. package/esm/cli/commands/pm2/pm2-command.action.mjs +91 -0
  17. package/esm/cli/commands/pm2/pm2-command.action.mjs.map +1 -0
  18. package/esm/cli/commands/pm2/pm2-config.mjs +115 -0
  19. package/esm/cli/commands/pm2/pm2-config.mjs.map +1 -0
  20. package/esm/cli/commands/pm2.command.mjs +53 -0
  21. package/esm/cli/commands/pm2.command.mjs.map +1 -0
  22. package/esm/cli/framework-cli-commands.mjs +4 -0
  23. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  24. package/esm/connectors/connectors-manager.d.mts.map +1 -1
  25. package/esm/connectors/connectors-manager.mjs +1 -4
  26. package/esm/connectors/connectors-manager.mjs.map +1 -1
  27. package/esm/connectors/shutdown-timeout.mjs +7 -0
  28. package/esm/connectors/shutdown-timeout.mjs.map +1 -0
  29. package/esm/connectors/socket-connector.d.mts.map +1 -1
  30. package/esm/connectors/socket-connector.mjs +8 -1
  31. package/esm/connectors/socket-connector.mjs.map +1 -1
  32. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +6 -8
  33. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +1 -1
  34. package/esm/dev-server/health-checker/default-health-checkers.mjs +6 -15
  35. package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -1
  36. package/esm/dev-server/health-checker/eslint-support.mjs +27 -0
  37. package/esm/dev-server/health-checker/eslint-support.mjs.map +1 -0
  38. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +2 -1
  39. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
  40. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs +2 -11
  41. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs.map +1 -1
  42. package/esm/generations/features/index.mjs +2 -0
  43. package/esm/generations/features/index.mjs.map +1 -1
  44. package/esm/generations/features/process.feature.mjs +65 -0
  45. package/esm/generations/features/process.feature.mjs.map +1 -0
  46. package/esm/http/port-preflight.d.mts.map +1 -1
  47. package/esm/http/port-preflight.mjs +2 -0
  48. package/esm/http/port-preflight.mjs.map +1 -1
  49. package/esm/index.d.mts +7 -2
  50. package/esm/index.mjs +7 -2
  51. package/esm/secrets/errors.d.mts +9 -0
  52. package/esm/secrets/errors.d.mts.map +1 -0
  53. package/esm/secrets/errors.mjs +22 -0
  54. package/esm/secrets/errors.mjs.map +1 -0
  55. package/esm/secrets/file-backend.mjs +167 -0
  56. package/esm/secrets/file-backend.mjs.map +1 -0
  57. package/esm/secrets/index.d.mts +6 -0
  58. package/esm/secrets/index.mjs +6 -0
  59. package/esm/secrets/keyring-backend.mjs +50 -0
  60. package/esm/secrets/keyring-backend.mjs.map +1 -0
  61. package/esm/secrets/memory-backend.d.mts +12 -0
  62. package/esm/secrets/memory-backend.d.mts.map +1 -0
  63. package/esm/secrets/memory-backend.mjs +28 -0
  64. package/esm/secrets/memory-backend.mjs.map +1 -0
  65. package/esm/secrets/secret-store.d.mts +25 -0
  66. package/esm/secrets/secret-store.d.mts.map +1 -0
  67. package/esm/secrets/secret-store.mjs +117 -0
  68. package/esm/secrets/secret-store.mjs.map +1 -0
  69. package/esm/secrets/secrets.d.mts +11 -0
  70. package/esm/secrets/secrets.d.mts.map +1 -0
  71. package/esm/secrets/secrets.mjs +63 -0
  72. package/esm/secrets/secrets.mjs.map +1 -0
  73. package/esm/secrets/types.d.mts +22 -0
  74. package/esm/secrets/types.d.mts.map +1 -0
  75. package/esm/socket/index.d.mts +2 -2
  76. package/esm/socket/index.mjs +2 -2
  77. package/esm/socket/utils.d.mts +7 -0
  78. package/esm/socket/utils.d.mts.map +1 -1
  79. package/esm/socket/utils.mjs +12 -1
  80. package/esm/socket/utils.mjs.map +1 -1
  81. package/esm/tests/fakes/index.d.mts +2 -1
  82. package/esm/tests/fakes/index.mjs +2 -1
  83. package/esm/tests/fakes/secrets-fake.d.mts +13 -0
  84. package/esm/tests/fakes/secrets-fake.d.mts.map +1 -0
  85. package/esm/tests/fakes/secrets-fake.mjs +50 -0
  86. package/esm/tests/fakes/secrets-fake.mjs.map +1 -0
  87. package/esm/tests/fakes/test-fakes.d.mts +2 -2
  88. package/esm/tests/fakes/test-fakes.d.mts.map +1 -1
  89. package/esm/tests/fakes/test-fakes.mjs +5 -2
  90. package/esm/tests/fakes/test-fakes.mjs.map +1 -1
  91. package/esm/tests/index.d.mts +2 -1
  92. package/esm/tests/index.mjs +2 -1
  93. package/esm/tests/vitest-setup.d.mts +1 -1
  94. package/esm/tests/vitest-setup.mjs.map +1 -1
  95. package/esm/tooling/check/lint-step.d.mts +2 -8
  96. package/esm/tooling/check/lint-step.d.mts.map +1 -1
  97. package/esm/tooling/check/lint-step.mjs +14 -11
  98. package/esm/tooling/check/lint-step.mjs.map +1 -1
  99. package/esm/tooling/check/run-check.d.mts +2 -0
  100. package/esm/tooling/check/run-check.d.mts.map +1 -1
  101. package/esm/tooling/check/run-check.mjs +2 -2
  102. package/esm/tooling/check/run-check.mjs.map +1 -1
  103. package/esm/upgrade/codemods/index.mjs +2 -0
  104. package/esm/upgrade/codemods/index.mjs.map +1 -1
  105. package/esm/upgrade/codemods/oxc-toolchain.mjs +142 -0
  106. package/esm/upgrade/codemods/oxc-toolchain.mjs.map +1 -0
  107. package/esm/upgrade/codemods/toolchain-templates.mjs +244 -0
  108. package/esm/upgrade/codemods/toolchain-templates.mjs.map +1 -0
  109. package/esm/upgrade/run-upgrade.mjs +25 -6
  110. package/esm/upgrade/run-upgrade.mjs.map +1 -1
  111. package/esm/warlock-config/types.d.mts +2 -1
  112. package/esm/warlock-config/types.d.mts.map +1 -1
  113. package/llms-full.txt +326 -19
  114. package/llms.txt +3 -2
  115. package/package.json +23 -13
  116. package/skills/README.md +2 -1
  117. package/skills/index.md +1 -1
  118. package/skills/run-app/SKILL.md +114 -5
  119. package/skills/store-secrets/SKILL.md +102 -0
  120. package/skills/test-fakes/SKILL.md +13 -11
  121. package/skills/upgrade-from-5-29/SKILL.md +39 -2
  122. package/skills/warlock-check/SKILL.md +5 -1
  123. package/skills/warlock-doctor/SKILL.md +9 -0
  124. package/skills/wire-socket/SKILL.md +19 -0
  125. package/skills/write-cli-command/SKILL.md +21 -0
@@ -0,0 +1,115 @@
1
+ import { resolveBindPort } from "../../../http/resolve-bind-port.mjs";
2
+ import { DEFAULT_SHUTDOWN_TIMEOUT } from "../../../connectors/shutdown-timeout.mjs";
3
+ import path from "node:path";
4
+ import { writeFile } from "node:fs/promises";
5
+
6
+ //#region ../../packages/core/src/cli/commands/pm2/pm2-config.ts
7
+ /** Refuse selectors PM2 could interpret as patterns, file names or options. */
8
+ function nameComponent(value, label) {
9
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(value)) throw new Error(`${label} must contain only letters, digits, underscores and hyphens, starting with a letter or digit.`);
10
+ return value;
11
+ }
12
+ function pm2AppName(input) {
13
+ return [
14
+ nameComponent(input.project.replace(/^@([^/]+)\/([^/]+)$/, "$1-$2"), "Project name (--project)"),
15
+ nameComponent(input.app.appName ?? "app", "App name (--app or app.appName)"),
16
+ nameComponent(input.environment, "NODE_ENV")
17
+ ].join("-");
18
+ }
19
+ function positiveInteger(value, label) {
20
+ const number = typeof value === "number" ? value : /^\d+$/.test(value) ? Number(value) : NaN;
21
+ if (!Number.isSafeInteger(number) || number <= 0 || number > 2147483647) throw new Error(`${label} must be a positive integer no greater than 2147483647.`);
22
+ return number;
23
+ }
24
+ function assertClusterSockets(socket) {
25
+ if (!socket) return;
26
+ if (typeof socket.adapter !== "function") throw new Error("PM2 cluster mode with sockets requires socket.adapter for broadcasts between workers. Configure an adapter such as @socket.io/redis-adapter, or use fork mode.");
27
+ if (socket.options?.transports?.length !== 1 || socket.options.transports[0] !== "websocket") throw new Error("PM2 cluster sockets require socket.options.transports: [\"websocket\"] and WebSocket-only clients. Polling needs sticky routing to individual workers; use separate fork listeners behind a sticky proxy for polling.");
28
+ }
29
+ function readinessUrl(http) {
30
+ if (!http || http.health?.enabled === false) return void 0;
31
+ const port = resolveBindPort(http.port);
32
+ if (port === 0) return void 0;
33
+ const bindHost = http.host ?? "localhost";
34
+ const host = bindHost === "0.0.0.0" ? "127.0.0.1" : bindHost === "::" ? "::1" : bindHost;
35
+ const authority = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
36
+ const probePath = http.health?.readinessPath ?? "/ready";
37
+ if (!probePath.startsWith("/") || probePath.startsWith("//") || /[\r\n]/.test(probePath)) throw new Error("http.health.readinessPath must be a local absolute URL path.");
38
+ return `${http.serverOptions?.https ? "https" : "http"}://${authority}:${port}${probePath}`;
39
+ }
40
+ function createPm2Config(input, options = {}) {
41
+ const cluster = options.cluster === true;
42
+ if (!cluster && options.instances !== void 0) throw new Error("--instances requires --cluster; fork mode runs exactly one process.");
43
+ if (cluster) assertClusterSockets(input.socket);
44
+ const instances = cluster ? options.instances === "max" ? "max" : positiveInteger(options.instances ?? "2", "--instances") : 1;
45
+ const app = {
46
+ name: pm2AppName(input),
47
+ cwd: path.resolve(input.root),
48
+ script: path.resolve(input.root, input.entryPath),
49
+ interpreter: process.execPath,
50
+ node_args: input.sourcemap === false ? [] : ["--enable-source-maps"],
51
+ exec_mode: cluster ? "cluster" : "fork",
52
+ instances,
53
+ wait_ready: true,
54
+ listen_timeout: positiveInteger(options.listenTimeout ?? "30000", "--listen-timeout"),
55
+ kill_timeout: positiveInteger(input.app.shutdownTimeout ?? 1e4, "app.shutdownTimeout"),
56
+ time: false,
57
+ merge_logs: false,
58
+ env: { NODE_ENV: input.environment }
59
+ };
60
+ const url = readinessUrl(input.http);
61
+ return {
62
+ app,
63
+ content: [
64
+ "// Generated by warlock pm2 init. Rebuild the app before starting PM2.",
65
+ "// PM2 timeouts: https://pm2.keymetrics.io/docs/usage/signals-clean-restart/",
66
+ `// Readiness URL (external probe; PM2 does not poll it): ${JSON.stringify(url ?? "unavailable: HTTP/health disabled or ephemeral port").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")}`,
67
+ `module.exports = ${JSON.stringify({ apps: [app] }, null, 2)};`,
68
+ ""
69
+ ].join("\n"),
70
+ readinessUrl: url
71
+ };
72
+ }
73
+ async function writePm2Config(root, content) {
74
+ const target = path.resolve(root, "ecosystem.config.cjs");
75
+ await writeFile(target, content, {
76
+ encoding: "utf8",
77
+ flag: "wx"
78
+ });
79
+ return target;
80
+ }
81
+ function record(value) {
82
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
83
+ }
84
+ /** Validate the entire target set before issuing any mutating PM2 command. */
85
+ function selectPm2Targets(list, input, id) {
86
+ if (!Array.isArray(list)) throw new Error("PM2 jlist did not return a process array.");
87
+ if (id !== void 0 && (!/^\d+$/.test(id) || !Number.isSafeInteger(Number(id)))) throw new Error("--id must be a numeric PM2 id.");
88
+ const name = pm2AppName(input);
89
+ const targets = [];
90
+ for (const item of list) {
91
+ const process = record(item);
92
+ if (!process) throw new Error("Invalid PM2 process entry.");
93
+ if (id !== void 0 ? process.pm_id !== Number(id) : process.name !== name) continue;
94
+ const env = record(process.pm2_env);
95
+ if (process.name !== name || !env || env.pm_cwd !== path.resolve(input.root) || env.pm_exec_path !== path.resolve(input.root, input.entryPath)) throw new Error(`Refusing to reload PM2 id ${String(process.pm_id)}: name, working directory or bundle does not belong to this app.`);
96
+ if (typeof process.pm_id !== "number" || !Number.isSafeInteger(process.pm_id) || process.pm_id < 0) throw new Error("Invalid PM2 id in process list.");
97
+ if (env.exec_mode !== "fork_mode" && env.exec_mode !== "cluster_mode") throw new Error("Unrecognized PM2 execution mode.");
98
+ const cluster = env.exec_mode === "cluster_mode";
99
+ if (cluster) {
100
+ assertClusterSockets(input.socket);
101
+ if (env.wait_ready !== true || env.status !== "online") throw new Error("Rolling reload requires an online cluster worker with wait_ready: true. Check its PM2 configuration and status.");
102
+ }
103
+ targets.push({
104
+ id: process.pm_id,
105
+ cluster
106
+ });
107
+ }
108
+ if (targets.length === 0) throw new Error(`No PM2 process belonging to ${name} was found. Start its ecosystem file first.`);
109
+ if (new Set(targets.map((target) => target.id)).size !== targets.length) throw new Error("Duplicate PM2 ids in process list.");
110
+ return targets;
111
+ }
112
+
113
+ //#endregion
114
+ export { assertClusterSockets, createPm2Config, pm2AppName, selectPm2Targets, writePm2Config };
115
+ //# sourceMappingURL=pm2-config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pm2-config.mjs","names":[],"sources":["../../../../../../../../../../packages/core/src/cli/commands/pm2/pm2-config.ts"],"sourcesContent":["import path from \"node:path\";\nimport { writeFile } from \"node:fs/promises\";\nimport type { AppConfigurations } from \"../../../application/application-config-types\";\nimport { DEFAULT_SHUTDOWN_TIMEOUT } from \"../../../connectors/shutdown-timeout\";\nimport { resolveBindPort } from \"../../../http/resolve-bind-port\";\nimport type { HttpConfigurations } from \"../../../http/types\";\nimport type { SocketOptions } from \"../../../socket/types\";\nimport type { ResolvedBuildConfig } from \"../../../production/resolve-build-config\";\n\nexport type Pm2AppInput = {\n root: string;\n project: string;\n app: AppConfigurations;\n environment: string;\n entryPath: string;\n sourcemap: ResolvedBuildConfig[\"sourcemap\"];\n http?: HttpConfigurations;\n socket?: SocketOptions;\n};\n\nexport type Pm2InitOptions = {\n cluster?: boolean;\n instances?: string;\n listenTimeout?: string;\n};\n\n/** Refuse selectors PM2 could interpret as patterns, file names or options. */\nfunction nameComponent(value: string, label: string): string {\n if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(value)) {\n throw new Error(\n `${label} must contain only letters, digits, underscores and hyphens, starting with a letter or digit.`,\n );\n }\n return value;\n}\n\nexport function pm2AppName(input: Pick<Pm2AppInput, \"project\" | \"app\" | \"environment\">): string {\n // Preserve the npm scope in the project identity instead of dropping it.\n const project = input.project.replace(/^@([^/]+)\\/([^/]+)$/, \"$1-$2\");\n return [\n nameComponent(project, \"Project name (--project)\"),\n nameComponent(input.app.appName ?? \"app\", \"App name (--app or app.appName)\"),\n nameComponent(input.environment, \"NODE_ENV\"),\n ].join(\"-\");\n}\n\nfunction positiveInteger(value: number | string, label: string): number {\n const number = typeof value === \"number\" ? value : /^\\d+$/.test(value) ? Number(value) : NaN;\n if (!Number.isSafeInteger(number) || number <= 0 || number > 2_147_483_647) {\n throw new Error(`${label} must be a positive integer no greater than 2147483647.`);\n }\n return number;\n}\n\nexport function assertClusterSockets(socket?: SocketOptions): void {\n if (!socket) return;\n if (typeof socket.adapter !== \"function\") {\n throw new Error(\n \"PM2 cluster mode with sockets requires socket.adapter for broadcasts between workers. Configure an adapter such as @socket.io/redis-adapter, or use fork mode.\",\n );\n }\n // A proxy sticky to a shared PM2 port is not sticky to its workers. We do\n // not install primary-process routing. Fail closed for long-polling.\n // https://socket.io/docs/v4/using-multiple-nodes/\n if (socket.options?.transports?.length !== 1 || socket.options.transports[0] !== \"websocket\") {\n throw new Error(\n 'PM2 cluster sockets require socket.options.transports: [\"websocket\"] and WebSocket-only clients. Polling needs sticky routing to individual workers; use separate fork listeners behind a sticky proxy for polling.',\n );\n }\n}\n\nfunction readinessUrl(http?: HttpConfigurations): string | undefined {\n if (!http || http.health?.enabled === false) return undefined;\n const port = resolveBindPort(http.port);\n if (port === 0) return undefined;\n const bindHost = http.host ?? \"localhost\";\n const host = bindHost === \"0.0.0.0\" ? \"127.0.0.1\" : bindHost === \"::\" ? \"::1\" : bindHost;\n const authority = host.includes(\":\") && !host.startsWith(\"[\") ? `[${host}]` : host;\n const probePath = http.health?.readinessPath ?? \"/ready\";\n if (!probePath.startsWith(\"/\") || probePath.startsWith(\"//\") || /[\\r\\n]/.test(probePath)) {\n throw new Error(\"http.health.readinessPath must be a local absolute URL path.\");\n }\n return `${http.serverOptions?.https ? \"https\" : \"http\"}://${authority}:${port}${probePath}`;\n}\n\nexport function createPm2Config(input: Pm2AppInput, options: Pm2InitOptions = {}) {\n const cluster = options.cluster === true;\n if (!cluster && options.instances !== undefined) {\n throw new Error(\"--instances requires --cluster; fork mode runs exactly one process.\");\n }\n if (cluster) assertClusterSockets(input.socket);\n const instances = cluster\n ? options.instances === \"max\"\n ? \"max\"\n : positiveInteger(options.instances ?? \"2\", \"--instances\")\n : 1;\n const app = {\n name: pm2AppName(input),\n cwd: path.resolve(input.root),\n // Run the bundle itself, not the `warlock start` child supervisor: PM2\n // must own the process that listens and sends its readiness message.\n script: path.resolve(input.root, input.entryPath),\n interpreter: process.execPath,\n node_args: input.sourcemap === false ? [] : [\"--enable-source-maps\"],\n exec_mode: cluster ? \"cluster\" : \"fork\",\n instances,\n wait_ready: true,\n listen_timeout: positiveInteger(options.listenTimeout ?? \"30000\", \"--listen-timeout\"),\n kill_timeout: positiveInteger(\n input.app.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT,\n \"app.shutdownTimeout\",\n ),\n // Keep @warlock.js/logger messages intact; leave per-worker log suffixes.\n time: false,\n merge_logs: false,\n env: { NODE_ENV: input.environment },\n };\n const url = readinessUrl(input.http);\n // JSON encoding keeps project/config strings from becoming executable code\n // or escaping a comment in this CommonJS file.\n const content = [\n \"// Generated by warlock pm2 init. Rebuild the app before starting PM2.\",\n \"// PM2 timeouts: https://pm2.keymetrics.io/docs/usage/signals-clean-restart/\",\n `// Readiness URL (external probe; PM2 does not poll it): ${JSON.stringify(\n url ?? \"unavailable: HTTP/health disabled or ephemeral port\",\n )\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\")}`,\n `module.exports = ${JSON.stringify({ apps: [app] }, null, 2)};`,\n \"\",\n ].join(\"\\n\");\n return { app, content, readinessUrl: url };\n}\n\nexport async function writePm2Config(root: string, content: string): Promise<string> {\n const target = path.resolve(root, \"ecosystem.config.cjs\");\n // Atomic create: an existing/customized ecosystem is never overwritten.\n await writeFile(target, content, { encoding: \"utf8\", flag: \"wx\" });\n return target;\n}\n\nexport type Pm2Target = { id: number; cluster: boolean };\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\n/** Validate the entire target set before issuing any mutating PM2 command. */\nexport function selectPm2Targets(list: unknown, input: Pm2AppInput, id?: string): Pm2Target[] {\n if (!Array.isArray(list)) throw new Error(\"PM2 jlist did not return a process array.\");\n if (id !== undefined && (!/^\\d+$/.test(id) || !Number.isSafeInteger(Number(id)))) {\n throw new Error(\"--id must be a numeric PM2 id.\");\n }\n const name = pm2AppName(input);\n const targets: Pm2Target[] = [];\n for (const item of list) {\n const process = record(item);\n if (!process) throw new Error(\"Invalid PM2 process entry.\");\n if (id !== undefined ? process.pm_id !== Number(id) : process.name !== name) continue;\n const env = record(process.pm2_env);\n if (\n process.name !== name ||\n !env ||\n env.pm_cwd !== path.resolve(input.root) ||\n env.pm_exec_path !== path.resolve(input.root, input.entryPath)\n ) {\n throw new Error(\n `Refusing to reload PM2 id ${String(process.pm_id)}: name, working directory or bundle does not belong to this app.`,\n );\n }\n if (\n typeof process.pm_id !== \"number\" ||\n !Number.isSafeInteger(process.pm_id) ||\n process.pm_id < 0\n ) {\n throw new Error(\"Invalid PM2 id in process list.\");\n }\n if (env.exec_mode !== \"fork_mode\" && env.exec_mode !== \"cluster_mode\") {\n throw new Error(\"Unrecognized PM2 execution mode.\");\n }\n const cluster = env.exec_mode === \"cluster_mode\";\n if (cluster) {\n assertClusterSockets(input.socket);\n if (env.wait_ready !== true || env.status !== \"online\") {\n throw new Error(\n \"Rolling reload requires an online cluster worker with wait_ready: true. Check its PM2 configuration and status.\",\n );\n }\n }\n targets.push({ id: process.pm_id, cluster });\n }\n if (targets.length === 0)\n throw new Error(\n `No PM2 process belonging to ${name} was found. Start its ecosystem file first.`,\n );\n if (new Set(targets.map((target) => target.id)).size !== targets.length) {\n throw new Error(\"Duplicate PM2 ids in process list.\");\n }\n return targets;\n}\n"],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { command } from "../../commands/cli-command.mjs";
2
+ import { pm2CommandAction } from "./pm2/pm2-command.action.mjs";
3
+
4
+ //#region ../../packages/core/src/cli/commands/pm2.command.ts
5
+ const pm2Command = command({
6
+ name: "pm2 <action>",
7
+ description: "Optional PM2 tooling: init an ecosystem file or reload only this app",
8
+ preload: {
9
+ warlockConfig: true,
10
+ config: [
11
+ "app",
12
+ "http",
13
+ "socket"
14
+ ]
15
+ },
16
+ action: pm2CommandAction,
17
+ options: [
18
+ {
19
+ text: "--project",
20
+ type: "string",
21
+ description: "Project name (default: package.json name)"
22
+ },
23
+ {
24
+ text: "--app",
25
+ type: "string",
26
+ description: "App name (default: app.appName or app)"
27
+ },
28
+ {
29
+ text: "--cluster",
30
+ type: "boolean",
31
+ description: "init: use cluster mode (default: fork)"
32
+ },
33
+ {
34
+ text: "--instances",
35
+ type: "string",
36
+ description: "init with --cluster: worker count or max (default: 2)"
37
+ },
38
+ {
39
+ text: "--listen-timeout",
40
+ type: "string",
41
+ description: "init: readiness timeout in ms (default: 30000)"
42
+ },
43
+ {
44
+ text: "--id",
45
+ type: "string",
46
+ description: "reload: one PM2 id, verified to belong to this app"
47
+ }
48
+ ]
49
+ });
50
+
51
+ //#endregion
52
+ export { pm2Command };
53
+ //# sourceMappingURL=pm2.command.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pm2.command.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/cli/commands/pm2.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\nimport { pm2CommandAction } from \"./pm2/pm2-command.action\";\n\nexport const pm2Command = command({\n name: \"pm2 <action>\",\n description: \"Optional PM2 tooling: init an ecosystem file or reload only this app\",\n // Read config without running bootstrap, prestart or any connector.\n preload: { warlockConfig: true, config: [\"app\", \"http\", \"socket\"] },\n action: pm2CommandAction,\n options: [\n { text: \"--project\", type: \"string\", description: \"Project name (default: package.json name)\" },\n { text: \"--app\", type: \"string\", description: \"App name (default: app.appName or app)\" },\n { text: \"--cluster\", type: \"boolean\", description: \"init: use cluster mode (default: fork)\" },\n {\n text: \"--instances\",\n type: \"string\",\n description: \"init with --cluster: worker count or max (default: 2)\",\n },\n {\n text: \"--listen-timeout\",\n type: \"string\",\n description: \"init: readiness timeout in ms (default: 30000)\",\n },\n {\n text: \"--id\",\n type: \"string\",\n description: \"reload: one PM2 id, verified to belong to this app\",\n },\n ],\n});\n"],"mappings":";;;;AAGA,MAAa,aAAa,QAAQ;CAChC,MAAM;CACN,aAAa;CAEb,SAAS;EAAE,eAAe;EAAM,QAAQ;GAAC;GAAO;GAAQ;EAAQ;CAAE;CAClE,QAAQ;CACR,SAAS;EACP;GAAE,MAAM;GAAa,MAAM;GAAU,aAAa;EAA4C;EAC9F;GAAE,MAAM;GAAS,MAAM;GAAU,aAAa;EAAyC;EACvF;GAAE,MAAM;GAAa,MAAM;GAAW,aAAa;EAAyC;EAC5F;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;EACA;GACE,MAAM;GACN,MAAM;GACN,aAAa;EACf;CACF;AACF,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { aiCommand } from "./commands/ai.command.mjs";
1
2
  import { addCommand } from "./commands/add.command.mjs";
2
3
  import { agentsMdCommand } from "./commands/agents-md.command.mjs";
3
4
  import { boardCommand } from "./commands/board.command.mjs";
@@ -15,6 +16,7 @@ import { mcpCommand } from "./commands/mcp.command.mjs";
15
16
  import { migrateCommand } from "./commands/migrate.command.mjs";
16
17
  import { openApiGeneratorCommand } from "./commands/openapi-generator.command.mjs";
17
18
  import { postmanGeneratorCommand } from "./commands/postman-generator.command.mjs";
19
+ import { pm2Command } from "./commands/pm2.command.mjs";
18
20
  import { routesCommand, routesDiffCommand } from "./commands/routes.command.mjs";
19
21
  import { seedCommand } from "./commands/seed.command.mjs";
20
22
  import { startProductionCommand } from "./commands/start-production.command.mjs";
@@ -33,6 +35,7 @@ const frameworkCommands = [
33
35
  openApiGeneratorCommand,
34
36
  postmanGeneratorCommand,
35
37
  testCommand,
38
+ aiCommand,
36
39
  doctorCommand,
37
40
  routesCommand,
38
41
  routesDiffCommand,
@@ -47,6 +50,7 @@ const frameworkCommands = [
47
50
  boardCommand,
48
51
  buildCommand,
49
52
  startProductionCommand,
53
+ pm2Command,
50
54
  migrateCommand,
51
55
  seedCommand,
52
56
  createDatabaseCommand,
@@ -1 +1 @@
1
- {"version":3,"file":"framework-cli-commands.mjs","names":[],"sources":["../../../../../../../../packages/core/src/cli/framework-cli-commands.ts"],"sourcesContent":["import { addCommand } from \"./commands/add.command\";\nimport { agentsMdCommand } from \"./commands/agents-md.command\";\nimport { boardCommand } from \"./commands/board.command\";\nimport { buildCommand } from \"./commands/build.command\";\nimport { checkCommand } from \"./commands/check.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 { explainCommand } from \"./commands/explain.command\";\nimport {\n generateCommand,\n generateControllerCommand,\n generateEndpointCommand,\n generateMigrationCommand,\n generateModelCommand,\n generateModuleCommand,\n generateRepositoryCommand,\n generateResourceCommand,\n generateServiceCommand,\n generateUseCaseCommand,\n makeCommand,\n} from \"./commands/generate/generate.command\";\nimport { inspectCommand } from \"./commands/inspect.command\";\nimport { issueCommand } from \"./commands/issue.command\";\nimport { mcpCommand } from \"./commands/mcp.command\";\nimport { migrateCommand } from \"./commands/migrate.command\";\nimport { openApiGeneratorCommand } from \"./commands/openapi-generator.command\";\nimport { postmanGeneratorCommand } from \"./commands/postman-generator.command\";\nimport { routesCommand, routesDiffCommand } from \"./commands/routes.command\";\nimport { seedCommand } from \"./commands/seed.command\";\nimport { startProductionCommand } from \"./commands/start-production.command\";\nimport { traceCommand } from \"./commands/trace.command\";\nimport { storagePutCommand } from \"./commands/storage-put.command\";\nimport { typecheckCommand } from \"./commands/typecheck.command\";\nimport { testCommand } from \"./commands/test.command\";\nimport { typingsGeneratorCommand } from \"./commands/typings-generator.command\";\nimport { updateCommand } from \"./commands/update.command\";\nimport { upgradeCommand } from \"./commands/upgrade.command\";\n\nexport const frameworkCommands = [\n // development commands\n devServerCommand,\n typingsGeneratorCommand,\n openApiGeneratorCommand,\n postmanGeneratorCommand,\n\n // testing commands\n testCommand,\n\n // diagnostics commands\n doctorCommand,\n routesCommand,\n routesDiffCommand,\n inspectCommand,\n explainCommand,\n issueCommand,\n checkCommand,\n typecheckCommand,\n\n // agent tooling commands\n mcpCommand,\n traceCommand,\n agentsMdCommand,\n boardCommand,\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 upgradeCommand,\n\n // scaffolding commands\n generateCommand,\n makeCommand,\n generateEndpointCommand,\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAa,oBAAoB;CAE/B;CACA;CACA;CACA;CAGA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF"}
1
+ {"version":3,"file":"framework-cli-commands.mjs","names":[],"sources":["../../../../../../../../packages/core/src/cli/framework-cli-commands.ts"],"sourcesContent":["import { aiCommand } from \"./commands/ai.command\";\nimport { addCommand } from \"./commands/add.command\";\nimport { agentsMdCommand } from \"./commands/agents-md.command\";\nimport { boardCommand } from \"./commands/board.command\";\nimport { buildCommand } from \"./commands/build.command\";\nimport { checkCommand } from \"./commands/check.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 { explainCommand } from \"./commands/explain.command\";\nimport {\n generateCommand,\n generateControllerCommand,\n generateEndpointCommand,\n generateMigrationCommand,\n generateModelCommand,\n generateModuleCommand,\n generateRepositoryCommand,\n generateResourceCommand,\n generateServiceCommand,\n generateUseCaseCommand,\n makeCommand,\n} from \"./commands/generate/generate.command\";\nimport { inspectCommand } from \"./commands/inspect.command\";\nimport { issueCommand } from \"./commands/issue.command\";\nimport { mcpCommand } from \"./commands/mcp.command\";\nimport { migrateCommand } from \"./commands/migrate.command\";\nimport { openApiGeneratorCommand } from \"./commands/openapi-generator.command\";\nimport { postmanGeneratorCommand } from \"./commands/postman-generator.command\";\nimport { pm2Command } from \"./commands/pm2.command\";\nimport { routesCommand, routesDiffCommand } from \"./commands/routes.command\";\nimport { seedCommand } from \"./commands/seed.command\";\nimport { startProductionCommand } from \"./commands/start-production.command\";\nimport { traceCommand } from \"./commands/trace.command\";\nimport { storagePutCommand } from \"./commands/storage-put.command\";\nimport { typecheckCommand } from \"./commands/typecheck.command\";\nimport { testCommand } from \"./commands/test.command\";\nimport { typingsGeneratorCommand } from \"./commands/typings-generator.command\";\nimport { updateCommand } from \"./commands/update.command\";\nimport { upgradeCommand } from \"./commands/upgrade.command\";\n\nexport const frameworkCommands = [\n // development commands\n devServerCommand,\n typingsGeneratorCommand,\n openApiGeneratorCommand,\n postmanGeneratorCommand,\n\n // testing commands\n testCommand,\n\n // diagnostics commands\n aiCommand,\n doctorCommand,\n routesCommand,\n routesDiffCommand,\n inspectCommand,\n explainCommand,\n issueCommand,\n checkCommand,\n typecheckCommand,\n\n // agent tooling commands\n mcpCommand,\n traceCommand,\n agentsMdCommand,\n boardCommand,\n\n // production commands\n buildCommand,\n startProductionCommand,\n pm2Command,\n\n // database commands\n migrateCommand,\n seedCommand,\n createDatabaseCommand,\n dropTablesCommand,\n\n // generation/installation commands\n addCommand,\n updateCommand,\n upgradeCommand,\n\n // scaffolding commands\n generateCommand,\n makeCommand,\n generateEndpointCommand,\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,oBAAoB;CAE/B;CACA;CACA;CACA;CAGA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACA;CACA;CAGA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"connectors-manager.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/connectors/connectors-manager.ts"],"mappings":";;qBAsBa;;;;mBAIM;;;;;;;;;;;;;mBAcA;;;;EAKjB;;;;;;;;UAqBQ;;;;;;;;;EAgBD,cAAc;;;;EAOd,YAAY,YAAY;;;;EASxB,QAAQ;;;;;;;;EAWR,IAAI,MAAM;;;;EAOJ,MAAM,kBAAkB,kBAAkB;;;;;;;;;;EAuB1C,WAAW,OAAO,0BAA0B;;;;EAiB5C,aAAa,oBAAoB,kBAAkB;;;;EAWnD,YAAY;;;;UA8CX;;;;;;;;;;;;;EAgCP;;qBAyBI,mBAAiB"}
1
+ {"version":3,"file":"connectors-manager.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/connectors/connectors-manager.ts"],"mappings":";;qBAkBa;;;;mBAIM;;;;;;;;;;;;;mBAcA;;;;EAKjB;;;;;;;;UAqBQ;;;;;;;;;EAgBD,cAAc;;;;EAOd,YAAY,YAAY;;;;EASxB,QAAQ;;;;;;;;EAWR,IAAI,MAAM;;;;EAOJ,MAAM,kBAAkB,kBAAkB;;;;;;;;;;EAuB1C,WAAW,OAAO,0BAA0B;;;;EAiB5C,aAAa,oBAAoB,kBAAkB;;;;EAWnD,YAAY;;;;UA8CX;;;;;;;;;;;;;EAgCP;;qBAyBI,mBAAiB"}
@@ -11,14 +11,11 @@ import { MailerConnector } from "./mail-connector.mjs";
11
11
  import { NotificationsConnector } from "./notifications-connector.mjs";
12
12
  import { SocketConnector } from "./socket-connector.mjs";
13
13
  import { StorageConnector } from "./storage.connector.mjs";
14
+ import { DEFAULT_SHUTDOWN_TIMEOUT } from "./shutdown-timeout.mjs";
14
15
  import baseConfig from "@mongez/config";
15
16
  import { log } from "@warlock.js/logger";
16
17
 
17
18
  //#region ../../packages/core/src/connectors/connectors-manager.ts
18
- /**
19
- * Overall shutdown budget in ms. Override via `app.shutdownTimeout`.
20
- */
21
- const DEFAULT_SHUTDOWN_TIMEOUT = 1e4;
22
19
  var ConnectorsManager = class {
23
20
  /**
24
21
  * Constructor
@@ -1 +1 @@
1
- {"version":3,"file":"connectors-manager.mjs","names":["config"],"sources":["../../../../../../../../packages/core/src/connectors/connectors-manager.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { Application } from \"../application\";\nimport { log } from \"@warlock.js/logger\";\nimport { AccessConnector } from \"./access-connector\";\nimport { AiConnector } from \"./ai-connector\";\nimport { CacheConnector } from \"./cache-connector\";\nimport { DatabaseConnector } from \"./database-connector\";\nimport { HeraldConnector } from \"./herald-connector\";\nimport { HttpConnector } from \"./http-connector\";\nimport { LoggerConnector } from \"./logger-connector\";\nimport { MailerConnector } from \"./mail-connector\";\nimport { NotificationsConnector } from \"./notifications-connector\";\nimport { SocketConnector } from \"./socket-connector\";\nimport { StorageConnector } from \"./storage.connector\";\nimport { ConnectorLifecyclePhase } from \"./types\";\nimport type { Connector, ConnectorName } from \"./types\";\n\n/**\n * Overall shutdown budget in ms. Override via `app.shutdownTimeout`.\n */\nconst DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\n\nexport class ConnectorsManager {\n /**\n * Connectors list\n */\n private readonly connectors: Connector[] = [];\n\n /**\n * The names the framework's own connectors hold.\n *\n * Filled by the constructor's own registration calls rather than declared\n * beside them, so there is exactly ONE place a built-in name exists: the\n * connector that owns it. A second list — in the builder, in a config\n * validator, in a test double — is a list that drifts the moment a built-in\n * is added or renamed, and it drifts silently, because nothing compares the\n * two. Both halves of a connector's life read this one through\n * {@link isBuiltInName}: the build refuses a `connectors` array that claims\n * a built-in name, and so does registration.\n */\n private readonly builtInNames = new Set<string>();\n\n /**\n * Constructor\n */\n public constructor() {\n this.registerBuiltIn(new LoggerConnector());\n this.registerBuiltIn(new MailerConnector());\n this.registerBuiltIn(new HttpConnector());\n this.registerBuiltIn(new DatabaseConnector());\n this.registerBuiltIn(new HeraldConnector());\n this.registerBuiltIn(new CacheConnector());\n this.registerBuiltIn(new StorageConnector());\n this.registerBuiltIn(new SocketConnector());\n this.registerBuiltIn(new NotificationsConnector());\n this.registerBuiltIn(new AccessConnector());\n this.registerBuiltIn(new AiConnector());\n }\n\n /**\n * Register a framework-owned connector and reserve its name.\n *\n * Private on purpose: an app connector must not be able to reserve a name,\n * and the set is only correct if every entry in it came from a connector\n * this class constructed itself.\n */\n private registerBuiltIn(...connectors: Connector[]): void {\n for (const connector of connectors) {\n this.builtInNames.add(connector.name);\n }\n\n this.register(...connectors);\n }\n\n /**\n * Whether the given name belongs to a framework built-in connector.\n *\n * Reserved, not merely taken: a configured connector carrying one of these\n * names would be skipped by {@link has} at boot while the build had already\n * drained its contribution, so the artifact would carry a connector the\n * server never runs. Callers refuse the name instead of skipping it.\n */\n public isBuiltInName(name: string): boolean {\n return this.builtInNames.has(name);\n }\n\n /**\n * Register a connector\n */\n public register(...connectors: Connector[]): void {\n this.connectors.push(...connectors);\n // sort connectors by priority\n this.connectors.sort((a, b) => a.priority - b.priority);\n }\n\n /**\n * Get all connectors\n */\n public list(): Connector[] {\n return this.connectors;\n }\n\n /**\n * Whether a connector with the given name is already registered.\n *\n * Names are the identity of a connector here, which is what lets a caller\n * that may run more than once per process register idempotently instead of\n * stacking duplicates that would each boot and shut down.\n */\n public has(name: ConnectorName): boolean {\n return this.connectors.some((connector) => connector.name === name);\n }\n\n /**\n * start all connectors\n */\n public async start(connectorsNames?: ConnectorName[]): Promise<void> {\n const connectorsList = connectorsNames\n ? this.connectors.filter((connector) => connectorsNames.includes(connector.name))\n : this.connectors;\n\n for (const connector of connectorsList) {\n await connector.boot();\n }\n\n for (const connector of connectorsList) {\n await connector.start();\n }\n }\n\n /**\n * Start all connectors in the given lifecycle phase.\n *\n * The production builder and dev preload split startup around app\n * code: early phase before app imports, late phase after. Within a\n * phase, all connectors `boot()` first, then all `start()`, so\n * cross-connector wiring inside the phase still works (e.g. socket\n * reads http's instance during its own boot).\n */\n public async startPhase(phase: ConnectorLifecyclePhase): Promise<void> {\n const phaseConnectors = this.connectors.filter(\n (connector) => connector.lifecyclePhase === phase,\n );\n\n for (const connector of phaseConnectors) {\n await connector.boot();\n }\n\n for (const connector of phaseConnectors) {\n await connector.start();\n }\n }\n\n /**\n * Start all connectors except the given ones\n */\n public async startWithout(excludedConnectors: ConnectorName[]): Promise<void> {\n await this.start(\n this.connectors\n .filter((connector) => !excludedConnectors.includes(connector.name))\n .map((connector) => connector.name),\n );\n }\n\n /**\n * Shutdown all connectors\n */\n public async shutdown(): Promise<void> {\n // App-owned teardown runs first, while every connector (db, cache, http) is\n // still up, so cleanup hooks can use them. Idempotent + error-isolated.\n await Application.runShutdownHooks();\n\n // HTTP stops accepting and drains in-flight requests FIRST, while storage,\n // socket, etc. are still up; then the rest go in reverse priority. Copy the\n // list first — `reverse()` mutates in place, so reversing the live array\n // would corrupt the order on a second shutdown pass.\n const reversed = [...this.connectors].reverse();\n const ordered = [\n ...reversed.filter((connector) => connector.name === \"http\"),\n ...reversed.filter((connector) => connector.name !== \"http\"),\n ];\n\n const timeout = config.get(\"app.shutdownTimeout\", DEFAULT_SHUTDOWN_TIMEOUT);\n let current: string | undefined;\n\n const timer = setTimeout(() => {\n // Sync write: the process exits right after, so an async log would be lost.\n console.error(\n `Shutdown timed out after ${timeout}ms; connector still shutting down: ${current ?? \"none\"}`,\n );\n process.exit(1);\n }, timeout);\n timer.unref?.();\n\n try {\n for (const connector of ordered) {\n current = connector.name;\n await this.shutdownOne(connector);\n }\n current = undefined;\n } finally {\n clearTimeout(timer);\n }\n\n // Log calls return before an async channel (file, Sentry) has written the\n // entry, so a channel would lose it when the process exits; `flush()` waits\n // for those deliveries.\n await log.flush();\n }\n\n /**\n * Shut one connector down, isolating and reporting its failure.\n */\n private async shutdownOne(connector: Connector): Promise<void> {\n try {\n await connector.shutdown();\n } catch (error) {\n try {\n // Synchronous: the entry is handed to every channel before this\n // returns. `shutdown()` then flushes async channels before resolving,\n // because the caller (`shutdownOnProcessKill`) calls `process.exit(0)`\n // as soon as it does.\n log.error(\"connectors\", \"shutdown\", error as Error, { connector: connector.name });\n } catch {\n // Reporting a failure must never become a worse failure than the one\n // being reported: anything thrown here (a redact rule, say) would\n // otherwise abort the remaining teardown. Swallowed\n // rather than re-reported: the only channel we could report it through\n // is the one that just threw.\n }\n }\n }\n\n /**\n * Shutdown connectors on process kill\n *\n * Registers signal handlers that run an idempotent graceful shutdown:\n * - SIGINT: Ctrl+C on Unix, also caught on Windows but unreliable in child processes\n * - SIGTERM: Termination signal (Unix primarily)\n * - SIGHUP: registered on Windows (`win32`) only, to catch Ctrl+C / console close\n *\n * Note: no `beforeExit` handler is registered — `beforeExit` cannot reliably\n * await async teardown (the process exits once the callback returns), so\n * shutdown is driven entirely by the signal handlers above.\n */\n public shutdownOnProcessKill(): void {\n let isShuttingDown = false;\n\n const gracefulShutdown = async (signal: string) => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n\n console.log(`\\nExiting...`);\n await this.shutdown();\n process.exit(0);\n };\n\n // Unix signals\n process.on(\"SIGINT\", () => gracefulShutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => gracefulShutdown(\"SIGTERM\"));\n\n // Windows-specific: handle when process is about to exit\n // This is more reliable on Windows for spawned child processes\n if (process.platform === \"win32\") {\n // Handle Ctrl+C on Windows specifically\n process.on(\"SIGHUP\", () => gracefulShutdown(\"SIGHUP\"));\n }\n }\n}\n\nexport const connectorsManager = new ConnectorsManager();\n"],"mappings":"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"}
1
+ {"version":3,"file":"connectors-manager.mjs","names":["config"],"sources":["../../../../../../../../packages/core/src/connectors/connectors-manager.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { Application } from \"../application\";\nimport { log } from \"@warlock.js/logger\";\nimport { AccessConnector } from \"./access-connector\";\nimport { AiConnector } from \"./ai-connector\";\nimport { CacheConnector } from \"./cache-connector\";\nimport { DatabaseConnector } from \"./database-connector\";\nimport { HeraldConnector } from \"./herald-connector\";\nimport { HttpConnector } from \"./http-connector\";\nimport { LoggerConnector } from \"./logger-connector\";\nimport { MailerConnector } from \"./mail-connector\";\nimport { NotificationsConnector } from \"./notifications-connector\";\nimport { SocketConnector } from \"./socket-connector\";\nimport { StorageConnector } from \"./storage.connector\";\nimport { DEFAULT_SHUTDOWN_TIMEOUT } from \"./shutdown-timeout\";\nimport { ConnectorLifecyclePhase } from \"./types\";\nimport type { Connector, ConnectorName } from \"./types\";\n\nexport class ConnectorsManager {\n /**\n * Connectors list\n */\n private readonly connectors: Connector[] = [];\n\n /**\n * The names the framework's own connectors hold.\n *\n * Filled by the constructor's own registration calls rather than declared\n * beside them, so there is exactly ONE place a built-in name exists: the\n * connector that owns it. A second list — in the builder, in a config\n * validator, in a test double — is a list that drifts the moment a built-in\n * is added or renamed, and it drifts silently, because nothing compares the\n * two. Both halves of a connector's life read this one through\n * {@link isBuiltInName}: the build refuses a `connectors` array that claims\n * a built-in name, and so does registration.\n */\n private readonly builtInNames = new Set<string>();\n\n /**\n * Constructor\n */\n public constructor() {\n this.registerBuiltIn(new LoggerConnector());\n this.registerBuiltIn(new MailerConnector());\n this.registerBuiltIn(new HttpConnector());\n this.registerBuiltIn(new DatabaseConnector());\n this.registerBuiltIn(new HeraldConnector());\n this.registerBuiltIn(new CacheConnector());\n this.registerBuiltIn(new StorageConnector());\n this.registerBuiltIn(new SocketConnector());\n this.registerBuiltIn(new NotificationsConnector());\n this.registerBuiltIn(new AccessConnector());\n this.registerBuiltIn(new AiConnector());\n }\n\n /**\n * Register a framework-owned connector and reserve its name.\n *\n * Private on purpose: an app connector must not be able to reserve a name,\n * and the set is only correct if every entry in it came from a connector\n * this class constructed itself.\n */\n private registerBuiltIn(...connectors: Connector[]): void {\n for (const connector of connectors) {\n this.builtInNames.add(connector.name);\n }\n\n this.register(...connectors);\n }\n\n /**\n * Whether the given name belongs to a framework built-in connector.\n *\n * Reserved, not merely taken: a configured connector carrying one of these\n * names would be skipped by {@link has} at boot while the build had already\n * drained its contribution, so the artifact would carry a connector the\n * server never runs. Callers refuse the name instead of skipping it.\n */\n public isBuiltInName(name: string): boolean {\n return this.builtInNames.has(name);\n }\n\n /**\n * Register a connector\n */\n public register(...connectors: Connector[]): void {\n this.connectors.push(...connectors);\n // sort connectors by priority\n this.connectors.sort((a, b) => a.priority - b.priority);\n }\n\n /**\n * Get all connectors\n */\n public list(): Connector[] {\n return this.connectors;\n }\n\n /**\n * Whether a connector with the given name is already registered.\n *\n * Names are the identity of a connector here, which is what lets a caller\n * that may run more than once per process register idempotently instead of\n * stacking duplicates that would each boot and shut down.\n */\n public has(name: ConnectorName): boolean {\n return this.connectors.some((connector) => connector.name === name);\n }\n\n /**\n * start all connectors\n */\n public async start(connectorsNames?: ConnectorName[]): Promise<void> {\n const connectorsList = connectorsNames\n ? this.connectors.filter((connector) => connectorsNames.includes(connector.name))\n : this.connectors;\n\n for (const connector of connectorsList) {\n await connector.boot();\n }\n\n for (const connector of connectorsList) {\n await connector.start();\n }\n }\n\n /**\n * Start all connectors in the given lifecycle phase.\n *\n * The production builder and dev preload split startup around app\n * code: early phase before app imports, late phase after. Within a\n * phase, all connectors `boot()` first, then all `start()`, so\n * cross-connector wiring inside the phase still works (e.g. socket\n * reads http's instance during its own boot).\n */\n public async startPhase(phase: ConnectorLifecyclePhase): Promise<void> {\n const phaseConnectors = this.connectors.filter(\n (connector) => connector.lifecyclePhase === phase,\n );\n\n for (const connector of phaseConnectors) {\n await connector.boot();\n }\n\n for (const connector of phaseConnectors) {\n await connector.start();\n }\n }\n\n /**\n * Start all connectors except the given ones\n */\n public async startWithout(excludedConnectors: ConnectorName[]): Promise<void> {\n await this.start(\n this.connectors\n .filter((connector) => !excludedConnectors.includes(connector.name))\n .map((connector) => connector.name),\n );\n }\n\n /**\n * Shutdown all connectors\n */\n public async shutdown(): Promise<void> {\n // App-owned teardown runs first, while every connector (db, cache, http) is\n // still up, so cleanup hooks can use them. Idempotent + error-isolated.\n await Application.runShutdownHooks();\n\n // HTTP stops accepting and drains in-flight requests FIRST, while storage,\n // socket, etc. are still up; then the rest go in reverse priority. Copy the\n // list first — `reverse()` mutates in place, so reversing the live array\n // would corrupt the order on a second shutdown pass.\n const reversed = [...this.connectors].reverse();\n const ordered = [\n ...reversed.filter((connector) => connector.name === \"http\"),\n ...reversed.filter((connector) => connector.name !== \"http\"),\n ];\n\n const timeout = config.get(\"app.shutdownTimeout\", DEFAULT_SHUTDOWN_TIMEOUT);\n let current: string | undefined;\n\n const timer = setTimeout(() => {\n // Sync write: the process exits right after, so an async log would be lost.\n console.error(\n `Shutdown timed out after ${timeout}ms; connector still shutting down: ${current ?? \"none\"}`,\n );\n process.exit(1);\n }, timeout);\n timer.unref?.();\n\n try {\n for (const connector of ordered) {\n current = connector.name;\n await this.shutdownOne(connector);\n }\n current = undefined;\n } finally {\n clearTimeout(timer);\n }\n\n // Log calls return before an async channel (file, Sentry) has written the\n // entry, so a channel would lose it when the process exits; `flush()` waits\n // for those deliveries.\n await log.flush();\n }\n\n /**\n * Shut one connector down, isolating and reporting its failure.\n */\n private async shutdownOne(connector: Connector): Promise<void> {\n try {\n await connector.shutdown();\n } catch (error) {\n try {\n // Synchronous: the entry is handed to every channel before this\n // returns. `shutdown()` then flushes async channels before resolving,\n // because the caller (`shutdownOnProcessKill`) calls `process.exit(0)`\n // as soon as it does.\n log.error(\"connectors\", \"shutdown\", error as Error, { connector: connector.name });\n } catch {\n // Reporting a failure must never become a worse failure than the one\n // being reported: anything thrown here (a redact rule, say) would\n // otherwise abort the remaining teardown. Swallowed\n // rather than re-reported: the only channel we could report it through\n // is the one that just threw.\n }\n }\n }\n\n /**\n * Shutdown connectors on process kill\n *\n * Registers signal handlers that run an idempotent graceful shutdown:\n * - SIGINT: Ctrl+C on Unix, also caught on Windows but unreliable in child processes\n * - SIGTERM: Termination signal (Unix primarily)\n * - SIGHUP: registered on Windows (`win32`) only, to catch Ctrl+C / console close\n *\n * Note: no `beforeExit` handler is registered — `beforeExit` cannot reliably\n * await async teardown (the process exits once the callback returns), so\n * shutdown is driven entirely by the signal handlers above.\n */\n public shutdownOnProcessKill(): void {\n let isShuttingDown = false;\n\n const gracefulShutdown = async (signal: string) => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n\n console.log(`\\nExiting...`);\n await this.shutdown();\n process.exit(0);\n };\n\n // Unix signals\n process.on(\"SIGINT\", () => gracefulShutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => gracefulShutdown(\"SIGTERM\"));\n\n // Windows-specific: handle when process is about to exit\n // This is more reliable on Windows for spawned child processes\n if (process.platform === \"win32\") {\n // Handle Ctrl+C on Windows specifically\n process.on(\"SIGHUP\", () => gracefulShutdown(\"SIGHUP\"));\n }\n }\n}\n\nexport const connectorsManager = new ConnectorsManager();\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ //#region ../../packages/core/src/connectors/shutdown-timeout.ts
2
+ /** Overall connector shutdown budget; overridden by `app.shutdownTimeout`. */
3
+ const DEFAULT_SHUTDOWN_TIMEOUT = 1e4;
4
+
5
+ //#endregion
6
+ export { DEFAULT_SHUTDOWN_TIMEOUT };
7
+ //# sourceMappingURL=shutdown-timeout.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shutdown-timeout.mjs","names":[],"sources":["../../../../../../../../packages/core/src/connectors/shutdown-timeout.ts"],"sourcesContent":["/** Overall connector shutdown budget; overridden by `app.shutdownTimeout`. */\nexport const DEFAULT_SHUTDOWN_TIMEOUT = 10_000;\n"],"mappings":";;AACA,MAAa,2BAA2B"}
@@ -1 +1 @@
1
- {"version":3,"file":"socket-connector.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/connectors/socket-connector.ts"],"mappings":";;;;;;;;qBAkCa,wBAAwB;WACnB,MAAM;WACN,WAAQ,kBAAA;WACR,iBAAc,wBAAA;;;;;;qBAOX;YAET,SAAS;;;;;;;;;YAUT;;;;YAKA;;;;;;;YAQA;IACR,OAAO,mBAAmB;IAC1B,SAAS,mBAAmB;;;;;EAMjB,QAAI;;;;EA2EJ,SAAS;;;;;EAgBT,WAAW;;;;;;;;;;EAeX,YAAY;;;;UA8BjB;UAUA;;;;;UAsBA"}
1
+ {"version":3,"file":"socket-connector.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/connectors/socket-connector.ts"],"mappings":";;;;;;;;qBAkCa,wBAAwB;WACnB,MAAM;WACN,WAAQ,kBAAA;WACR,iBAAc,wBAAA;;;;;;qBAOX;YAET,SAAS;;;;;;;;;YAUT;;;;YAKA;;;;;;;YAQA;IACR,OAAO,mBAAmB;IAC1B,SAAS,mBAAmB;;;;;EAMjB,QAAI;;;;EAqFJ,SAAS;;;;;EAgBT,WAAW;;;;;;;;;;EAeX,YAAY;;;;UA8BjB;UAUA;;;;;UAsBA"}
@@ -68,7 +68,14 @@ var SocketConnector = class extends BaseConnector {
68
68
  server.on("error", (error) => {
69
69
  log.error("socket", "connection", error, { port: socketConfig.port });
70
70
  });
71
- server.listen(socketConfig.port);
71
+ await new Promise((resolve, reject) => {
72
+ const onBindError = (error) => reject(error);
73
+ server.once("error", onBindError);
74
+ server.listen(socketConfig.port, () => {
75
+ server.removeListener("error", onBindError);
76
+ resolve();
77
+ });
78
+ });
72
79
  this.ownsRawServer = true;
73
80
  }
74
81
  container.set("socket.rawServer", server);
@@ -1 +1 @@
1
- {"version":3,"file":"socket-connector.mjs","names":["config","createHttpsServer","createHttpServer"],"sources":["../../../../../../../../packages/core/src/connectors/socket-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { createServer as createHttpServer } from \"http\";\nimport { createServer as createHttpsServer } from \"https\";\nimport type { Server } from \"socket.io\";\nimport { container } from \"../container\";\nimport { environment } from \"../utils/environment\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, type ConnectorName, ConnectorPriority } from \"./types\";\n\n/**\n * Shown when a project enables sockets (a `socket` config is present) but the\n * optional `socket.io` peer is not installed.\n */\nconst SOCKET_INSTALL_INSTRUCTIONS = `\nRealtime socket server requires the socket.io package.\nInstall it with:\n\n warlock add socket\n\nOr manually:\n\n npm install socket.io\n pnpm add socket.io\n yarn add socket.io\n`.trim();\n\nconst SINGLE_SERVER_WARNING =\n \"socket: no adapter configured — broadcasts only reach clients connected to this server. Use @socket.io/redis-adapter (socket.adapter) behind a load balancer, with sticky sessions for the polling transport.\";\n\n/**\n * Socket Connector\n * Manages Socket server (Socket.IO) lifecycle\n */\nexport class SocketConnector extends BaseConnector {\n public readonly name: ConnectorName = \"socket\";\n public readonly priority = ConnectorPriority.SOCKET;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\n\n /**\n * Files that trigger Socket server restart\n * Note: routes.ts changes will be handled by HMR with wildcard routing\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\n */\n protected readonly watchedFiles = [\"src/config/socket.ts\"];\n\n protected socket?: Server;\n\n /**\n * Whether this connector created the underlying node HTTP(S) server.\n *\n * When `http` is present, socket binds onto the HTTP connector's shared\n * `fastify.server` and must NOT close it on shutdown — the HTTP connector\n * owns the graceful drain. Socket only closes the node server when it\n * created one itself (http absent). See {@link shutdown}.\n */\n protected ownsRawServer = false;\n\n /**\n * The shared server socket.io attached to, kept for the detach.\n */\n protected sharedServer?: any;\n\n /**\n * Listener changes socket.io/engine.io made on the shared server at attach:\n * the listeners they added, and the ones they removed (engine.io lifts the\n * existing `request` listeners into its own wrapper). Undone on detach so\n * repeated dev restarts don't stack engines on Fastify's server.\n */\n protected attachedListeners: {\n added: Array<[string, (...args: any[]) => void]>;\n removed: Array<[string, (...args: any[]) => void]>;\n } = { added: [], removed: [] };\n\n /**\n * Boot the connector\n */\n public async boot() {\n const socketConfig = config.get(\"socket\");\n\n if (!socketConfig) return;\n\n // socket.io is an optional peer — load it lazily so projects that don't use\n // realtime sockets never need it installed (mirrors the mail/storage drivers).\n let SocketServer: typeof import(\"socket.io\").Server;\n try {\n ({ Server: SocketServer } = await import(\"socket.io\"));\n } catch {\n throw new Error(SOCKET_INSTALL_INSTRUCTIONS);\n }\n\n log.info(\"socket\", \"connection\", \"Starting Socket.IO server\");\n\n // now we have two cases\n // 1. http is used, then use it (shared — the HTTP connector owns it)\n // 2. http is not used, then create a new server (we own it)\n let server;\n const fastify = container.tryGet(\"http.server\");\n if (fastify) {\n server = fastify.server;\n this.ownsRawServer = false;\n } else {\n server = socketConfig.ssl\n ? createHttpsServer({ key: socketConfig.ssl.key, cert: socketConfig.ssl.cert })\n : createHttpServer();\n // Without a handler, EADDRINUSE is an unhandled 'error' event that crashes\n // the process with no context.\n server.on(\"error\", (error: Error) => {\n log.error(\"socket\", \"connection\", error, { port: socketConfig.port });\n });\n server.listen(socketConfig.port);\n this.ownsRawServer = true;\n }\n\n container.set(\"socket.rawServer\", server);\n\n const before = this.snapshotListeners(server);\n\n this.socket = new SocketServer(server, {\n // The shared raw server may carry OTHER `upgrade` consumers — Vite's HMR\n // websocket does exactly that when `WebConnector` is registered\n // (`web/src/server/web-connector.ts`, `server.hmr.server = fastify.server`).\n // engine.io's default `destroyUpgrade: true` schedules a destroy for every\n // upgrade whose path is not `/socket.io`\n // (`engine.io/build/server.js:676-695`: `setTimeout(… socket.end(), 1000)`),\n // and the only thing saving a foreign websocket today is that it wrote its\n // handshake bytes synchronously and so trips the `bytesWritten <= 0` guard\n // before the 1s timer fires. That is an undocumented race, not a contract.\n // Turning the destroy off removes the coupling outright and makes the\n // shared server safe for any app-owned websocket route.\n // A project can still override it via `socket.options`.\n destroyUpgrade: false,\n ...socketConfig.options,\n });\n\n if (!this.ownsRawServer) {\n this.sharedServer = server;\n this.attachedListeners = this.diffListeners(before, this.snapshotListeners(server));\n }\n\n if (socketConfig.adapter) {\n this.socket.adapter((await socketConfig.adapter(this.socket)) as never);\n } else if (environment() === \"production\" && !socketConfig.silenceSingleServerWarning) {\n log.warn(\"socket\", \"adapter\", SINGLE_SERVER_WARNING);\n }\n\n container.set(\"socket\", this.socket);\n }\n\n /**\n * Initialize Socket server\n */\n public async start(): Promise<void> {\n const socketConfig = config.get(\"socket\");\n\n // `this.socket` is only set by boot() once a socket config is present —\n // use it (not a never-assigned field) to detect a successful boot.\n if (!socketConfig || !this.socket) return;\n\n log.success(\"socket\", \"connection\", \"Established Socket.IO server\");\n\n this.active = true;\n }\n\n /**\n * Restart re-boots too: the closed `io` must be replaced and the new\n * `socket` config read, otherwise a config edit in dev restarts nothing.\n */\n public async restart(): Promise<void> {\n await this.shutdown();\n await this.boot();\n await this.start();\n }\n\n /**\n * Shutdown Socket server\n *\n * Always closes the Socket.IO layer (and awaits the drain). The underlying\n * node HTTP(S) server is only closed here when this connector created it\n * ({@link ownsRawServer}); when `http` owns the shared server, we leave it\n * for the HTTP connector to drain and close, so the two connectors don't\n * race to `close()` the same socket.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n const socket = container.tryGet(\"socket\");\n if (socket) {\n if (this.ownsRawServer) {\n // We own the raw server: `io.close()` closes it too, so it is the only\n // close needed (a second `rawServer.close()` always rejects with\n // ERR_SERVER_NOT_RUNNING).\n await new Promise<void>((resolve) => {\n socket.close(() => resolve());\n });\n } else {\n // Shared mode: `io.close()` calls `httpServer.close()`, which would close\n // Fastify's server from here and bypass its graceful drain. Detach\n // instead: disconnect the clients and close the engine only.\n socket.of(\"/\").disconnectSockets(true);\n socket.engine.close();\n this.restoreListeners();\n }\n }\n\n this.active = false;\n }\n\n /**\n * Current listeners of the events socket.io touches on a node server.\n */\n private snapshotListeners(server: any): Map<string, Array<(...args: any[]) => void>> {\n const snapshot = new Map<string, Array<(...args: any[]) => void>>();\n\n for (const event of [\"request\", \"upgrade\", \"close\", \"listening\"]) {\n snapshot.set(event, server?.listeners?.(event)?.slice() ?? []);\n }\n\n return snapshot;\n }\n\n private diffListeners(\n before: Map<string, Array<(...args: any[]) => void>>,\n after: Map<string, Array<(...args: any[]) => void>>,\n ) {\n const added: Array<[string, (...args: any[]) => void]> = [];\n const removed: Array<[string, (...args: any[]) => void]> = [];\n\n for (const [event, listeners] of before) {\n const now = after.get(event) ?? [];\n\n for (const listener of now) if (!listeners.includes(listener)) added.push([event, listener]);\n for (const listener of listeners)\n if (!now.includes(listener)) removed.push([event, listener]);\n }\n\n return { added, removed };\n }\n\n /**\n * Undo what attaching did to the shared server, leaving listeners other\n * consumers added since (e.g. Vite's HMR upgrade handler) untouched.\n */\n private restoreListeners(): void {\n const server = this.sharedServer;\n\n if (!server) return;\n\n const { added, removed } = this.attachedListeners;\n\n for (const [event, listener] of added) server.removeListener(event, listener);\n for (const [event, listener] of removed) server.on(event, listener);\n\n this.attachedListeners = { added: [], removed: [] };\n this.sharedServer = undefined;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"socket-connector.mjs","names":["config","createHttpsServer","createHttpServer"],"sources":["../../../../../../../../packages/core/src/connectors/socket-connector.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { log } from \"@warlock.js/logger\";\nimport { createServer as createHttpServer, type Server as HttpServer } from \"http\";\nimport { createServer as createHttpsServer } from \"https\";\nimport type { Server } from \"socket.io\";\nimport { container } from \"../container\";\nimport { environment } from \"../utils/environment\";\nimport { BaseConnector } from \"./base-connector\";\nimport { ConnectorLifecyclePhase, type ConnectorName, ConnectorPriority } from \"./types\";\n\n/**\n * Shown when a project enables sockets (a `socket` config is present) but the\n * optional `socket.io` peer is not installed.\n */\nconst SOCKET_INSTALL_INSTRUCTIONS = `\nRealtime socket server requires the socket.io package.\nInstall it with:\n\n warlock add socket\n\nOr manually:\n\n npm install socket.io\n pnpm add socket.io\n yarn add socket.io\n`.trim();\n\nconst SINGLE_SERVER_WARNING =\n \"socket: no adapter configured — broadcasts only reach clients connected to this server. Use @socket.io/redis-adapter (socket.adapter) behind a load balancer, with sticky sessions for the polling transport.\";\n\n/**\n * Socket Connector\n * Manages Socket server (Socket.IO) lifecycle\n */\nexport class SocketConnector extends BaseConnector {\n public readonly name: ConnectorName = \"socket\";\n public readonly priority = ConnectorPriority.SOCKET;\n public readonly lifecyclePhase = ConnectorLifecyclePhase.Late;\n\n /**\n * Files that trigger Socket server restart\n * Note: routes.ts changes will be handled by HMR with wildcard routing\n * Connectors receive config file paths directly (not .env) thanks to layer-executor\n */\n protected readonly watchedFiles = [\"src/config/socket.ts\"];\n\n protected socket?: Server;\n\n /**\n * Whether this connector created the underlying node HTTP(S) server.\n *\n * When `http` is present, socket binds onto the HTTP connector's shared\n * `fastify.server` and must NOT close it on shutdown — the HTTP connector\n * owns the graceful drain. Socket only closes the node server when it\n * created one itself (http absent). See {@link shutdown}.\n */\n protected ownsRawServer = false;\n\n /**\n * The shared server socket.io attached to, kept for the detach.\n */\n protected sharedServer?: any;\n\n /**\n * Listener changes socket.io/engine.io made on the shared server at attach:\n * the listeners they added, and the ones they removed (engine.io lifts the\n * existing `request` listeners into its own wrapper). Undone on detach so\n * repeated dev restarts don't stack engines on Fastify's server.\n */\n protected attachedListeners: {\n added: Array<[string, (...args: any[]) => void]>;\n removed: Array<[string, (...args: any[]) => void]>;\n } = { added: [], removed: [] };\n\n /**\n * Boot the connector\n */\n public async boot() {\n const socketConfig = config.get(\"socket\");\n\n if (!socketConfig) return;\n\n // socket.io is an optional peer — load it lazily so projects that don't use\n // realtime sockets never need it installed (mirrors the mail/storage drivers).\n let SocketServer: typeof import(\"socket.io\").Server;\n try {\n ({ Server: SocketServer } = await import(\"socket.io\"));\n } catch {\n throw new Error(SOCKET_INSTALL_INSTRUCTIONS);\n }\n\n log.info(\"socket\", \"connection\", \"Starting Socket.IO server\");\n\n // now we have two cases\n // 1. http is used, then use it (shared — the HTTP connector owns it)\n // 2. http is not used, then create a new server (we own it)\n let server: HttpServer;\n const fastify = container.tryGet(\"http.server\");\n if (fastify) {\n server = fastify.server;\n this.ownsRawServer = false;\n } else {\n server = socketConfig.ssl\n ? createHttpsServer({ key: socketConfig.ssl.key, cert: socketConfig.ssl.cert })\n : createHttpServer();\n // Without a handler, EADDRINUSE is an unhandled 'error' event that crashes\n // the process with no context.\n server.on(\"error\", (error: Error) => {\n log.error(\"socket\", \"connection\", error, { port: socketConfig.port });\n });\n // Boot must await the socket-only listener too: Application.markBooted\n // reports readiness to PM2 after the late phase resolves. Logging a bind\n // error alone would otherwise still let the app announce readiness.\n await new Promise<void>((resolve, reject) => {\n const onBindError = (error: Error) => reject(error);\n server.once(\"error\", onBindError);\n server.listen(socketConfig.port, () => {\n server.removeListener(\"error\", onBindError);\n resolve();\n });\n });\n this.ownsRawServer = true;\n }\n\n container.set(\"socket.rawServer\", server);\n\n const before = this.snapshotListeners(server);\n\n this.socket = new SocketServer(server, {\n // The shared raw server may carry OTHER `upgrade` consumers — Vite's HMR\n // websocket does exactly that when `WebConnector` is registered\n // (`web/src/server/web-connector.ts`, `server.hmr.server = fastify.server`).\n // engine.io's default `destroyUpgrade: true` schedules a destroy for every\n // upgrade whose path is not `/socket.io`\n // (`engine.io/build/server.js:676-695`: `setTimeout(… socket.end(), 1000)`),\n // and the only thing saving a foreign websocket today is that it wrote its\n // handshake bytes synchronously and so trips the `bytesWritten <= 0` guard\n // before the 1s timer fires. That is an undocumented race, not a contract.\n // Turning the destroy off removes the coupling outright and makes the\n // shared server safe for any app-owned websocket route.\n // A project can still override it via `socket.options`.\n destroyUpgrade: false,\n ...socketConfig.options,\n });\n\n if (!this.ownsRawServer) {\n this.sharedServer = server;\n this.attachedListeners = this.diffListeners(before, this.snapshotListeners(server));\n }\n\n if (socketConfig.adapter) {\n this.socket.adapter((await socketConfig.adapter(this.socket)) as never);\n } else if (environment() === \"production\" && !socketConfig.silenceSingleServerWarning) {\n log.warn(\"socket\", \"adapter\", SINGLE_SERVER_WARNING);\n }\n\n container.set(\"socket\", this.socket);\n }\n\n /**\n * Initialize Socket server\n */\n public async start(): Promise<void> {\n const socketConfig = config.get(\"socket\");\n\n // `this.socket` is only set by boot() once a socket config is present —\n // use it (not a never-assigned field) to detect a successful boot.\n if (!socketConfig || !this.socket) return;\n\n log.success(\"socket\", \"connection\", \"Established Socket.IO server\");\n\n this.active = true;\n }\n\n /**\n * Restart re-boots too: the closed `io` must be replaced and the new\n * `socket` config read, otherwise a config edit in dev restarts nothing.\n */\n public async restart(): Promise<void> {\n await this.shutdown();\n await this.boot();\n await this.start();\n }\n\n /**\n * Shutdown Socket server\n *\n * Always closes the Socket.IO layer (and awaits the drain). The underlying\n * node HTTP(S) server is only closed here when this connector created it\n * ({@link ownsRawServer}); when `http` owns the shared server, we leave it\n * for the HTTP connector to drain and close, so the two connectors don't\n * race to `close()` the same socket.\n */\n public async shutdown(): Promise<void> {\n if (!this.active) {\n return;\n }\n\n const socket = container.tryGet(\"socket\");\n if (socket) {\n if (this.ownsRawServer) {\n // We own the raw server: `io.close()` closes it too, so it is the only\n // close needed (a second `rawServer.close()` always rejects with\n // ERR_SERVER_NOT_RUNNING).\n await new Promise<void>((resolve) => {\n socket.close(() => resolve());\n });\n } else {\n // Shared mode: `io.close()` calls `httpServer.close()`, which would close\n // Fastify's server from here and bypass its graceful drain. Detach\n // instead: disconnect the clients and close the engine only.\n socket.of(\"/\").disconnectSockets(true);\n socket.engine.close();\n this.restoreListeners();\n }\n }\n\n this.active = false;\n }\n\n /**\n * Current listeners of the events socket.io touches on a node server.\n */\n private snapshotListeners(server: any): Map<string, Array<(...args: any[]) => void>> {\n const snapshot = new Map<string, Array<(...args: any[]) => void>>();\n\n for (const event of [\"request\", \"upgrade\", \"close\", \"listening\"]) {\n snapshot.set(event, server?.listeners?.(event)?.slice() ?? []);\n }\n\n return snapshot;\n }\n\n private diffListeners(\n before: Map<string, Array<(...args: any[]) => void>>,\n after: Map<string, Array<(...args: any[]) => void>>,\n ) {\n const added: Array<[string, (...args: any[]) => void]> = [];\n const removed: Array<[string, (...args: any[]) => void]> = [];\n\n for (const [event, listeners] of before) {\n const now = after.get(event) ?? [];\n\n for (const listener of now) if (!listeners.includes(listener)) added.push([event, listener]);\n for (const listener of listeners)\n if (!now.includes(listener)) removed.push([event, listener]);\n }\n\n return { added, removed };\n }\n\n /**\n * Undo what attaching did to the shared server, leaving listeners other\n * consumers added since (e.g. Vite's HMR upgrade handler) untouched.\n */\n private restoreListeners(): void {\n const server = this.sharedServer;\n\n if (!server) return;\n\n const { added, removed } = this.attachedListeners;\n\n for (const [event, listener] of added) server.removeListener(event, listener);\n for (const [event, listener] of removed) server.on(event, listener);\n\n this.attachedListeners = { added: [], removed: [] };\n this.sharedServer = undefined;\n }\n}\n"],"mappings":"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"}
@@ -1,17 +1,18 @@
1
+ import { hasEslint, warnEslintDeprecation } from "../eslint-support.mjs";
1
2
  import { BaseHealthChecker } from "./base-health-checker.mjs";
2
- import path from "path";
3
3
  import { colors } from "@mongez/copper";
4
- import fs from "fs";
5
4
  import { ESLint } from "eslint";
6
5
 
7
6
  //#region ../../packages/core/src/dev-server/health-checker/checkers/eslint-health-checker.ts
7
+ /** @deprecated Use OxlintHealthChecker. ESLint support is removed in 5.34.0. */
8
8
  var EslintHealthChecker = class extends BaseHealthChecker {
9
- constructor(..._args) {
10
- super(..._args);
9
+ constructor() {
10
+ super();
11
11
  this.eslint = null;
12
12
  this.name = "ESLint";
13
13
  this.workerPath = "./workers/eslint-health.worker";
14
14
  this.initialized = false;
15
+ warnEslintDeprecation();
15
16
  }
16
17
  /**
17
18
  * Check if file is a lintable file
@@ -25,10 +26,7 @@ var EslintHealthChecker = class extends BaseHealthChecker {
25
26
  */
26
27
  initialize() {
27
28
  try {
28
- const flatConfigPath = path.join(process.cwd(), "eslint.config.js");
29
- const flatConfigMjsPath = path.join(process.cwd(), "eslint.config.mjs");
30
- const flatConfigCjsPath = path.join(process.cwd(), "eslint.config.cjs");
31
- if (!(fs.existsSync(flatConfigPath) || fs.existsSync(flatConfigMjsPath) || fs.existsSync(flatConfigCjsPath))) {
29
+ if (!hasEslint(process.cwd())) {
32
30
  this.initialized = true;
33
31
  return this;
34
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"eslint-health-checker.mjs","names":[],"sources":["../../../../../../../../../../packages/core/src/dev-server/health-checker/checkers/eslint-health-checker.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { ESLint } from \"eslint\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport type { FileManager } from \"../../file-manager\";\nimport type { FileHealthCheckerContract } from \"../file-health-checker.contract\";\nimport { FileHealthResult } from \"../file-health-result\";\nimport { BaseHealthChecker } from \"./base-health-checker\";\n\nexport class EslintHealthChecker extends BaseHealthChecker implements FileHealthCheckerContract {\n /**\n * ESLint instance (using new ESLint v9 API with flat config)\n */\n private eslint: ESLint | null = null;\n\n /**\n * Health checker name\n */\n public name: string = \"ESLint\";\n\n /**\n * Path to dedicated worker file for ESLint checking\n * Runs in a separate thread to avoid blocking the main dev server\n */\n public workerPath: string = \"./workers/eslint-health.worker\";\n\n /**\n * Whether checker is initialized\n */\n private initialized: boolean = false;\n\n /**\n * Check if file is a lintable file\n */\n private isLintableFile(filePath: string): boolean {\n const ext = filePath.toLowerCase();\n return (\n ext.endsWith(\".ts\") || ext.endsWith(\".tsx\") || ext.endsWith(\".js\") || ext.endsWith(\".jsx\")\n );\n }\n\n /**\n * Initialize the health checker\n */\n public initialize(): EslintHealthChecker {\n try {\n // Check if eslint.config.js exists (new flat config format)\n const flatConfigPath = path.join(process.cwd(), \"eslint.config.js\");\n const flatConfigMjsPath = path.join(process.cwd(), \"eslint.config.mjs\");\n const flatConfigCjsPath = path.join(process.cwd(), \"eslint.config.cjs\");\n\n const hasFlatConfig =\n fs.existsSync(flatConfigPath) ||\n fs.existsSync(flatConfigMjsPath) ||\n fs.existsSync(flatConfigCjsPath);\n\n if (!hasFlatConfig) {\n // No flat config found, skip initialization\n this.initialized = true;\n return this;\n }\n\n // Use new ESLint v9 API with flat config\n // ESLint will automatically find eslint.config.js in the project root\n this.eslint = new ESLint({\n cwd: process.cwd(),\n });\n\n this.initialized = true;\n } catch (error) {\n // Handle any errors during initialization gracefully\n console.warn(\"ESLint Health Checker: Failed to initialize:\", error);\n this.initialized = true; // Mark as initialized to prevent retries\n }\n\n return this;\n }\n\n /**\n * Display health check results in a pretty format\n */\n private displayResults(file: FileManager, result: FileHealthResult): void {\n const stats = result.getStats();\n\n // Only display if there are errors or warnings\n if (stats.errors === 0 && stats.warnings === 0) {\n return;\n }\n\n const fileName = file.relativePath.replace(/\\\\/g, \"/\");\n const errorCount = stats.errors;\n const warningCount = stats.warnings;\n const sourceLines = file.source ? file.source.split(\"\\n\") : [];\n\n // Display errors\n if (errorCount > 0) {\n const errorMessages = result.messages.filter((m) => m.type === \"error\");\n for (const error of errorMessages) {\n const icon = colors.redBright(\"✖\");\n const level = colors.redBright(colors.bold(\"ERROR\"));\n console.log(\n `\\n${icon} ${level} ${colors.dim(\"in\")} ${colors.cyanBright(fileName)}${colors.dim(`(${error.lineNumber},${error.columnNumber})`)}`,\n );\n // Get rule ID from the error if available\n const ruleId = error.ruleId || \"eslint\";\n console.log(\n ` ${colors.magentaBright(ruleId)} ${colors.dim(\"→\")} ${colors.red(error.message)}`,\n );\n\n // Display code line with underline indicator\n if (\n sourceLines.length > 0 &&\n error.lineNumber > 0 &&\n error.lineNumber <= sourceLines.length\n ) {\n const lineIndex = error.lineNumber - 1; // Convert to 0-based index\n const lineContent = sourceLines[lineIndex];\n const lineNum = error.lineNumber.toString().padStart(4, \" \");\n const errorLength = error.length || 1;\n const columnIndex = error.columnNumber - 1; // Convert to 0-based index\n\n // Display the line with normal color (not all red)\n console.log(` ${colors.dim(lineNum)} ${colors.dim(\"│\")} ${lineContent || \"\"}`);\n\n // Display underline indicator\n // Padding accounts for: lineNum.length + 1 space + 1 (│) + 1 space = lineNum.length + 3\n // (The initial 2 spaces indent is already in the console.log)\n const prefixPadding = lineNum.length + 3;\n const columnPadding = \" \".repeat(columnIndex);\n const underline = colors.redBright(\"~\".repeat(Math.max(1, errorLength)));\n console.log(` ${colors.dim(\" \".repeat(prefixPadding))}${columnPadding}${underline}`);\n }\n }\n }\n\n // Display warnings\n if (warningCount > 0) {\n const warningMessages = result.messages.filter((m) => m.type === \"warning\");\n for (const warning of warningMessages) {\n const icon = colors.yellowBright(\"⚠\");\n const level = colors.yellowBright(colors.bold(\"WARNING\"));\n console.log(\n `\\n${icon} ${level} ${colors.dim(\"in\")} ${colors.cyanBright(fileName)}${colors.dim(`(${warning.lineNumber},${warning.columnNumber})`)}`,\n );\n // Get rule ID from the warning if available\n const ruleId = warning.ruleId || \"eslint\";\n console.log(\n ` ${colors.magentaBright(ruleId)} ${colors.dim(\"→\")} ${colors.yellow(warning.message)}`,\n );\n\n // Display code line with underline indicator\n if (\n sourceLines.length > 0 &&\n warning.lineNumber > 0 &&\n warning.lineNumber <= sourceLines.length\n ) {\n const lineIndex = warning.lineNumber - 1; // Convert to 0-based index\n const lineContent = sourceLines[lineIndex];\n const lineNum = warning.lineNumber.toString().padStart(4, \" \");\n const warningLength = warning.length || 1;\n const columnIndex = warning.columnNumber - 1; // Convert to 0-based index\n\n // Display the line with normal color (not all yellow)\n console.log(` ${colors.dim(lineNum)} ${colors.dim(\"│\")} ${lineContent || \"\"}`);\n\n // Display underline indicator\n // Padding accounts for: lineNum.length + 1 space + 1 (│) + 1 space = lineNum.length + 3\n // (The initial 2 spaces indent is already in the console.log)\n const prefixPadding = lineNum.length + 3;\n const columnPadding = \" \".repeat(columnIndex);\n const underline = colors.yellowBright(\"~\".repeat(Math.max(1, warningLength)));\n console.log(` ${colors.dim(\" \".repeat(prefixPadding))}${columnPadding}${underline}`);\n }\n }\n }\n\n // Display summary\n const summary = [];\n if (errorCount > 0) {\n summary.push(colors.red(`${errorCount} error${errorCount > 1 ? \"s\" : \"\"}`));\n }\n if (warningCount > 0) {\n summary.push(colors.yellow(`${warningCount} warning${warningCount > 1 ? \"s\" : \"\"}`));\n }\n }\n\n /**\n * Validate the health of the file\n */\n public async validate(file: FileManager, result: FileHealthResult): Promise<FileHealthResult> {\n // Early exit: skip non-lintable files\n if (!this.isLintableFile(file.absolutePath)) {\n result.markAsHealthy();\n return result;\n }\n\n // Early exit: check if ESLint is available\n if (!this.eslint) {\n result.markAsHealthy();\n return result;\n }\n\n try {\n // Use lintText to lint the source code directly from FileManager\n // This ensures we always use the latest content\n const lintResults = await this.eslint.lintText(file.source, {\n filePath: file.absolutePath,\n });\n\n const lintResult = lintResults[0];\n if (!lintResult) {\n console.log(\"No lint results\", file.relativePath);\n result.markAsHealthy();\n return result;\n }\n\n // Convert ESLint messages to FileHealthResult format\n const errors: Array<{\n message: string;\n type: \"error\";\n lineNumber: number;\n columnNumber: number;\n length: number;\n ruleId?: string;\n }> = [];\n const warnings: Array<{\n message: string;\n type: \"warning\";\n lineNumber: number;\n columnNumber: number;\n length: number;\n ruleId?: string;\n }> = [];\n\n for (const message of lintResult.messages) {\n const isError = message.severity === 2;\n const isWarning = message.severity === 1;\n\n if (isError) {\n errors.push({\n message: message.message,\n type: \"error\",\n lineNumber: message.line || 1,\n columnNumber: message.column || 1,\n length: message.endColumn && message.column ? message.endColumn - message.column : 1,\n ruleId: message.ruleId || undefined,\n });\n } else if (isWarning) {\n warnings.push({\n message: message.message,\n type: \"warning\",\n lineNumber: message.line || 1,\n columnNumber: message.column || 1,\n length: message.endColumn && message.column ? message.endColumn - message.column : 1,\n ruleId: message.ruleId || undefined,\n });\n }\n }\n\n // Add errors and warnings to result\n if (errors.length > 0) {\n result.addErrors(errors);\n }\n\n if (warnings.length > 0) {\n result.addWarnings(warnings);\n }\n\n // If no errors or warnings, mark as healthy\n if (errors.length === 0 && warnings.length === 0) {\n result.markAsHealthy();\n } else {\n // Display results if there are errors or warnings\n this.displayResults(file, result);\n }\n } catch (error) {\n // Handle any errors during validation gracefully\n console.log(\"ESlint Error:\", error);\n result.markAsHealthy(); // Return healthy on error to avoid blocking\n }\n\n return result;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"eslint-health-checker.mjs","names":[],"sources":["../../../../../../../../../../packages/core/src/dev-server/health-checker/checkers/eslint-health-checker.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { ESLint } from \"eslint\";\nimport { hasEslint, warnEslintDeprecation } from \"../eslint-support\";\nimport type { FileManager } from \"../../file-manager\";\nimport type { FileHealthCheckerContract } from \"../file-health-checker.contract\";\nimport { FileHealthResult } from \"../file-health-result\";\nimport { BaseHealthChecker } from \"./base-health-checker\";\n\n/** @deprecated Use OxlintHealthChecker. ESLint support is removed in 5.34.0. */\nexport class EslintHealthChecker extends BaseHealthChecker implements FileHealthCheckerContract {\n public constructor() {\n super();\n // Construction happens on the main thread for both inline and worker checking.\n warnEslintDeprecation();\n }\n\n /**\n * ESLint instance (using new ESLint v9 API with flat config)\n */\n private eslint: ESLint | null = null;\n\n /**\n * Health checker name\n */\n public name: string = \"ESLint\";\n\n /**\n * Path to dedicated worker file for ESLint checking\n * Runs in a separate thread to avoid blocking the main dev server\n */\n public workerPath: string = \"./workers/eslint-health.worker\";\n\n /**\n * Whether checker is initialized\n */\n private initialized: boolean = false;\n\n /**\n * Check if file is a lintable file\n */\n private isLintableFile(filePath: string): boolean {\n const ext = filePath.toLowerCase();\n return (\n ext.endsWith(\".ts\") || ext.endsWith(\".tsx\") || ext.endsWith(\".js\") || ext.endsWith(\".jsx\")\n );\n }\n\n /**\n * Initialize the health checker\n */\n public initialize(): EslintHealthChecker {\n try {\n const hasFlatConfig = hasEslint(process.cwd());\n\n if (!hasFlatConfig) {\n // No flat config found, skip initialization\n this.initialized = true;\n return this;\n }\n\n // Use new ESLint v9 API with flat config\n // ESLint will automatically find eslint.config.js in the project root\n this.eslint = new ESLint({\n cwd: process.cwd(),\n });\n\n this.initialized = true;\n } catch (error) {\n // Handle any errors during initialization gracefully\n console.warn(\"ESLint Health Checker: Failed to initialize:\", error);\n this.initialized = true; // Mark as initialized to prevent retries\n }\n\n return this;\n }\n\n /**\n * Display health check results in a pretty format\n */\n private displayResults(file: FileManager, result: FileHealthResult): void {\n const stats = result.getStats();\n\n // Only display if there are errors or warnings\n if (stats.errors === 0 && stats.warnings === 0) {\n return;\n }\n\n const fileName = file.relativePath.replace(/\\\\/g, \"/\");\n const errorCount = stats.errors;\n const warningCount = stats.warnings;\n const sourceLines = file.source ? file.source.split(\"\\n\") : [];\n\n // Display errors\n if (errorCount > 0) {\n const errorMessages = result.messages.filter((m) => m.type === \"error\");\n for (const error of errorMessages) {\n const icon = colors.redBright(\"✖\");\n const level = colors.redBright(colors.bold(\"ERROR\"));\n console.log(\n `\\n${icon} ${level} ${colors.dim(\"in\")} ${colors.cyanBright(fileName)}${colors.dim(`(${error.lineNumber},${error.columnNumber})`)}`,\n );\n // Get rule ID from the error if available\n const ruleId = error.ruleId || \"eslint\";\n console.log(\n ` ${colors.magentaBright(ruleId)} ${colors.dim(\"→\")} ${colors.red(error.message)}`,\n );\n\n // Display code line with underline indicator\n if (\n sourceLines.length > 0 &&\n error.lineNumber > 0 &&\n error.lineNumber <= sourceLines.length\n ) {\n const lineIndex = error.lineNumber - 1; // Convert to 0-based index\n const lineContent = sourceLines[lineIndex];\n const lineNum = error.lineNumber.toString().padStart(4, \" \");\n const errorLength = error.length || 1;\n const columnIndex = error.columnNumber - 1; // Convert to 0-based index\n\n // Display the line with normal color (not all red)\n console.log(` ${colors.dim(lineNum)} ${colors.dim(\"│\")} ${lineContent || \"\"}`);\n\n // Display underline indicator\n // Padding accounts for: lineNum.length + 1 space + 1 (│) + 1 space = lineNum.length + 3\n // (The initial 2 spaces indent is already in the console.log)\n const prefixPadding = lineNum.length + 3;\n const columnPadding = \" \".repeat(columnIndex);\n const underline = colors.redBright(\"~\".repeat(Math.max(1, errorLength)));\n console.log(` ${colors.dim(\" \".repeat(prefixPadding))}${columnPadding}${underline}`);\n }\n }\n }\n\n // Display warnings\n if (warningCount > 0) {\n const warningMessages = result.messages.filter((m) => m.type === \"warning\");\n for (const warning of warningMessages) {\n const icon = colors.yellowBright(\"⚠\");\n const level = colors.yellowBright(colors.bold(\"WARNING\"));\n console.log(\n `\\n${icon} ${level} ${colors.dim(\"in\")} ${colors.cyanBright(fileName)}${colors.dim(`(${warning.lineNumber},${warning.columnNumber})`)}`,\n );\n // Get rule ID from the warning if available\n const ruleId = warning.ruleId || \"eslint\";\n console.log(\n ` ${colors.magentaBright(ruleId)} ${colors.dim(\"→\")} ${colors.yellow(warning.message)}`,\n );\n\n // Display code line with underline indicator\n if (\n sourceLines.length > 0 &&\n warning.lineNumber > 0 &&\n warning.lineNumber <= sourceLines.length\n ) {\n const lineIndex = warning.lineNumber - 1; // Convert to 0-based index\n const lineContent = sourceLines[lineIndex];\n const lineNum = warning.lineNumber.toString().padStart(4, \" \");\n const warningLength = warning.length || 1;\n const columnIndex = warning.columnNumber - 1; // Convert to 0-based index\n\n // Display the line with normal color (not all yellow)\n console.log(` ${colors.dim(lineNum)} ${colors.dim(\"│\")} ${lineContent || \"\"}`);\n\n // Display underline indicator\n // Padding accounts for: lineNum.length + 1 space + 1 (│) + 1 space = lineNum.length + 3\n // (The initial 2 spaces indent is already in the console.log)\n const prefixPadding = lineNum.length + 3;\n const columnPadding = \" \".repeat(columnIndex);\n const underline = colors.yellowBright(\"~\".repeat(Math.max(1, warningLength)));\n console.log(` ${colors.dim(\" \".repeat(prefixPadding))}${columnPadding}${underline}`);\n }\n }\n }\n\n // Display summary\n const summary = [];\n if (errorCount > 0) {\n summary.push(colors.red(`${errorCount} error${errorCount > 1 ? \"s\" : \"\"}`));\n }\n if (warningCount > 0) {\n summary.push(colors.yellow(`${warningCount} warning${warningCount > 1 ? \"s\" : \"\"}`));\n }\n }\n\n /**\n * Validate the health of the file\n */\n public async validate(file: FileManager, result: FileHealthResult): Promise<FileHealthResult> {\n // Early exit: skip non-lintable files\n if (!this.isLintableFile(file.absolutePath)) {\n result.markAsHealthy();\n return result;\n }\n\n // Early exit: check if ESLint is available\n if (!this.eslint) {\n result.markAsHealthy();\n return result;\n }\n\n try {\n // Use lintText to lint the source code directly from FileManager\n // This ensures we always use the latest content\n const lintResults = await this.eslint.lintText(file.source, {\n filePath: file.absolutePath,\n });\n\n const lintResult = lintResults[0];\n if (!lintResult) {\n console.log(\"No lint results\", file.relativePath);\n result.markAsHealthy();\n return result;\n }\n\n // Convert ESLint messages to FileHealthResult format\n const errors: Array<{\n message: string;\n type: \"error\";\n lineNumber: number;\n columnNumber: number;\n length: number;\n ruleId?: string;\n }> = [];\n const warnings: Array<{\n message: string;\n type: \"warning\";\n lineNumber: number;\n columnNumber: number;\n length: number;\n ruleId?: string;\n }> = [];\n\n for (const message of lintResult.messages) {\n const isError = message.severity === 2;\n const isWarning = message.severity === 1;\n\n if (isError) {\n errors.push({\n message: message.message,\n type: \"error\",\n lineNumber: message.line || 1,\n columnNumber: message.column || 1,\n length: message.endColumn && message.column ? message.endColumn - message.column : 1,\n ruleId: message.ruleId || undefined,\n });\n } else if (isWarning) {\n warnings.push({\n message: message.message,\n type: \"warning\",\n lineNumber: message.line || 1,\n columnNumber: message.column || 1,\n length: message.endColumn && message.column ? message.endColumn - message.column : 1,\n ruleId: message.ruleId || undefined,\n });\n }\n }\n\n // Add errors and warnings to result\n if (errors.length > 0) {\n result.addErrors(errors);\n }\n\n if (warnings.length > 0) {\n result.addWarnings(warnings);\n }\n\n // If no errors or warnings, mark as healthy\n if (errors.length === 0 && warnings.length === 0) {\n result.markAsHealthy();\n } else {\n // Display results if there are errors or warnings\n this.displayResults(file, result);\n }\n } catch (error) {\n // Handle any errors during validation gracefully\n console.log(\"ESlint Error:\", error);\n result.markAsHealthy(); // Return healthy on error to avoid blocking\n }\n\n return result;\n }\n}\n"],"mappings":"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"}
@@ -1,31 +1,22 @@
1
+ import { hasEslint, warnEslintDeprecation } from "./eslint-support.mjs";
1
2
  import { EslintHealthChecker } from "./checkers/eslint-health-checker.mjs";
2
3
  import { findOxlint } from "./oxlint-runner.mjs";
3
4
  import { OxlintHealthChecker } from "./checkers/oxlint-health-checker.mjs";
4
5
  import { TypescriptHealthChecker } from "./checkers/typescript-health-checker.mjs";
5
- import path from "node:path";
6
- import { existsSync } from "node:fs";
7
6
 
8
7
  //#region ../../packages/core/src/dev-server/health-checker/default-health-checkers.ts
9
- /** Whether the app has an ESLint flat config at its root. */
10
- function hasEslint(cwd) {
11
- return [
12
- "js",
13
- "mjs",
14
- "cjs",
15
- "ts",
16
- "mts",
17
- "cts"
18
- ].some((extension) => existsSync(path.join(cwd, `eslint.config.${extension}`)));
19
- }
20
8
  /** Explicit arrays remain custom-only; built-in booleans select among available checkers. */
21
9
  function defaultHealthCheckers(options = {}, cwd = process.cwd()) {
22
10
  const checkers = [];
23
11
  if (options.typescript !== false) checkers.push(new TypescriptHealthChecker());
24
- if (options.eslint !== false && hasEslint(cwd)) checkers.push(new EslintHealthChecker());
12
+ if (options.eslint === true) {
13
+ warnEslintDeprecation();
14
+ if (hasEslint(cwd)) checkers.push(new EslintHealthChecker());
15
+ }
25
16
  if (options.oxlint !== false && findOxlint(cwd)) checkers.push(new OxlintHealthChecker());
26
17
  return checkers;
27
18
  }
28
19
 
29
20
  //#endregion
30
- export { defaultHealthCheckers, hasEslint };
21
+ export { defaultHealthCheckers };
31
22
  //# sourceMappingURL=default-health-checkers.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"default-health-checkers.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/dev-server/health-checker/default-health-checkers.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { EslintHealthChecker } from \"./checkers/eslint-health-checker\";\nimport { OxlintHealthChecker } from \"./checkers/oxlint-health-checker\";\nimport { TypescriptHealthChecker } from \"./checkers/typescript-health-checker\";\nimport type {\n BuiltInHealthCheckerOptions,\n FileHealthCheckerContract,\n} from \"./file-health-checker.contract\";\nimport { findOxlint } from \"./oxlint-runner\";\n\n/** Whether the app has an ESLint flat config at its root. */\nexport function hasEslint(cwd: string): boolean {\n // ESLint is a core dependency, so an app needs only its configuration.\n return [\"js\", \"mjs\", \"cjs\", \"ts\", \"mts\", \"cts\"].some((extension) =>\n existsSync(path.join(cwd, `eslint.config.${extension}`)),\n );\n}\n\n/** Explicit arrays remain custom-only; built-in booleans select among available checkers. */\nexport function defaultHealthCheckers(\n options: BuiltInHealthCheckerOptions = {},\n cwd = process.cwd(),\n): FileHealthCheckerContract[] {\n const checkers: FileHealthCheckerContract[] = [];\n if (options.typescript !== false) checkers.push(new TypescriptHealthChecker());\n if (options.eslint !== false && hasEslint(cwd)) checkers.push(new EslintHealthChecker());\n if (options.oxlint !== false && findOxlint(cwd)) checkers.push(new OxlintHealthChecker());\n return checkers;\n}\n"],"mappings":";;;;;;;;;AAYA,SAAgB,UAAU,KAAsB;CAE9C,OAAO;EAAC;EAAM;EAAO;EAAO;EAAM;EAAO;CAAK,CAAC,CAAC,MAAM,cACpD,WAAW,KAAK,KAAK,KAAK,iBAAiB,WAAW,CAAC,CACzD;AACF;;AAGA,SAAgB,sBACd,UAAuC,CAAC,GACxC,MAAM,QAAQ,IAAI,GACW;CAC7B,MAAM,WAAwC,CAAC;CAC/C,IAAI,QAAQ,eAAe,OAAO,SAAS,KAAK,IAAI,wBAAwB,CAAC;CAC7E,IAAI,QAAQ,WAAW,SAAS,UAAU,GAAG,GAAG,SAAS,KAAK,IAAI,oBAAoB,CAAC;CACvF,IAAI,QAAQ,WAAW,SAAS,WAAW,GAAG,GAAG,SAAS,KAAK,IAAI,oBAAoB,CAAC;CACxF,OAAO;AACT"}
1
+ {"version":3,"file":"default-health-checkers.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/dev-server/health-checker/default-health-checkers.ts"],"sourcesContent":["import { EslintHealthChecker } from \"./checkers/eslint-health-checker\";\nimport { OxlintHealthChecker } from \"./checkers/oxlint-health-checker\";\nimport { TypescriptHealthChecker } from \"./checkers/typescript-health-checker\";\nimport type {\n BuiltInHealthCheckerOptions,\n FileHealthCheckerContract,\n} from \"./file-health-checker.contract\";\nimport { hasEslint, warnEslintDeprecation } from \"./eslint-support\";\nimport { findOxlint } from \"./oxlint-runner\";\n\nexport { hasEslint } from \"./eslint-support\";\n\n/** Explicit arrays remain custom-only; built-in booleans select among available checkers. */\nexport function defaultHealthCheckers(\n options: BuiltInHealthCheckerOptions = {},\n cwd = process.cwd(),\n): FileHealthCheckerContract[] {\n const checkers: FileHealthCheckerContract[] = [];\n if (options.typescript !== false) checkers.push(new TypescriptHealthChecker());\n if (options.eslint === true) {\n warnEslintDeprecation();\n if (hasEslint(cwd)) checkers.push(new EslintHealthChecker());\n }\n if (options.oxlint !== false && findOxlint(cwd)) checkers.push(new OxlintHealthChecker());\n return checkers;\n}\n"],"mappings":";;;;;;;;AAaA,SAAgB,sBACd,UAAuC,CAAC,GACxC,MAAM,QAAQ,IAAI,GACW;CAC7B,MAAM,WAAwC,CAAC;CAC/C,IAAI,QAAQ,eAAe,OAAO,SAAS,KAAK,IAAI,wBAAwB,CAAC;CAC7E,IAAI,QAAQ,WAAW,MAAM;EAC3B,sBAAsB;EACtB,IAAI,UAAU,GAAG,GAAG,SAAS,KAAK,IAAI,oBAAoB,CAAC;CAC7D;CACA,IAAI,QAAQ,WAAW,SAAS,WAAW,GAAG,GAAG,SAAS,KAAK,IAAI,oBAAoB,CAAC;CACxF,OAAO;AACT"}