@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
package/CHANGELOG.md CHANGED
@@ -6,6 +6,44 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  > ⚠ **Versioning: `@warlock.js/*` does not follow SemVer strictly — breaking changes may ship in a minor.** This is a deliberate decision, not an oversight: the framework is pre-adoption and the cost of a major per behaviour fix currently outweighs the benefit. **Pin an exact version or a tilde range (`~4.13.0`) if you need to opt into changes rather than receive them.** Every breaking change is marked **BREAKING** in its entry and summarised in an _Upgrading_ section at the top of the release. **This policy will change once the framework has consumers beyond its author.**
8
8
 
9
+ ## 5.33.0 - 2026-10-19
10
+
11
+ ### Added
12
+
13
+ - `oxc-toolchain`, a 5.33.0 `warlock upgrade` codemod, migrates generated configs, package scripts/dependencies, editor settings and literal dev health switches to Oxlint/Oxfmt. Customized files are retained with file:line review guidance; additions and deletions honor `--dry-run` and `--json`.
14
+
15
+ ### Changed
16
+
17
+ - **BREAKING** ESLint no longer starts from config detection alone in the dev server or `warlock check`. TypeScript and configured Oxlint remain the default health checkers; `check` defaults to Oxlint.
18
+
19
+ ### Deprecated
20
+
21
+ - ESLint/Prettier support is removed in 5.34.0. Explicit ESLint opt-ins emit one deprecation warning per process on stderr with migration instructions.
22
+
23
+ ### Upgrading
24
+
25
+ - Run `npx warlock upgrade --from 5.32 --to 5.33.0` (preview with `--dry-run`), install dependencies and review customized configs. To retain legacy lint checks temporarily, set `devServer.healthCheckers.eslint: true` and select `warlock check --lint eslint`. See the upgrade skill's "5.32 to 5.33" section.
26
+ - `broadcastSocketEvent(event, ...args)` dispatches through the existing socket connector's adapter and returns `false` when no socket server is available. Web page-cache invalidations use this path to notify clients across processes configured with a shared adapter.
27
+ - Add app-scoped `secrets.get/set/remove/has` and `environmentName`, also exported from `@warlock.js/core/secrets`, with lazy optional OS keychain storage, read-only scoped environment lookup, authenticated encrypted owner-only files, typed errors, logger masking and explicit memory backends.
28
+ - Add `secretsFake` to the default test fakes, with isolated memory storage and assertions that never reveal credentials; opt out with `tests.fakes: { secrets: false }`.
29
+ - `warlock ai measure --model <id> [--tools <module>] [--system <file>] [--skills <dir>] [--json]` lazily loads AI and reports per-part input tokens and priced USD. Supports provider-qualified IDs, configured model modules, and JSON output without app bootstrap.
30
+ - Optional `warlock pm2 init` generates `ecosystem.config.cjs` from app/build config with a `<project>-<app>-<env>` name, readiness URL, `wait_ready`, startup timeout and a kill timeout matched to `app.shutdownTimeout`. PM2 stays outside core's dependencies. Fork mode is the default; cluster mode requires a socket adapter and WebSocket-only transports when sockets are configured.
31
+ - `warlock pm2 reload [--id <pm2-id>]` verifies the exact app name, working directory and bundle path before reloading numeric PM2 ids sequentially. Document rolling reload limits, shared-server stops by PM2 id and logger output formatting.
32
+
33
+ ### Changed
34
+
35
+ - Completed application boot now sends PM2's `"ready"` IPC message without disconnecting its channel; the existing `warlock start` readiness protocol remains supported. Cluster workers skip the exclusive port preflight so they can share their primary's listener during rolling reloads.
36
+
37
+ ### Fixed
38
+
39
+ - Type the socket connector’s raw HTTP(S) server across asynchronous listener startup; keep native-keyring test doubles declaration-safe and cover the documented `check --lint` option.
40
+ - Socket-only connector boot awaits its listener and rejects bind failures before application readiness can be announced.
41
+
42
+ ### Upgrading
43
+
44
+ - Rebuild production bundles to include PM2 readiness before using `wait_ready`. Generate with the target `NODE_ENV`, review the ecosystem file, and start the bundle directly under PM2. Cluster sockets require `socket.adapter`, `socket.options.transports: ["websocket"]` and WebSocket-only clients; polling deployments need sticky routing to individual listeners. Fork reloads can interrupt traffic.
45
+ - Add `warlock add process` with exact family dependency, typed supervisor config, application recovery/shutdown hooks, optional devtools registration, next steps and idempotent file generation.
46
+
9
47
  ## 5.32.0 - 2026-10-18
10
48
 
11
49
  ### Added
@@ -4,6 +4,14 @@ export type AppConfigurations = {
4
4
  * App name
5
5
  */
6
6
  appName?: string;
7
+ /**
8
+ * Overall connector shutdown budget in milliseconds. PM2 config generation
9
+ * uses the same value for `kill_timeout`. Allow enough time for HTTP draining
10
+ * and the remaining connectors; tune `http.gracefulShutdown.timeout` below it.
11
+ *
12
+ * @default 10000
13
+ */
14
+ shutdownTimeout?: number;
7
15
  /**
8
16
  * Default locale code
9
17
  *
@@ -1 +1 @@
1
- {"version":3,"file":"application-config-types.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/application/application-config-types.ts"],"mappings":";YAAY;;;;EAIV;;;;;;EAMA;;;;;;EAMA;;;;;;;;;EASA;;;;EAIA;;;;EAIA"}
1
+ {"version":3,"file":"application-config-types.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/application/application-config-types.ts"],"mappings":";YAAY;;;;EAIV;;;;;;;;EAQA;;;;;;EAMA;;;;;;EAMA;;;;;;;;;EASA;;;;EAIA;;;;EAIA"}
@@ -41,7 +41,7 @@ export type BootSignal = {
41
41
  * Environment flag `warlock start` sets on the process it supervises.
42
42
  *
43
43
  * An IPC channel alone is NOT proof that the parent speaks this protocol —
44
- * pm2, a vitest worker fork, and any custom supervisor all hand their child a
44
+ * a vitest worker fork or a custom supervisor may hand their child a
45
45
  * channel they use for their own messages. Writing to one of those, and then
46
46
  * closing it, corrupts someone else's protocol and can kill the process. The
47
47
  * flag is the handshake: only a parent that set it gets signalled.
@@ -50,14 +50,14 @@ export declare const BOOT_SIGNAL_ENV_KEY = "WARLOCK_BOOT_SIGNAL";
50
50
  /**
51
51
  * Report a completed boot to the parent process, if one is supervising.
52
52
  *
53
- * Two conditions must both hold: the process was spawned with an `ipc` stdio
54
- * channel (`process.send` exists), and the parent identified itself with
55
- * {@link BOOT_SIGNAL_ENV_KEY}. A bundle run directly (`node app.mjs`, a Docker
56
- * `CMD`) or under a foreign supervisor is a no-op, so the signal never changes
57
- * how a standalone process behaves and never touches a channel we don't own.
53
+ * The versioned signal requires an IPC channel (`process.send` exists) and
54
+ * the parent's {@link BOOT_SIGNAL_ENV_KEY} handshake. With IPC and `pm_id`,
55
+ * PM2 instead receives its documented `"ready"` message and keeps its channel
56
+ * open for logs and worker coordination.
57
+ * Standalone bundles and unrecognized supervisors remain silent.
58
58
  *
59
- * The channel is closed as soon as the message is flushed. An open IPC channel
60
- * holds a `ref` on the child's event loop, which would keep a finished or
59
+ * The `warlock start` channel is closed as soon as its message is flushed.
60
+ * An open IPC channel holds a `ref` on the child's event loop, keeping a finished or
61
61
  * wedged process alive and indistinguishable from a healthy server — the exact
62
62
  * confusion this signal exists to remove. One message, then disconnect.
63
63
  *
@@ -1 +1 @@
1
- {"version":3,"file":"boot-signal.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/application/boot-signal.ts"],"mappings":";;;;;;;YAOY;;;;;;;;qBASC;;;;;;;;;YAUD;EACV,MAAM;EACN;;EAEA;;EAEA;EACA,aAAa;EACb,iBAAiB;EACjB;;EAEA;;EAEA;;;;;;;;;;;qBAYW;;;;;;;;;;;;;;;;;;;wBAoBG,eAAe,QAAQ;;;;;;;;;;wBA4BvB,aAAa,mBAAmB,WAAW"}
1
+ {"version":3,"file":"boot-signal.d.mts","names":[],"sources":["../../../../../../../../packages/core/src/application/boot-signal.ts"],"mappings":";;;;;;;YAOY;;;;;;;;qBASC;;;;;;;;;YAUD;EACV,MAAM;EACN;;EAEA;;EAEA;EACA,aAAa;EACb,iBAAiB;EACjB;;EAEA;;EAEA;;;;;;;;;;;qBAYW;;;;;;;;;;;;;;;;;;;wBAoBG,eAAe,QAAQ;;;;;;;;;;wBA2CvB,aAAa,mBAAmB,WAAW"}
@@ -11,7 +11,7 @@ const BOOT_SIGNAL_VERSION = 1;
11
11
  * Environment flag `warlock start` sets on the process it supervises.
12
12
  *
13
13
  * An IPC channel alone is NOT proof that the parent speaks this protocol —
14
- * pm2, a vitest worker fork, and any custom supervisor all hand their child a
14
+ * a vitest worker fork or a custom supervisor may hand their child a
15
15
  * channel they use for their own messages. Writing to one of those, and then
16
16
  * closing it, corrupts someone else's protocol and can kill the process. The
17
17
  * flag is the handshake: only a parent that set it gets signalled.
@@ -20,14 +20,14 @@ const BOOT_SIGNAL_ENV_KEY = "WARLOCK_BOOT_SIGNAL";
20
20
  /**
21
21
  * Report a completed boot to the parent process, if one is supervising.
22
22
  *
23
- * Two conditions must both hold: the process was spawned with an `ipc` stdio
24
- * channel (`process.send` exists), and the parent identified itself with
25
- * {@link BOOT_SIGNAL_ENV_KEY}. A bundle run directly (`node app.mjs`, a Docker
26
- * `CMD`) or under a foreign supervisor is a no-op, so the signal never changes
27
- * how a standalone process behaves and never touches a channel we don't own.
23
+ * The versioned signal requires an IPC channel (`process.send` exists) and
24
+ * the parent's {@link BOOT_SIGNAL_ENV_KEY} handshake. With IPC and `pm_id`,
25
+ * PM2 instead receives its documented `"ready"` message and keeps its channel
26
+ * open for logs and worker coordination.
27
+ * Standalone bundles and unrecognized supervisors remain silent.
28
28
  *
29
- * The channel is closed as soon as the message is flushed. An open IPC channel
30
- * holds a `ref` on the child's event loop, which would keep a finished or
29
+ * The `warlock start` channel is closed as soon as its message is flushed.
30
+ * An open IPC channel holds a `ref` on the child's event loop, keeping a finished or
31
31
  * wedged process alive and indistinguishable from a healthy server — the exact
32
32
  * confusion this signal exists to remove. One message, then disconnect.
33
33
  *
@@ -36,7 +36,13 @@ const BOOT_SIGNAL_ENV_KEY = "WARLOCK_BOOT_SIGNAL";
36
36
  * healthy application.
37
37
  */
38
38
  function sendBootSignal(signal) {
39
- if (typeof process.send !== "function" || process.env["WARLOCK_BOOT_SIGNAL"] !== "1") return;
39
+ if (typeof process.send !== "function") return;
40
+ if (process.env["WARLOCK_BOOT_SIGNAL"] !== "1") {
41
+ if (process.env.pm_id !== void 0) try {
42
+ process.send("ready", void 0, void 0, () => {});
43
+ } catch {}
44
+ return;
45
+ }
40
46
  delete process.env[BOOT_SIGNAL_ENV_KEY];
41
47
  try {
42
48
  process.send(signal, void 0, void 0, () => {
@@ -1 +1 @@
1
- {"version":3,"file":"boot-signal.mjs","names":[],"sources":["../../../../../../../../packages/core/src/application/boot-signal.ts"],"sourcesContent":["import type { Environment, RuntimeStrategy } from \"../utils/environment\";\n\n/**\n * The only message type the boot channel carries today. Named rather than\n * inlined because both the sender (`sendBootSignal`) and the receiver\n * (`warlock start`) must agree on the exact literal.\n */\nexport type BootSignalType = \"warlock:ready\";\n\n/**\n * Schema version of {@link BootSignal}.\n *\n * Present from the first release on purpose: a version field cannot be added to\n * a shipped wire format without a flag day, and this one has to be able to\n * evolve while older CLIs are still supervising newer bundles.\n */\nexport const BOOT_SIGNAL_VERSION = 1;\n\n/**\n * What a booted child process tells its supervising parent over the IPC\n * channel `warlock start` opens for it.\n *\n * This is a wire contract, not an internal detail: `warlock start` prints its\n * success banner **only** when this arrives, so anything watching that banner\n * (a CI gate, a process supervisor) transitively depends on it.\n */\nexport type BootSignal = {\n type: BootSignalType;\n version: number;\n /** The application process, so a supervisor can address it directly. */\n pid: number;\n /** When the boot completed, ISO-8601. */\n at: string;\n environment: Environment;\n runtimeStrategy: RuntimeStrategy;\n bootDurationMs?: number;\n /** The bound http port, absent for an app with no http connector. */\n port?: number;\n /** The roles this process serves — see `Application.roles`. */\n roles: string[];\n};\n\n/**\n * Environment flag `warlock start` sets on the process it supervises.\n *\n * An IPC channel alone is NOT proof that the parent speaks this protocol —\n * pm2, a vitest worker fork, and any custom supervisor all hand their child a\n * channel they use for their own messages. Writing to one of those, and then\n * closing it, corrupts someone else's protocol and can kill the process. The\n * flag is the handshake: only a parent that set it gets signalled.\n */\nexport const BOOT_SIGNAL_ENV_KEY = \"WARLOCK_BOOT_SIGNAL\";\n\n/**\n * Report a completed boot to the parent process, if one is supervising.\n *\n * Two conditions must both hold: the process was spawned with an `ipc` stdio\n * channel (`process.send` exists), and the parent identified itself with\n * {@link BOOT_SIGNAL_ENV_KEY}. A bundle run directly (`node app.mjs`, a Docker\n * `CMD`) or under a foreign supervisor is a no-op, so the signal never changes\n * how a standalone process behaves and never touches a channel we don't own.\n *\n * The channel is closed as soon as the message is flushed. An open IPC channel\n * holds a `ref` on the child's event loop, which would keep a finished or\n * wedged process alive and indistinguishable from a healthy server — the exact\n * confusion this signal exists to remove. One message, then disconnect.\n *\n * Failure to send is deliberately swallowed: the parent may have already\n * detached or died, and a supervisor going away must never take down a\n * healthy application.\n */\nexport function sendBootSignal(signal: BootSignal): void {\n if (typeof process.send !== \"function\" || process.env[BOOT_SIGNAL_ENV_KEY] !== \"1\") {\n return;\n }\n\n // Consumed here so it cannot be inherited by anything this app itself spawns\n // and make a grandchild report readiness on a channel meant for someone else.\n delete process.env[BOOT_SIGNAL_ENV_KEY];\n\n try {\n process.send(signal, undefined, undefined, () => {\n process.disconnect?.();\n });\n } catch {\n // The channel closed between the check and the write — the app is up\n // regardless, and there is no longer anyone to tell.\n }\n}\n\n/**\n * Narrow an arbitrary IPC payload to a {@link BootSignal}.\n *\n * The parent receives whatever the child chooses to send, so the message is\n * untrusted input and is validated before it is allowed to flip the parent's\n * \"the server is up\" state. A *newer* version than this CLI knows is still\n * accepted — readiness is readiness, and refusing it would make an old\n * supervisor report a healthy app as failed.\n */\nexport function isBootSignal(message: unknown): message is BootSignal {\n if (typeof message !== \"object\" || message === null) {\n return false;\n }\n\n const candidate = message as { type?: unknown; version?: unknown };\n\n return candidate.type === \"warlock:ready\" && typeof candidate.version === \"number\";\n}\n"],"mappings":";;;;;;;;AAgBA,MAAa,sBAAsB;;;;;;;;;;AAmCnC,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;;AAoBnC,SAAgB,eAAe,QAA0B;CACvD,IAAI,OAAO,QAAQ,SAAS,cAAc,QAAQ,+BAA6B,KAC7E;CAKF,OAAO,QAAQ,IAAI;CAEnB,IAAI;EACF,QAAQ,KAAK,QAAQ,QAAW,cAAiB;GAC/C,QAAQ,aAAa;EACvB,CAAC;CACH,QAAQ,CAGR;AACF;;;;;;;;;;AAWA,SAAgB,aAAa,SAAyC;CACpE,IAAI,OAAO,YAAY,YAAY,YAAY,MAC7C,OAAO;CAGT,MAAM,YAAY;CAElB,OAAO,UAAU,SAAS,mBAAmB,OAAO,UAAU,YAAY;AAC5E"}
1
+ {"version":3,"file":"boot-signal.mjs","names":[],"sources":["../../../../../../../../packages/core/src/application/boot-signal.ts"],"sourcesContent":["import type { Environment, RuntimeStrategy } from \"../utils/environment\";\n\n/**\n * The only message type the boot channel carries today. Named rather than\n * inlined because both the sender (`sendBootSignal`) and the receiver\n * (`warlock start`) must agree on the exact literal.\n */\nexport type BootSignalType = \"warlock:ready\";\n\n/**\n * Schema version of {@link BootSignal}.\n *\n * Present from the first release on purpose: a version field cannot be added to\n * a shipped wire format without a flag day, and this one has to be able to\n * evolve while older CLIs are still supervising newer bundles.\n */\nexport const BOOT_SIGNAL_VERSION = 1;\n\n/**\n * What a booted child process tells its supervising parent over the IPC\n * channel `warlock start` opens for it.\n *\n * This is a wire contract, not an internal detail: `warlock start` prints its\n * success banner **only** when this arrives, so anything watching that banner\n * (a CI gate, a process supervisor) transitively depends on it.\n */\nexport type BootSignal = {\n type: BootSignalType;\n version: number;\n /** The application process, so a supervisor can address it directly. */\n pid: number;\n /** When the boot completed, ISO-8601. */\n at: string;\n environment: Environment;\n runtimeStrategy: RuntimeStrategy;\n bootDurationMs?: number;\n /** The bound http port, absent for an app with no http connector. */\n port?: number;\n /** The roles this process serves — see `Application.roles`. */\n roles: string[];\n};\n\n/**\n * Environment flag `warlock start` sets on the process it supervises.\n *\n * An IPC channel alone is NOT proof that the parent speaks this protocol —\n * a vitest worker fork or a custom supervisor may hand their child a\n * channel they use for their own messages. Writing to one of those, and then\n * closing it, corrupts someone else's protocol and can kill the process. The\n * flag is the handshake: only a parent that set it gets signalled.\n */\nexport const BOOT_SIGNAL_ENV_KEY = \"WARLOCK_BOOT_SIGNAL\";\n\n/**\n * Report a completed boot to the parent process, if one is supervising.\n *\n * The versioned signal requires an IPC channel (`process.send` exists) and\n * the parent's {@link BOOT_SIGNAL_ENV_KEY} handshake. With IPC and `pm_id`,\n * PM2 instead receives its documented `\"ready\"` message and keeps its channel\n * open for logs and worker coordination.\n * Standalone bundles and unrecognized supervisors remain silent.\n *\n * The `warlock start` channel is closed as soon as its message is flushed.\n * An open IPC channel holds a `ref` on the child's event loop, keeping a finished or\n * wedged process alive and indistinguishable from a healthy server — the exact\n * confusion this signal exists to remove. One message, then disconnect.\n *\n * Failure to send is deliberately swallowed: the parent may have already\n * detached or died, and a supervisor going away must never take down a\n * healthy application.\n */\nexport function sendBootSignal(signal: BootSignal): void {\n if (typeof process.send !== \"function\") {\n return;\n }\n\n // PM2 injects pm_id (including \"0\") in both fork and cluster mode. Its\n // ProcessContainer uses this channel for logs too: never disconnect it.\n // https://github.com/Unitech/pm2/blob/master/lib/ProcessContainer.js\n // https://pm2.keymetrics.io/docs/usage/signals-clean-restart/\n if (process.env[BOOT_SIGNAL_ENV_KEY] !== \"1\") {\n if (process.env.pm_id !== undefined) {\n try {\n process.send(\"ready\", undefined, undefined, () => {});\n } catch {\n // A detached supervisor must not take down an already booted app.\n }\n }\n return;\n }\n\n // Consumed here so it cannot be inherited by anything this app itself spawns\n // and make a grandchild report readiness on a channel meant for someone else.\n delete process.env[BOOT_SIGNAL_ENV_KEY];\n\n try {\n process.send(signal, undefined, undefined, () => {\n process.disconnect?.();\n });\n } catch {\n // The channel closed between the check and the write — the app is up\n // regardless, and there is no longer anyone to tell.\n }\n}\n\n/**\n * Narrow an arbitrary IPC payload to a {@link BootSignal}.\n *\n * The parent receives whatever the child chooses to send, so the message is\n * untrusted input and is validated before it is allowed to flip the parent's\n * \"the server is up\" state. A *newer* version than this CLI knows is still\n * accepted — readiness is readiness, and refusing it would make an old\n * supervisor report a healthy app as failed.\n */\nexport function isBootSignal(message: unknown): message is BootSignal {\n if (typeof message !== \"object\" || message === null) {\n return false;\n }\n\n const candidate = message as { type?: unknown; version?: unknown };\n\n return candidate.type === \"warlock:ready\" && typeof candidate.version === \"number\";\n}\n"],"mappings":";;;;;;;;AAgBA,MAAa,sBAAsB;;;;;;;;;;AAmCnC,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;;AAoBnC,SAAgB,eAAe,QAA0B;CACvD,IAAI,OAAO,QAAQ,SAAS,YAC1B;CAOF,IAAI,QAAQ,+BAA6B,KAAK;EAC5C,IAAI,QAAQ,IAAI,UAAU,QACxB,IAAI;GACF,QAAQ,KAAK,SAAS,QAAW,cAAiB,CAAC,CAAC;EACtD,QAAQ,CAER;EAEF;CACF;CAIA,OAAO,QAAQ,IAAI;CAEnB,IAAI;EACF,QAAQ,KAAK,QAAQ,QAAW,cAAiB;GAC/C,QAAQ,aAAa;EACvB,CAAC;CACH,QAAQ,CAGR;AACF;;;;;;;;;;AAWA,SAAgB,aAAa,SAAyC;CACpE,IAAI,OAAO,YAAY,YAAY,YAAY,MAC7C,OAAO;CAGT,MAAM,YAAY;CAElB,OAAO,UAAU,SAAS,mBAAmB,OAAO,UAAU,YAAY;AAC5E"}
@@ -0,0 +1,140 @@
1
+ import { withConsoleOnStderr } from "../routes/with-console-on-stderr.mjs";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { readFile, stat } from "node:fs/promises";
5
+ import { resolve as resolve$1 } from "import-meta-resolve";
6
+
7
+ //#region ../../packages/core/src/cli/commands/ai/ai-command.action.ts
8
+ /** Resolve from the caller's project, including optional provider packages. */
9
+ async function loadMeasureModule(specifier, cwd = process.cwd()) {
10
+ const parent = pathToFileURL(path.join(cwd, "package.json")).href;
11
+ return import(resolve$1(specifier, parent));
12
+ }
13
+ function object(value) {
14
+ return value !== null && typeof value === "object";
15
+ }
16
+ function isMeasureModule(value) {
17
+ return typeof value.measure === "function" && typeof value.skills === "function";
18
+ }
19
+ function isModel(value) {
20
+ return object(value) && typeof value.name === "string" && typeof value.provider === "string" && typeof value.complete === "function" && typeof value.stream === "function" && (value.countTokens === void 0 || typeof value.countTokens === "function") && (value.pricing === void 0 || object(value.pricing) && typeof value.pricing.input === "number" && Number.isFinite(value.pricing.input) && value.pricing.input >= 0 && typeof value.pricing.output === "number" && Number.isFinite(value.pricing.output) && value.pricing.output >= 0);
21
+ }
22
+ function isTool(value) {
23
+ return object(value) && typeof value.name === "string" && typeof value.description === "string" && typeof value.execute === "function" && (value.input === void 0 || object(value.input) && object(value.input["~standard"]) && typeof value.input["~standard"].validate === "function");
24
+ }
25
+ /** SDK constructor/model signatures verified against the first-party adapter sources. */
26
+ async function resolveMeasureModel(id, modelModule, load = loadMeasureModule, env = process.env) {
27
+ if (modelModule) {
28
+ const exported = await load(modelModule);
29
+ const model = typeof exported.resolveModel === "function" ? await exported.resolveModel(id) : exported.model ?? exported.default;
30
+ if (!isModel(model)) throw new Error("--model-module must export a model or resolveModel(id) returning a model.");
31
+ return model;
32
+ }
33
+ const separator = id.indexOf(":");
34
+ const provider = separator < 0 ? "openai" : id.slice(0, separator);
35
+ const name = separator < 0 ? id : id.slice(separator + 1);
36
+ const adapters = {
37
+ openai: {
38
+ package: "@warlock.js/ai-openai",
39
+ sdk: "OpenAISDK",
40
+ key: "OPENAI_API_KEY"
41
+ },
42
+ anthropic: {
43
+ package: "@warlock.js/ai-anthropic",
44
+ sdk: "AnthropicSDK",
45
+ key: "ANTHROPIC_API_KEY"
46
+ },
47
+ google: {
48
+ package: "@warlock.js/ai-google",
49
+ sdk: "GoogleSDK",
50
+ key: "GEMINI_API_KEY"
51
+ }
52
+ };
53
+ const adapter = Object.hasOwn(adapters, provider) ? adapters[provider] : void 0;
54
+ if (!adapter || !name) throw new Error("Use a model ID, openai:<id>, anthropic:<id>, google:<id>, or --model-module for a custom provider.");
55
+ const apiKey = env[adapter.key];
56
+ if (!apiKey) throw new Error(`Set ${adapter.key}, or supply a configured model with --model-module.`);
57
+ let exported;
58
+ try {
59
+ exported = await load(adapter.package);
60
+ } catch (cause) {
61
+ throw new Error(`Cannot load ${adapter.package}. Install it in this project or use --model-module.`, { cause });
62
+ }
63
+ const Constructor = exported[adapter.sdk];
64
+ if (typeof Constructor !== "function") throw new Error(`${adapter.package} does not export ${adapter.sdk}.`);
65
+ const model = new Constructor({ apiKey }).model({
66
+ name,
67
+ ...provider === "openai" ? { api: "responses" } : {}
68
+ });
69
+ if (!isModel(model)) throw new Error(`${adapter.package} returned an invalid model.`);
70
+ return model;
71
+ }
72
+ function formatMeasurement(report) {
73
+ const lines = [`${report.provider}:${report.model} — input per call`, "Part Tokens Source USD"];
74
+ const row = (name, part) => `${name}\t${part.tokens}\t${part.source}\t${part.costUSD === void 0 ? "unknown" : part.costUSD.toFixed(8)}`;
75
+ for (const [name, part] of Object.entries(report.parts)) lines.push(row(name, part));
76
+ lines.push(row("total", report.total));
77
+ if (report.probe) lines.push("Messages include a user probe: \".\"");
78
+ lines.push("USD uses uncached input pricing; output, cache adjustments and hosted-tool fees are excluded.");
79
+ return lines.join("\n");
80
+ }
81
+ async function aiCommandAction({ args, options }, dependencies = {}) {
82
+ if (args.length !== 1 || args[0] !== "measure") throw new Error("Usage: warlock ai measure --model <id> [--tools <module>] [--system <file>] [--skills <dir>] [--json]");
83
+ const stringOption = (name, required = false) => {
84
+ const value = options[name];
85
+ if (value === void 0 && !required) return void 0;
86
+ if (typeof value !== "string" || !value.trim()) throw new Error(`--${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)} requires a value.`);
87
+ return value;
88
+ };
89
+ const id = stringOption("model", true);
90
+ if (!id) throw new Error("--model is required.");
91
+ const modelModule = stringOption("modelModule");
92
+ const toolsModule = stringOption("tools");
93
+ const systemFile = stringOption("system");
94
+ const skillsDir = stringOption("skills");
95
+ const cwd = dependencies.cwd ?? process.cwd();
96
+ const load = dependencies.load ?? ((specifier) => loadMeasureModule(specifier, cwd));
97
+ const calculate = async () => {
98
+ let exported;
99
+ try {
100
+ exported = await load("@warlock.js/ai");
101
+ } catch (cause) {
102
+ throw new Error("Cannot load @warlock.js/ai. Install it in this project to use warlock ai measure.", { cause });
103
+ }
104
+ if (!isMeasureModule(exported)) throw new Error("warlock ai measure requires @warlock.js/ai 5.33.0 or newer.");
105
+ const aiPackage = exported;
106
+ const model = await resolveMeasureModel(id, modelModule, load);
107
+ let tools = [];
108
+ if (toolsModule) {
109
+ const module = await load(toolsModule);
110
+ const entries = module.tools ?? module.default;
111
+ if (!Array.isArray(entries) || !entries.every(isTool)) throw new Error("--tools must export an array of tool configs as tools or default.");
112
+ tools = entries;
113
+ }
114
+ const system = systemFile ? await readFile(path.resolve(cwd, systemFile), "utf8") : void 0;
115
+ let skills;
116
+ if (skillsDir) {
117
+ const directory = path.resolve(cwd, skillsDir);
118
+ if (!(await stat(directory)).isDirectory()) throw new Error("--skills must name a directory.");
119
+ skills = aiPackage.skills({
120
+ name: "measured-skills",
121
+ sources: [{
122
+ type: "directory",
123
+ path: directory
124
+ }]
125
+ });
126
+ }
127
+ return aiPackage.measure({
128
+ model,
129
+ tools,
130
+ system,
131
+ skills
132
+ });
133
+ };
134
+ const report = options.json === true ? await withConsoleOnStderr(calculate) : await calculate();
135
+ console.log(options.json === true ? JSON.stringify(report, null, 2) : formatMeasurement(report));
136
+ }
137
+
138
+ //#endregion
139
+ export { aiCommandAction, formatMeasurement, loadMeasureModule, resolveMeasureModel };
140
+ //# sourceMappingURL=ai-command.action.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-command.action.mjs","names":["resolveModule"],"sources":["../../../../../../../../../../packages/core/src/cli/commands/ai/ai-command.action.ts"],"sourcesContent":["import { readFile, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { resolve as resolveModule } from \"import-meta-resolve\";\nimport type { Measurement, ModelContract, ToolConfig } from \"@warlock.js/ai\";\nimport type { CommandActionData } from \"../../../commands/types\";\nimport { withConsoleOnStderr } from \"../routes/with-console-on-stderr\";\n\ntype ModuleExports = Record<string, unknown>;\ntype MeasureModule = Pick<typeof import(\"@warlock.js/ai\"), \"measure\" | \"skills\">;\ntype ModuleLoader = (specifier: string) => Promise<ModuleExports>;\ntype SDKConstructor = new (config: { apiKey: string }) => {\n model(config: { name: string; api?: \"responses\" }): ModelContract;\n};\n\n/** Resolve from the caller's project, including optional provider packages. */\nexport async function loadMeasureModule(\n specifier: string,\n cwd = process.cwd(),\n): Promise<ModuleExports> {\n const parent = pathToFileURL(path.join(cwd, \"package.json\")).href;\n return import(resolveModule(specifier, parent));\n}\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\";\n}\n\nfunction isMeasureModule(value: ModuleExports): value is ModuleExports & MeasureModule {\n return typeof value.measure === \"function\" && typeof value.skills === \"function\";\n}\n\nfunction isModel(value: unknown): value is ModelContract {\n return (\n object(value) &&\n typeof value.name === \"string\" &&\n typeof value.provider === \"string\" &&\n typeof value.complete === \"function\" &&\n typeof value.stream === \"function\" &&\n (value.countTokens === undefined || typeof value.countTokens === \"function\") &&\n (value.pricing === undefined ||\n (object(value.pricing) &&\n typeof value.pricing.input === \"number\" &&\n Number.isFinite(value.pricing.input) &&\n value.pricing.input >= 0 &&\n typeof value.pricing.output === \"number\" &&\n Number.isFinite(value.pricing.output) &&\n value.pricing.output >= 0))\n );\n}\n\nfunction isTool(value: unknown): value is ToolConfig<unknown, unknown> {\n return (\n object(value) &&\n typeof value.name === \"string\" &&\n typeof value.description === \"string\" &&\n typeof value.execute === \"function\" &&\n (value.input === undefined ||\n (object(value.input) &&\n object(value.input[\"~standard\"]) &&\n typeof value.input[\"~standard\"].validate === \"function\"))\n );\n}\n\n/** SDK constructor/model signatures verified against the first-party adapter sources. */\nexport async function resolveMeasureModel(\n id: string,\n modelModule: string | undefined,\n load: ModuleLoader = loadMeasureModule,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<ModelContract> {\n if (modelModule) {\n const exported = await load(modelModule);\n const model: unknown =\n typeof exported.resolveModel === \"function\"\n ? await exported.resolveModel(id)\n : (exported.model ?? exported.default);\n if (!isModel(model))\n throw new Error(\"--model-module must export a model or resolveModel(id) returning a model.\");\n return model;\n }\n const separator = id.indexOf(\":\");\n const provider = separator < 0 ? \"openai\" : id.slice(0, separator);\n const name = separator < 0 ? id : id.slice(separator + 1);\n const adapters: Record<string, { package: string; sdk: string; key: string }> = {\n openai: { package: \"@warlock.js/ai-openai\", sdk: \"OpenAISDK\", key: \"OPENAI_API_KEY\" },\n anthropic: {\n package: \"@warlock.js/ai-anthropic\",\n sdk: \"AnthropicSDK\",\n key: \"ANTHROPIC_API_KEY\",\n },\n google: { package: \"@warlock.js/ai-google\", sdk: \"GoogleSDK\", key: \"GEMINI_API_KEY\" },\n };\n const adapter = Object.hasOwn(adapters, provider) ? adapters[provider] : undefined;\n if (!adapter || !name)\n throw new Error(\n \"Use a model ID, openai:<id>, anthropic:<id>, google:<id>, or --model-module for a custom provider.\",\n );\n const apiKey = env[adapter.key];\n if (!apiKey)\n throw new Error(`Set ${adapter.key}, or supply a configured model with --model-module.`);\n let exported: ModuleExports;\n try {\n exported = await load(adapter.package);\n } catch (cause) {\n throw new Error(\n `Cannot load ${adapter.package}. Install it in this project or use --model-module.`,\n { cause },\n );\n }\n const Constructor = exported[adapter.sdk];\n if (typeof Constructor !== \"function\")\n throw new Error(`${adapter.package} does not export ${adapter.sdk}.`);\n const sdk = new (Constructor as SDKConstructor)({ apiKey });\n // The Responses adapter exposes countTokens; the Chat adapter does not.\n const model = sdk.model({\n name,\n ...(provider === \"openai\" ? { api: \"responses\" as const } : {}),\n });\n if (!isModel(model)) throw new Error(`${adapter.package} returned an invalid model.`);\n return model;\n}\n\nexport function formatMeasurement(report: Measurement): string {\n const lines = [\n `${report.provider}:${report.model} — input per call`,\n \"Part\\tTokens\\tSource\\tUSD\",\n ];\n const row = (name: string, part: Measurement[\"total\"]) =>\n `${name}\\t${part.tokens}\\t${part.source}\\t${part.costUSD === undefined ? \"unknown\" : part.costUSD.toFixed(8)}`;\n for (const [name, part] of Object.entries(report.parts)) lines.push(row(name, part));\n lines.push(row(\"total\", report.total));\n if (report.probe) lines.push('Messages include a user probe: \".\"');\n lines.push(\n \"USD uses uncached input pricing; output, cache adjustments and hosted-tool fees are excluded.\",\n );\n return lines.join(\"\\n\");\n}\n\nexport async function aiCommandAction(\n { args, options }: CommandActionData,\n dependencies: { load?: ModuleLoader; cwd?: string } = {},\n): Promise<void> {\n if (args.length !== 1 || args[0] !== \"measure\")\n throw new Error(\n \"Usage: warlock ai measure --model <id> [--tools <module>] [--system <file>] [--skills <dir>] [--json]\",\n );\n const stringOption = (name: string, required = false): string | undefined => {\n const value = options[name];\n if (value === undefined && !required) return undefined;\n if (typeof value !== \"string\" || !value.trim())\n throw new Error(\n `--${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)} requires a value.`,\n );\n return value;\n };\n const id = stringOption(\"model\", true);\n if (!id) throw new Error(\"--model is required.\");\n const modelModule = stringOption(\"modelModule\");\n const toolsModule = stringOption(\"tools\");\n const systemFile = stringOption(\"system\");\n const skillsDir = stringOption(\"skills\");\n const cwd = dependencies.cwd ?? process.cwd();\n const load = dependencies.load ?? ((specifier) => loadMeasureModule(specifier, cwd));\n const calculate = async (): Promise<Measurement> => {\n let exported: ModuleExports;\n try {\n exported = await load(\"@warlock.js/ai\");\n } catch (cause) {\n throw new Error(\n \"Cannot load @warlock.js/ai. Install it in this project to use warlock ai measure.\",\n { cause },\n );\n }\n // The optional peer stays behind the action; unrelated CLI commands never import AI.\n if (!isMeasureModule(exported)) {\n throw new Error(\"warlock ai measure requires @warlock.js/ai 5.33.0 or newer.\");\n }\n const aiPackage = exported;\n const model = await resolveMeasureModel(id, modelModule, load);\n let tools: ToolConfig<unknown, unknown>[] = [];\n if (toolsModule) {\n const module = await load(toolsModule);\n const entries: unknown = module.tools ?? module.default;\n if (!Array.isArray(entries) || !entries.every(isTool))\n throw new Error(\"--tools must export an array of tool configs as tools or default.\");\n tools = entries;\n }\n const system = systemFile ? await readFile(path.resolve(cwd, systemFile), \"utf8\") : undefined;\n let skills: ReturnType<typeof aiPackage.skills> | undefined;\n if (skillsDir) {\n const directory = path.resolve(cwd, skillsDir);\n if (!(await stat(directory)).isDirectory())\n throw new Error(\"--skills must name a directory.\");\n skills = aiPackage.skills({\n name: \"measured-skills\",\n sources: [{ type: \"directory\", path: directory }],\n });\n }\n return aiPackage.measure({ model, tools, system, skills });\n };\n const report = options.json === true ? await withConsoleOnStderr(calculate) : await calculate();\n console.log(options.json === true ? JSON.stringify(report, null, 2) : formatMeasurement(report));\n}\n"],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { command } from "../../commands/cli-command.mjs";
2
+
3
+ //#region ../../packages/core/src/cli/commands/ai.command.ts
4
+ /** Built in so installing AI does not require cli.commands registration. */
5
+ const aiCommand = command({
6
+ name: "ai <action>",
7
+ description: "Measure AI input tokens and uncached input USD per call",
8
+ preload: {},
9
+ options: [
10
+ {
11
+ text: "--model",
12
+ description: "Model ID (OpenAI), or openai: / anthropic: / google: ID",
13
+ type: "string",
14
+ required: true
15
+ },
16
+ {
17
+ text: "--model-module",
18
+ description: "Module exporting a model or resolveModel(id)",
19
+ type: "string"
20
+ },
21
+ {
22
+ text: "--tools",
23
+ description: "Module exporting a tools array (named tools or default)",
24
+ type: "string"
25
+ },
26
+ {
27
+ text: "--system",
28
+ description: "UTF-8 system prompt file",
29
+ type: "string"
30
+ },
31
+ {
32
+ text: "--skills",
33
+ description: "Directory containing skill folders with SKILL.md files",
34
+ type: "string"
35
+ },
36
+ {
37
+ text: "--json",
38
+ description: "Print one JSON report to stdout",
39
+ type: "boolean"
40
+ }
41
+ ],
42
+ action: async (data) => {
43
+ const { aiCommandAction } = await import("./ai/ai-command.action.mjs");
44
+ await aiCommandAction(data);
45
+ }
46
+ });
47
+
48
+ //#endregion
49
+ export { aiCommand };
50
+ //# sourceMappingURL=ai.command.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.command.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/cli/commands/ai.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\n\n/** Built in so installing AI does not require cli.commands registration. */\nexport const aiCommand = command({\n name: \"ai <action>\",\n description: \"Measure AI input tokens and uncached input USD per call\",\n preload: {},\n options: [\n {\n text: \"--model\",\n description: \"Model ID (OpenAI), or openai: / anthropic: / google: ID\",\n type: \"string\",\n required: true,\n },\n {\n text: \"--model-module\",\n description: \"Module exporting a model or resolveModel(id)\",\n type: \"string\",\n },\n {\n text: \"--tools\",\n description: \"Module exporting a tools array (named tools or default)\",\n type: \"string\",\n },\n { text: \"--system\", description: \"UTF-8 system prompt file\", type: \"string\" },\n {\n text: \"--skills\",\n description: \"Directory containing skill folders with SKILL.md files\",\n type: \"string\",\n },\n { text: \"--json\", description: \"Print one JSON report to stdout\", type: \"boolean\" },\n ],\n action: async (data) => {\n const { aiCommandAction } = await import(\"./ai/ai-command.action\");\n await aiCommandAction(data);\n },\n});\n"],"mappings":";;;;AAGA,MAAa,YAAY,QAAQ;CAC/B,MAAM;CACN,aAAa;CACb,SAAS,CAAC;CACV,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;GACN,UAAU;EACZ;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GAAE,MAAM;GAAY,aAAa;GAA4B,MAAM;EAAS;EAC5E;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GAAE,MAAM;GAAU,aAAa;GAAmC,MAAM;EAAU;CACpF;CACA,QAAQ,OAAO,SAAS;EACtB,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,MAAM,gBAAgB,IAAI;CAC5B;AACF,CAAC"}
@@ -30,6 +30,8 @@ function parseSkippedSteps(value) {
30
30
  * @param data Parsed CLI args: `changed`, `base`, `skip`, `project`, `json`.
31
31
  */
32
32
  async function checkCommandAction({ options }) {
33
+ const lint = stringOption(options.lint);
34
+ if (lint !== void 0 && lint !== "oxlint" && lint !== "eslint") throw new Error("Unknown lint tool. Use --lint oxlint or --lint eslint (deprecated).");
33
35
  const base = stringOption(options.base);
34
36
  const changed = options.changed === true || base !== void 0;
35
37
  const project = stringOption(options.project);
@@ -37,6 +39,7 @@ async function checkCommandAction({ options }) {
37
39
  const cliEntry = process.argv[1];
38
40
  const flags = [...changed ? ["--changed"] : [], ...base ? ["--base", base] : []];
39
41
  const report = await runCheck({
42
+ lint,
40
43
  changed,
41
44
  base,
42
45
  skip,
@@ -1 +1 @@
1
- {"version":3,"file":"check-command.action.mjs","names":[],"sources":["../../../../../../../../../../packages/core/src/cli/commands/check/check-command.action.ts"],"sourcesContent":["import type { CommandActionData } from \"../../../commands/types\";\nimport { CHECK_STEPS, type CheckStepName } from \"../../../tooling/check/check-report.types\";\nimport { runCheck } from \"../../../tooling/check/run-check\";\nimport { flushAndExit } from \"../../flush-and-exit\";\nimport { childTypecheckRunner } from \"./child-typecheck\";\nimport { formatCheckSummary, printProblems } from \"./format-problems\";\n\nfunction stringOption(value: string | number | boolean | undefined): string | undefined {\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : undefined;\n}\n\nfunction isStepName(value: string): value is CheckStepName {\n return (CHECK_STEPS as readonly string[]).includes(value);\n}\n\n/** Parse `--skip typecheck,lint`; throws on an unknown step name. */\nexport function parseSkippedSteps(value: string | undefined): CheckStepName[] {\n if (!value) return [];\n\n const names = value\n .split(\",\")\n .map((name) => name.trim())\n .filter((name) => name.length > 0);\n const unknown = names.filter((name) => !isStepName(name));\n\n if (unknown.length > 0) {\n throw new Error(\n `Unknown check step(s): ${unknown.join(\", \")}. Valid steps: ${CHECK_STEPS.join(\", \")}.`,\n );\n }\n\n return names.filter(isStepName);\n}\n\n/**\n * Action behind `warlock check`.\n *\n * Runs {@link runCheck} with the typecheck in a child `warlock typecheck`\n * process (so a heap overflow is reported, not fatal), prints the problems and\n * the step summary — or, with `--json`, exactly the report — and exits with\n * the report's code: 0 clean, 1 your code, 2 this machine.\n *\n * @param data Parsed CLI args: `changed`, `base`, `skip`, `project`, `json`.\n */\nexport async function checkCommandAction({ options }: CommandActionData): Promise<void> {\n const base = stringOption(options.base);\n const changed = options.changed === true || base !== undefined;\n const project = stringOption(options.project);\n const skip = parseSkippedSteps(stringOption(options.skip));\n const cliEntry = process.argv[1];\n const flags = [...(changed ? [\"--changed\"] : []), ...(base ? [\"--base\", base] : [])];\n\n const report = await runCheck({\n changed,\n base,\n skip,\n project,\n ...(cliEntry ? { typecheck: childTypecheckRunner(cliEntry, flags) } : {}),\n });\n\n if (options.json === true) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printProblems(report.problems);\n\n if (report.problems.length > 0) console.log(\"\");\n\n for (const line of formatCheckSummary(report)) {\n console.log(line);\n }\n }\n\n if (report.exitCode !== 0) {\n await flushAndExit(report.exitCode);\n }\n}\n"],"mappings":";;;;;;;AAOA,SAAS,aAAa,OAAkE;CACtF,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS,IAAI,MAAM,KAAK,IAAI;AAC/E;AAEA,SAAS,WAAW,OAAuC;CACzD,OAAQ,YAAkC,SAAS,KAAK;AAC1D;;AAGA,SAAgB,kBAAkB,OAA4C;CAC5E,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,MAAM,QAAQ,MACX,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,QAAQ,SAAS,KAAK,SAAS,CAAC;CACnC,MAAM,UAAU,MAAM,QAAQ,SAAS,CAAC,WAAW,IAAI,CAAC;CAExD,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,MACR,0BAA0B,QAAQ,KAAK,IAAI,EAAE,iBAAiB,YAAY,KAAK,IAAI,EAAE,EACvF;CAGF,OAAO,MAAM,OAAO,UAAU;AAChC;;;;;;;;;;;AAYA,eAAsB,mBAAmB,EAAE,WAA6C;CACtF,MAAM,OAAO,aAAa,QAAQ,IAAI;CACtC,MAAM,UAAU,QAAQ,YAAY,QAAQ,SAAS;CACrD,MAAM,UAAU,aAAa,QAAQ,OAAO;CAC5C,MAAM,OAAO,kBAAkB,aAAa,QAAQ,IAAI,CAAC;CACzD,MAAM,WAAW,QAAQ,KAAK;CAC9B,MAAM,QAAQ,CAAC,GAAI,UAAU,CAAC,WAAW,IAAI,CAAC,GAAI,GAAI,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,CAAE;CAEnF,MAAM,SAAS,MAAM,SAAS;EAC5B;EACA;EACA;EACA;EACA,GAAI,WAAW,EAAE,WAAW,qBAAqB,UAAU,KAAK,EAAE,IAAI,CAAC;CACzE,CAAC;CAED,IAAI,QAAQ,SAAS,MACnB,QAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;MACtC;EACL,cAAc,OAAO,QAAQ;EAE7B,IAAI,OAAO,SAAS,SAAS,GAAG,QAAQ,IAAI,EAAE;EAE9C,KAAK,MAAM,QAAQ,mBAAmB,MAAM,GAC1C,QAAQ,IAAI,IAAI;CAEpB;CAEA,IAAI,OAAO,aAAa,GACtB,MAAM,aAAa,OAAO,QAAQ;AAEtC"}
1
+ {"version":3,"file":"check-command.action.mjs","names":[],"sources":["../../../../../../../../../../packages/core/src/cli/commands/check/check-command.action.ts"],"sourcesContent":["import type { CommandActionData } from \"../../../commands/types\";\nimport { CHECK_STEPS, type CheckStepName } from \"../../../tooling/check/check-report.types\";\nimport { runCheck } from \"../../../tooling/check/run-check\";\nimport { flushAndExit } from \"../../flush-and-exit\";\nimport { childTypecheckRunner } from \"./child-typecheck\";\nimport { formatCheckSummary, printProblems } from \"./format-problems\";\n\nfunction stringOption(value: string | number | boolean | undefined): string | undefined {\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : undefined;\n}\n\nfunction isStepName(value: string): value is CheckStepName {\n return (CHECK_STEPS as readonly string[]).includes(value);\n}\n\n/** Parse `--skip typecheck,lint`; throws on an unknown step name. */\nexport function parseSkippedSteps(value: string | undefined): CheckStepName[] {\n if (!value) return [];\n\n const names = value\n .split(\",\")\n .map((name) => name.trim())\n .filter((name) => name.length > 0);\n const unknown = names.filter((name) => !isStepName(name));\n\n if (unknown.length > 0) {\n throw new Error(\n `Unknown check step(s): ${unknown.join(\", \")}. Valid steps: ${CHECK_STEPS.join(\", \")}.`,\n );\n }\n\n return names.filter(isStepName);\n}\n\n/**\n * Action behind `warlock check`.\n *\n * Runs {@link runCheck} with the typecheck in a child `warlock typecheck`\n * process (so a heap overflow is reported, not fatal), prints the problems and\n * the step summary — or, with `--json`, exactly the report — and exits with\n * the report's code: 0 clean, 1 your code, 2 this machine.\n *\n * @param data Parsed CLI args: `changed`, `base`, `skip`, `project`, `json`.\n */\nexport async function checkCommandAction({ options }: CommandActionData): Promise<void> {\n const lint = stringOption(options.lint);\n if (lint !== undefined && lint !== \"oxlint\" && lint !== \"eslint\") {\n throw new Error(\"Unknown lint tool. Use --lint oxlint or --lint eslint (deprecated).\");\n }\n const base = stringOption(options.base);\n const changed = options.changed === true || base !== undefined;\n const project = stringOption(options.project);\n const skip = parseSkippedSteps(stringOption(options.skip));\n const cliEntry = process.argv[1];\n const flags = [...(changed ? [\"--changed\"] : []), ...(base ? [\"--base\", base] : [])];\n\n const report = await runCheck({\n lint,\n changed,\n base,\n skip,\n project,\n ...(cliEntry ? { typecheck: childTypecheckRunner(cliEntry, flags) } : {}),\n });\n\n if (options.json === true) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printProblems(report.problems);\n\n if (report.problems.length > 0) console.log(\"\");\n\n for (const line of formatCheckSummary(report)) {\n console.log(line);\n }\n }\n\n if (report.exitCode !== 0) {\n await flushAndExit(report.exitCode);\n }\n}\n"],"mappings":";;;;;;;AAOA,SAAS,aAAa,OAAkE;CACtF,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,SAAS,IAAI,MAAM,KAAK,IAAI;AAC/E;AAEA,SAAS,WAAW,OAAuC;CACzD,OAAQ,YAAkC,SAAS,KAAK;AAC1D;;AAGA,SAAgB,kBAAkB,OAA4C;CAC5E,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,MAAM,QAAQ,MACX,MAAM,GAAG,CAAC,CACV,KAAK,SAAS,KAAK,KAAK,CAAC,CAAC,CAC1B,QAAQ,SAAS,KAAK,SAAS,CAAC;CACnC,MAAM,UAAU,MAAM,QAAQ,SAAS,CAAC,WAAW,IAAI,CAAC;CAExD,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,MACR,0BAA0B,QAAQ,KAAK,IAAI,EAAE,iBAAiB,YAAY,KAAK,IAAI,EAAE,EACvF;CAGF,OAAO,MAAM,OAAO,UAAU;AAChC;;;;;;;;;;;AAYA,eAAsB,mBAAmB,EAAE,WAA6C;CACtF,MAAM,OAAO,aAAa,QAAQ,IAAI;CACtC,IAAI,SAAS,UAAa,SAAS,YAAY,SAAS,UACtD,MAAM,IAAI,MAAM,qEAAqE;CAEvF,MAAM,OAAO,aAAa,QAAQ,IAAI;CACtC,MAAM,UAAU,QAAQ,YAAY,QAAQ,SAAS;CACrD,MAAM,UAAU,aAAa,QAAQ,OAAO;CAC5C,MAAM,OAAO,kBAAkB,aAAa,QAAQ,IAAI,CAAC;CACzD,MAAM,WAAW,QAAQ,KAAK;CAC9B,MAAM,QAAQ,CAAC,GAAI,UAAU,CAAC,WAAW,IAAI,CAAC,GAAI,GAAI,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,CAAE;CAEnF,MAAM,SAAS,MAAM,SAAS;EAC5B;EACA;EACA;EACA;EACA;EACA,GAAI,WAAW,EAAE,WAAW,qBAAqB,UAAU,KAAK,EAAE,IAAI,CAAC;CACzE,CAAC;CAED,IAAI,QAAQ,SAAS,MACnB,QAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;MACtC;EACL,cAAc,OAAO,QAAQ;EAE7B,IAAI,OAAO,SAAS,SAAS,GAAG,QAAQ,IAAI,EAAE;EAE9C,KAAK,MAAM,QAAQ,mBAAmB,MAAM,GAC1C,QAAQ,IAAI,IAAI;CAEpB;CAEA,IAAI,OAAO,aAAa,GACtB,MAAM,aAAa,OAAO,QAAQ;AAEtC"}
@@ -4,7 +4,7 @@ import { checkCommandAction } from "./check/check-command.action.mjs";
4
4
  //#region ../../packages/core/src/cli/commands/check.command.ts
5
5
  /**
6
6
  * `warlock check` — the single verify command: typecheck, lint (the app's
7
- * ESLint / oxlint config), the tests affected by the change, and framework
7
+ * Oxlint config, or explicitly selected deprecated ESLint), the tests affected by the change, and framework
8
8
  * convention checks, reported as one problem list. Exit 0 clean, 1 problems
9
9
  * in the code, 2 problems with this machine.
10
10
  *
@@ -17,6 +17,11 @@ const checkCommand = command({
17
17
  description: "Typecheck, lint, run the affected tests and check conventions; one problem list, exit 1 for your code and 2 for this machine",
18
18
  action: checkCommandAction,
19
19
  options: [
20
+ {
21
+ text: "--lint",
22
+ description: "Lint tool: oxlint (default) or eslint (deprecated; removed in 5.34.0)",
23
+ type: "string"
24
+ },
20
25
  {
21
26
  text: "--changed, -c",
22
27
  description: "Only the files changed against HEAD (plus untracked files) and what they affect",
@@ -1 +1 @@
1
- {"version":3,"file":"check.command.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/cli/commands/check.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\nimport { checkCommandAction } from \"./check/check-command.action\";\n\n/**\n * `warlock check` — the single verify command: typecheck, lint (the app's\n * ESLint / oxlint config), the tests affected by the change, and framework\n * convention checks, reported as one problem list. Exit 0 clean, 1 problems\n * in the code, 2 problems with this machine.\n *\n * No preload: the steps read files and run tools; nothing boots, connects or\n * binds a port, so `check` behaves the same with or without a database or\n * Redis running.\n */\nexport const checkCommand = command({\n name: \"check\",\n description:\n \"Typecheck, lint, run the affected tests and check conventions; one problem list, exit 1 for your code and 2 for this machine\",\n action: checkCommandAction,\n options: [\n {\n text: \"--changed, -c\",\n description:\n \"Only the files changed against HEAD (plus untracked files) and what they affect\",\n type: \"boolean\",\n },\n {\n text: \"--base, -b\",\n description: \"Compare with the merge base of this ref instead of HEAD (implies --changed)\",\n type: \"string\",\n },\n {\n text: \"--skip, -s\",\n description:\n \"Comma-separated steps to skip: environment, typecheck, lint, tests, conventions\",\n type: \"string\",\n },\n {\n text: \"--project, -p\",\n description: \"tsconfig path (default tsconfig.json)\",\n type: \"string\",\n },\n {\n text: \"--json, -j\",\n description: \"Print the report as JSON\",\n type: \"boolean\",\n },\n ],\n});\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAa,eAAe,QAAQ;CAClC,MAAM;CACN,aACE;CACF,QAAQ;CACR,SAAS;EACP;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"check.command.mjs","names":[],"sources":["../../../../../../../../../packages/core/src/cli/commands/check.command.ts"],"sourcesContent":["import { command } from \"../../commands/cli-command\";\nimport { checkCommandAction } from \"./check/check-command.action\";\n\n/**\n * `warlock check` — the single verify command: typecheck, lint (the app's\n * Oxlint config, or explicitly selected deprecated ESLint), the tests affected by the change, and framework\n * convention checks, reported as one problem list. Exit 0 clean, 1 problems\n * in the code, 2 problems with this machine.\n *\n * No preload: the steps read files and run tools; nothing boots, connects or\n * binds a port, so `check` behaves the same with or without a database or\n * Redis running.\n */\nexport const checkCommand = command({\n name: \"check\",\n description:\n \"Typecheck, lint, run the affected tests and check conventions; one problem list, exit 1 for your code and 2 for this machine\",\n action: checkCommandAction,\n options: [\n {\n text: \"--lint\",\n description: \"Lint tool: oxlint (default) or eslint (deprecated; removed in 5.34.0)\",\n type: \"string\",\n },\n {\n text: \"--changed, -c\",\n description:\n \"Only the files changed against HEAD (plus untracked files) and what they affect\",\n type: \"boolean\",\n },\n {\n text: \"--base, -b\",\n description: \"Compare with the merge base of this ref instead of HEAD (implies --changed)\",\n type: \"string\",\n },\n {\n text: \"--skip, -s\",\n description:\n \"Comma-separated steps to skip: environment, typecheck, lint, tests, conventions\",\n type: \"string\",\n },\n {\n text: \"--project, -p\",\n description: \"tsconfig path (default tsconfig.json)\",\n type: \"string\",\n },\n {\n text: \"--json, -j\",\n description: \"Print the report as JSON\",\n type: \"boolean\",\n },\n ],\n});\n"],"mappings":";;;;;;;;;;;;;;AAaA,MAAa,eAAe,QAAQ;CAClC,MAAM;CACN,aACE;CACF,QAAQ;CACR,SAAS;EACP;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aACE;GACF,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;EACA;GACE,MAAM;GACN,aAAa;GACb,MAAM;EACR;CACF;AACF,CAAC"}
@@ -0,0 +1,91 @@
1
+ import { config } from "../../../config/config-getter.mjs";
2
+ import { resolveBuildConfig } from "../../../production/resolve-build-config.mjs";
3
+ import { checkDistReadyToStartAsync } from "../../../production/assert-dist-ready-to-start.mjs";
4
+ import { createPm2Config, selectPm2Targets, writePm2Config } from "./pm2-config.mjs";
5
+ import path from "node:path";
6
+ import { readFile } from "node:fs/promises";
7
+ import { execFile } from "node:child_process";
8
+
9
+ //#region ../../packages/core/src/cli/commands/pm2/pm2-command.action.ts
10
+ /** No shell, no patterns, no import of PM2 or dependency on its daemon for init. */
11
+ function runPm2(args, cwd) {
12
+ return new Promise((resolve, reject) => {
13
+ const child = execFile("pm2", args, {
14
+ cwd,
15
+ encoding: "utf8",
16
+ maxBuffer: 8388608
17
+ }, (error, stdout) => {
18
+ if (error) reject(/* @__PURE__ */ new Error(`PM2 command failed (CLI pid ${child.pid ?? "unavailable"}). Install PM2 separately and ensure it is on PATH. ${error.message}`));
19
+ else resolve(stdout);
20
+ });
21
+ });
22
+ }
23
+ function stringOption(data, name) {
24
+ const value = data.options[name];
25
+ if (value === void 0) return void 0;
26
+ if (typeof value !== "string" || value.length === 0) throw new Error(`--${name} requires a value.`);
27
+ return value;
28
+ }
29
+ async function pm2CommandAction(data) {
30
+ const [action, ...extra] = data.args;
31
+ if (action !== "init" && action !== "reload" || extra.length > 0) throw new Error("Usage: warlock pm2 init | warlock pm2 reload [--id <pm2-id>]");
32
+ const allowed = new Set(action === "init" ? [
33
+ "project",
34
+ "app",
35
+ "cluster",
36
+ "instances",
37
+ "listenTimeout"
38
+ ] : [
39
+ "project",
40
+ "app",
41
+ "id"
42
+ ]);
43
+ for (const key of Object.keys(data.options)) if (!allowed.has(key)) throw new Error(`Unsupported option for pm2 ${action}: ${key}`);
44
+ if (data.options.cluster !== void 0 && typeof data.options.cluster !== "boolean") throw new Error("--cluster must be a boolean.");
45
+ const root = process.cwd();
46
+ const packageJson = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
47
+ const packageName = typeof packageJson === "object" && packageJson !== null && "name" in packageJson && typeof packageJson.name === "string" ? packageJson.name : void 0;
48
+ const project = stringOption(data, "project") ?? packageName;
49
+ if (!project) throw new Error("Set package.json name or pass --project to identify this project.");
50
+ const app = config.get("app") ?? {};
51
+ const appOverride = stringOption(data, "app");
52
+ const build = resolveBuildConfig();
53
+ const input = {
54
+ root,
55
+ project,
56
+ app: appOverride === void 0 ? app : {
57
+ ...app,
58
+ appName: appOverride
59
+ },
60
+ environment: process.env.NODE_ENV ?? "development",
61
+ entryPath: build.entryPath,
62
+ sourcemap: build.sourcemap,
63
+ http: config.get("http"),
64
+ socket: config.get("socket")
65
+ };
66
+ if (action === "init") {
67
+ const generated = createPm2Config(input, {
68
+ cluster: data.options.cluster === true,
69
+ instances: stringOption(data, "instances"),
70
+ listenTimeout: stringOption(data, "listenTimeout")
71
+ });
72
+ const target = await writePm2Config(root, generated.content);
73
+ console.log(`Created ${target} for ${generated.app.name}.`);
74
+ console.log(`Readiness URL: ${generated.readinessUrl ?? "unavailable (HTTP/health disabled or ephemeral port)"}`);
75
+ console.log(generated.app.exec_mode === "cluster" ? "Cluster reload replaces workers in sequence; a PM2 readiness timeout can still cause downtime." : "Fork mode uses a restart on reload; use --cluster for rolling HTTP reloads.");
76
+ return;
77
+ }
78
+ const readiness = await checkDistReadyToStartAsync(build.outdir);
79
+ if (!readiness.ready) throw new Error(readiness.reason);
80
+ const list = JSON.parse(await runPm2(["jlist"], root));
81
+ const targets = selectPm2Targets(list, input, stringOption(data, "id"));
82
+ if (targets.some((target) => !target.cluster)) console.warn("Fork mode reload falls back to restart; expect a brief interruption.");
83
+ for (const target of targets) {
84
+ const output = await runPm2(["reload", String(target.id)], root);
85
+ if (output) process.stdout.write(output);
86
+ }
87
+ }
88
+
89
+ //#endregion
90
+ export { pm2CommandAction, runPm2 };
91
+ //# sourceMappingURL=pm2-command.action.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pm2-command.action.mjs","names":[],"sources":["../../../../../../../../../../packages/core/src/cli/commands/pm2/pm2-command.action.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { AppConfigurations } from \"../../../application/application-config-types\";\nimport type { CommandActionData } from \"../../../commands/types\";\nimport { config } from \"../../../config/config-getter\";\nimport type { HttpConfigurations } from \"../../../http/types\";\nimport { checkDistReadyToStartAsync } from \"../../../production/assert-dist-ready-to-start\";\nimport { resolveBuildConfig } from \"../../../production/resolve-build-config\";\nimport type { SocketOptions } from \"../../../socket/types\";\nimport { createPm2Config, selectPm2Targets, writePm2Config } from \"./pm2-config\";\n\n/** No shell, no patterns, no import of PM2 or dependency on its daemon for init. */\nexport function runPm2(args: string[], cwd: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = execFile(\n \"pm2\",\n args,\n { cwd, encoding: \"utf8\", maxBuffer: 8 * 1024 * 1024 },\n (error, stdout) => {\n if (error) {\n reject(\n new Error(\n `PM2 command failed (CLI pid ${child.pid ?? \"unavailable\"}). Install PM2 separately and ensure it is on PATH. ${error.message}`,\n ),\n );\n } else {\n resolve(stdout);\n }\n },\n );\n // Retain the child and its PID; this function never stops a daemon or any\n // application process. PM2 reload is invoked only with validated ids.\n });\n}\n\nfunction stringOption(data: CommandActionData, name: string): string | undefined {\n const value = data.options[name];\n if (value === undefined) return undefined;\n if (typeof value !== \"string\" || value.length === 0)\n throw new Error(`--${name} requires a value.`);\n return value;\n}\n\nexport async function pm2CommandAction(data: CommandActionData): Promise<void> {\n const [action, ...extra] = data.args;\n if ((action !== \"init\" && action !== \"reload\") || extra.length > 0) {\n throw new Error(\"Usage: warlock pm2 init | warlock pm2 reload [--id <pm2-id>]\");\n }\n const allowed = new Set(\n action === \"init\"\n ? [\"project\", \"app\", \"cluster\", \"instances\", \"listenTimeout\"]\n : [\"project\", \"app\", \"id\"],\n );\n for (const key of Object.keys(data.options)) {\n if (!allowed.has(key)) throw new Error(`Unsupported option for pm2 ${action}: ${key}`);\n }\n if (data.options.cluster !== undefined && typeof data.options.cluster !== \"boolean\") {\n throw new Error(\"--cluster must be a boolean.\");\n }\n\n const root = process.cwd();\n const packageJson: unknown = JSON.parse(await readFile(path.join(root, \"package.json\"), \"utf8\"));\n const packageName =\n typeof packageJson === \"object\" &&\n packageJson !== null &&\n \"name\" in packageJson &&\n typeof packageJson.name === \"string\"\n ? packageJson.name\n : undefined;\n const project = stringOption(data, \"project\") ?? packageName;\n if (!project)\n throw new Error(\"Set package.json name or pass --project to identify this project.\");\n const app = config.get<AppConfigurations | undefined>(\"app\") ?? {};\n const appOverride = stringOption(data, \"app\");\n const build = resolveBuildConfig();\n const input = {\n root,\n project,\n app: appOverride === undefined ? app : { ...app, appName: appOverride },\n environment: process.env.NODE_ENV ?? \"development\",\n entryPath: build.entryPath,\n sourcemap: build.sourcemap,\n http: config.get<HttpConfigurations | undefined>(\"http\"),\n socket: config.get<SocketOptions | undefined>(\"socket\"),\n };\n\n if (action === \"init\") {\n const generated = createPm2Config(input, {\n cluster: data.options.cluster === true,\n instances: stringOption(data, \"instances\"),\n listenTimeout: stringOption(data, \"listenTimeout\"),\n });\n const target = await writePm2Config(root, generated.content);\n console.log(`Created ${target} for ${generated.app.name}.`);\n console.log(\n `Readiness URL: ${generated.readinessUrl ?? \"unavailable (HTTP/health disabled or ephemeral port)\"}`,\n );\n console.log(\n generated.app.exec_mode === \"cluster\"\n ? \"Cluster reload replaces workers in sequence; a PM2 readiness timeout can still cause downtime.\"\n : \"Fork mode uses a restart on reload; use --cluster for rolling HTTP reloads.\",\n );\n return;\n }\n\n const readiness = await checkDistReadyToStartAsync(build.outdir);\n if (!readiness.ready) throw new Error(readiness.reason);\n // jlist is JSON per PM2's API.js. Match exact identity locally, then reload\n // numeric ids only; a name collision never broadens the operation.\n // https://github.com/Unitech/pm2/blob/master/lib/API.js\n const list: unknown = JSON.parse(await runPm2([\"jlist\"], root));\n const targets = selectPm2Targets(list, input, stringOption(data, \"id\"));\n if (targets.some((target) => !target.cluster)) {\n console.warn(\"Fork mode reload falls back to restart; expect a brief interruption.\");\n }\n for (const target of targets) {\n const output = await runPm2([\"reload\", String(target.id)], root);\n if (output) process.stdout.write(output);\n }\n}\n"],"mappings":"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"}