@wildo-ai/platform-core-shared 1.1.11 → 1.1.13

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 (142) hide show
  1. package/dist/esm/core/resources/resources-operations.custom.shared.definitions.d.ts.map +1 -1
  2. package/dist/esm/core/resources/resources-operations.custom.shared.definitions.js.map +1 -1
  3. package/dist/esm/core/resources/resources-relationships.custom.shared.definitions.d.ts.map +1 -1
  4. package/dist/esm/core/resources/resources-relationships.custom.shared.definitions.js.map +1 -1
  5. package/dist/esm/core/resources/resources-types.custom.shared.schemas.d.ts.map +1 -1
  6. package/dist/esm/core/resources/resources-types.custom.shared.schemas.js.map +1 -1
  7. package/dist/esm/dev-runtime/dependency-install-record.d.ts +111 -0
  8. package/dist/esm/dev-runtime/dependency-install-record.d.ts.map +1 -0
  9. package/dist/esm/dev-runtime/dependency-install-record.js +185 -0
  10. package/dist/esm/dev-runtime/dependency-install-record.js.map +1 -0
  11. package/dist/esm/dev-runtime/dev-runtime-command.d.ts +50 -0
  12. package/dist/esm/dev-runtime/dev-runtime-command.d.ts.map +1 -0
  13. package/dist/esm/dev-runtime/dev-runtime-command.js +147 -0
  14. package/dist/esm/dev-runtime/dev-runtime-command.js.map +1 -0
  15. package/dist/esm/dev-runtime/dev-runtime-processes.d.ts +47 -0
  16. package/dist/esm/dev-runtime/dev-runtime-processes.d.ts.map +1 -0
  17. package/dist/esm/dev-runtime/dev-runtime-processes.js +163 -0
  18. package/dist/esm/dev-runtime/dev-runtime-processes.js.map +1 -0
  19. package/dist/esm/dev-runtime/dev-runtime-signals.d.ts +54 -0
  20. package/dist/esm/dev-runtime/dev-runtime-signals.d.ts.map +1 -0
  21. package/dist/esm/dev-runtime/dev-runtime-signals.js +113 -0
  22. package/dist/esm/dev-runtime/dev-runtime-signals.js.map +1 -0
  23. package/dist/esm/dev-runtime/emitted-relative-specifiers.d.ts +14 -0
  24. package/dist/esm/dev-runtime/emitted-relative-specifiers.d.ts.map +1 -0
  25. package/dist/esm/dev-runtime/emitted-relative-specifiers.js +71 -0
  26. package/dist/esm/dev-runtime/emitted-relative-specifiers.js.map +1 -0
  27. package/dist/esm/dev-runtime/frontend-dev-runtime.d.ts +43 -0
  28. package/dist/esm/dev-runtime/frontend-dev-runtime.d.ts.map +1 -0
  29. package/dist/esm/dev-runtime/frontend-dev-runtime.js +42 -0
  30. package/dist/esm/dev-runtime/frontend-dev-runtime.js.map +1 -0
  31. package/dist/esm/dev-runtime/node-runtime-dev-supervisor.d.ts +233 -0
  32. package/dist/esm/dev-runtime/node-runtime-dev-supervisor.d.ts.map +1 -0
  33. package/dist/esm/dev-runtime/node-runtime-dev-supervisor.js +578 -0
  34. package/dist/esm/dev-runtime/node-runtime-dev-supervisor.js.map +1 -0
  35. package/dist/esm/dev-runtime/package-compile-loop.d.ts +125 -0
  36. package/dist/esm/dev-runtime/package-compile-loop.d.ts.map +1 -0
  37. package/dist/esm/dev-runtime/package-compile-loop.js +386 -0
  38. package/dist/esm/dev-runtime/package-compile-loop.js.map +1 -0
  39. package/dist/esm/dev-runtime.exports.d.ts +19 -0
  40. package/dist/esm/dev-runtime.exports.d.ts.map +1 -0
  41. package/dist/esm/dev-runtime.exports.js +19 -0
  42. package/dist/esm/dev-runtime.exports.js.map +1 -0
  43. package/dist/esm/index.d.ts +4 -0
  44. package/dist/esm/index.d.ts.map +1 -1
  45. package/dist/esm/index.js +4 -0
  46. package/dist/esm/index.js.map +1 -1
  47. package/dist/esm/runtime/application-backend-env.contracts.d.ts +9 -2
  48. package/dist/esm/runtime/application-backend-env.contracts.d.ts.map +1 -1
  49. package/dist/esm/runtime/application-backend-env.contracts.js +8 -1
  50. package/dist/esm/runtime/application-backend-env.contracts.js.map +1 -1
  51. package/dist/esm/runtime/application-worker-env.contracts.d.ts +11 -3
  52. package/dist/esm/runtime/application-worker-env.contracts.d.ts.map +1 -1
  53. package/dist/esm/runtime/application-worker-env.contracts.js +9 -0
  54. package/dist/esm/runtime/application-worker-env.contracts.js.map +1 -1
  55. package/dist/esm/runtime/backend-start-outcome.contracts.d.ts +27 -0
  56. package/dist/esm/runtime/backend-start-outcome.contracts.d.ts.map +1 -1
  57. package/dist/esm/runtime/backend-start-outcome.contracts.js +34 -0
  58. package/dist/esm/runtime/backend-start-outcome.contracts.js.map +1 -1
  59. package/dist/esm/runtime/frontend-settings-hub-wiring.contracts.d.ts +28 -0
  60. package/dist/esm/runtime/frontend-settings-hub-wiring.contracts.d.ts.map +1 -0
  61. package/dist/esm/runtime/frontend-settings-hub-wiring.contracts.js +35 -0
  62. package/dist/esm/runtime/frontend-settings-hub-wiring.contracts.js.map +1 -0
  63. package/dist/esm/runtime/http-server-env.contracts.d.ts +14 -1
  64. package/dist/esm/runtime/http-server-env.contracts.d.ts.map +1 -1
  65. package/dist/esm/runtime/http-server-env.contracts.js +13 -0
  66. package/dist/esm/runtime/http-server-env.contracts.js.map +1 -1
  67. package/dist/esm/runtime/local-dev-runtime-signals.contracts.d.ts +239 -0
  68. package/dist/esm/runtime/local-dev-runtime-signals.contracts.d.ts.map +1 -0
  69. package/dist/esm/runtime/local-dev-runtime-signals.contracts.js +256 -0
  70. package/dist/esm/runtime/local-dev-runtime-signals.contracts.js.map +1 -0
  71. package/dist/esm/runtime/runtime-topology.contracts.d.ts.map +1 -1
  72. package/dist/esm/runtime/runtime-topology.contracts.js.map +1 -1
  73. package/dist/esm/security/authorizations/roles.custom.shared.definitions.d.ts.map +1 -1
  74. package/dist/esm/security/authorizations/roles.custom.shared.definitions.js.map +1 -1
  75. package/dist/esm/wildo-application-executions/wildo-application-executions.custom.shared.resources-config.schemas.d.ts +112 -104
  76. package/dist/esm/wildo-application-executions/wildo-application-executions.custom.shared.resources-config.schemas.d.ts.map +1 -1
  77. package/dist/esm/wildo-application-executions/wildo-application-executions.custom.shared.resources-config.schemas.js +4 -3
  78. package/dist/esm/wildo-application-executions/wildo-application-executions.custom.shared.resources-config.schemas.js.map +1 -1
  79. package/dist/esm/wildo-application-executions/wildo-application-executions.custom.shared.schemas.d.ts +112 -104
  80. package/dist/esm/wildo-application-executions/wildo-application-executions.custom.shared.schemas.d.ts.map +1 -1
  81. package/dist/esm/wildo-application-executions/wildo-application-executions.custom.shared.schemas.js.map +1 -1
  82. package/dist/esm/wildo-application-tasks/wildo-application-tasks.custom.shared.resources-config.schemas.d.ts +24 -34
  83. package/dist/esm/wildo-application-tasks/wildo-application-tasks.custom.shared.resources-config.schemas.d.ts.map +1 -1
  84. package/dist/esm/wildo-application-tasks/wildo-application-tasks.custom.shared.resources-config.schemas.js +4 -3
  85. package/dist/esm/wildo-application-tasks/wildo-application-tasks.custom.shared.resources-config.schemas.js.map +1 -1
  86. package/dist/esm/wildo-application-tasks/wildo-application-tasks.custom.shared.schemas.d.ts +24 -34
  87. package/dist/esm/wildo-application-tasks/wildo-application-tasks.custom.shared.schemas.d.ts.map +1 -1
  88. package/dist/esm/wildo-application-tasks/wildo-application-tasks.custom.shared.schemas.js.map +1 -1
  89. package/dist/esm/wildo-applications/wildo-applications.custom.shared.resources-config.schemas.d.ts +3 -3
  90. package/dist/esm/wildo-applications/wildo-applications.custom.shared.resources-config.schemas.d.ts.map +1 -1
  91. package/dist/esm/wildo-applications/wildo-applications.custom.shared.resources-config.schemas.js +30 -42
  92. package/dist/esm/wildo-applications/wildo-applications.custom.shared.resources-config.schemas.js.map +1 -1
  93. package/dist/esm/wildo-applications/wildo-applications.custom.shared.schemas.d.ts +9 -9
  94. package/dist/esm/wildo-applications/wildo-applications.custom.shared.schemas.d.ts.map +1 -1
  95. package/dist/esm/wildo-applications/wildo-applications.custom.shared.schemas.js.map +1 -1
  96. package/dist/esm/wildo-applications-versions/wildo-applications-versions.custom.shared.resources-config.schemas.d.ts.map +1 -1
  97. package/dist/esm/wildo-applications-versions/wildo-applications-versions.custom.shared.resources-config.schemas.js.map +1 -1
  98. package/dist/esm/wildo-applications-versions/wildo-applications-versions.custom.shared.schemas.d.ts +7 -7
  99. package/dist/esm/wildo-applications-versions/wildo-applications-versions.custom.shared.schemas.d.ts.map +1 -1
  100. package/dist/esm/wildo-applications-versions/wildo-applications-versions.custom.shared.schemas.js.map +1 -1
  101. package/dist/esm/workbench/creation-drive.custom.shared.schemas.d.ts +82 -0
  102. package/dist/esm/workbench/creation-drive.custom.shared.schemas.d.ts.map +1 -0
  103. package/dist/esm/workbench/creation-drive.custom.shared.schemas.js +73 -0
  104. package/dist/esm/workbench/creation-drive.custom.shared.schemas.js.map +1 -0
  105. package/dist/esm/workbench/creation-journey-timeline.custom.shared.schemas.d.ts +183 -0
  106. package/dist/esm/workbench/creation-journey-timeline.custom.shared.schemas.d.ts.map +1 -0
  107. package/dist/esm/workbench/creation-journey-timeline.custom.shared.schemas.js +188 -0
  108. package/dist/esm/workbench/creation-journey-timeline.custom.shared.schemas.js.map +1 -0
  109. package/dist/esm/workbench/workbench-aspect-resources.custom.shared.schemas.d.ts.map +1 -1
  110. package/dist/esm/workbench/workbench-aspect-resources.custom.shared.schemas.js +46 -0
  111. package/dist/esm/workbench/workbench-aspect-resources.custom.shared.schemas.js.map +1 -1
  112. package/dist/esm/workbench/workbench-creation-console.custom.shared.schemas.d.ts +170 -7
  113. package/dist/esm/workbench/workbench-creation-console.custom.shared.schemas.d.ts.map +1 -1
  114. package/dist/esm/workbench/workbench-creation-console.custom.shared.schemas.js +177 -7
  115. package/dist/esm/workbench/workbench-creation-console.custom.shared.schemas.js.map +1 -1
  116. package/dist/esm/workbench/workbench-creation-conversation.custom.shared.schemas.d.ts.map +1 -1
  117. package/dist/esm/workbench/workbench-creation-conversation.custom.shared.schemas.js.map +1 -1
  118. package/dist/esm/workbench/workbench-creation-dossier.custom.shared.schemas.d.ts.map +1 -1
  119. package/dist/esm/workbench/workbench-creation-dossier.custom.shared.schemas.js.map +1 -1
  120. package/dist/esm/workbench/workbench-creation-plane.custom.shared.resources-config.schemas.d.ts +480 -459
  121. package/dist/esm/workbench/workbench-creation-plane.custom.shared.resources-config.schemas.d.ts.map +1 -1
  122. package/dist/esm/workbench/workbench-creation-plane.custom.shared.resources-config.schemas.js +21 -16
  123. package/dist/esm/workbench/workbench-creation-plane.custom.shared.resources-config.schemas.js.map +1 -1
  124. package/dist/esm/workbench/workbench-creation-plane.custom.shared.schemas.d.ts +480 -278
  125. package/dist/esm/workbench/workbench-creation-plane.custom.shared.schemas.d.ts.map +1 -1
  126. package/dist/esm/workbench/workbench-creation-plane.custom.shared.schemas.js +316 -46
  127. package/dist/esm/workbench/workbench-creation-plane.custom.shared.schemas.js.map +1 -1
  128. package/dist/esm/workbench/workbench-domain-model.custom.shared.schemas.d.ts.map +1 -1
  129. package/dist/esm/workbench/workbench-domain-model.custom.shared.schemas.js.map +1 -1
  130. package/dist/esm/workbench/workbench-resources-types.custom.shared.schemas.d.ts +7 -0
  131. package/dist/esm/workbench/workbench-resources-types.custom.shared.schemas.d.ts.map +1 -1
  132. package/dist/esm/workbench/workbench-resources-types.custom.shared.schemas.js +13 -1
  133. package/dist/esm/workbench/workbench-resources-types.custom.shared.schemas.js.map +1 -1
  134. package/dist/esm/workbench/workbench-resources.custom.shared.resources-config.schemas.d.ts +287 -277
  135. package/dist/esm/workbench/workbench-resources.custom.shared.resources-config.schemas.d.ts.map +1 -1
  136. package/dist/esm/workbench/workbench-resources.custom.shared.resources-config.schemas.js.map +1 -1
  137. package/dist/esm/workbench/workbench-resources.custom.shared.schemas.d.ts +34 -29
  138. package/dist/esm/workbench/workbench-resources.custom.shared.schemas.d.ts.map +1 -1
  139. package/dist/esm/workbench/workbench-resources.custom.shared.schemas.js +9 -5
  140. package/dist/esm/workbench/workbench-resources.custom.shared.schemas.js.map +1 -1
  141. package/dist/tsconfig.build.tsbuildinfo +1 -1
  142. package/package.json +13 -8
@@ -0,0 +1,147 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { findApplicationRoot } from './dev-runtime-signals.js';
4
+ import { FrontendDevRuntime } from './frontend-dev-runtime.js';
5
+ import { NodeRuntimeDevSupervisor } from './node-runtime-dev-supervisor.js';
6
+ import { PackageCompileLoop } from './package-compile-loop.js';
7
+ export function parseDevRuntimeArguments(argv) {
8
+ const [verb, ...rest] = argv;
9
+ const values = new Map();
10
+ const nodeArguments = [];
11
+ for (let index = 0; index < rest.length; index += 1) {
12
+ const flag = rest[index];
13
+ const value = rest[index + 1];
14
+ if (!flag.startsWith('--') || value === undefined)
15
+ throw new Error(`Unexpected argument "${flag}".`);
16
+ index += 1;
17
+ if (flag === '--node-arg')
18
+ nodeArguments.push(value);
19
+ else
20
+ values.set(flag.slice(2), value);
21
+ }
22
+ return { verb, values, nodeArguments };
23
+ }
24
+ /** The workspace packages a package depends on (`workspace:` specifiers), in a stable order. */
25
+ export function readWorkspaceDependencyNames(manifest) {
26
+ const names = new Set();
27
+ for (const block of [manifest.dependencies, manifest.devDependencies]) {
28
+ for (const [name, specifier] of Object.entries(block ?? {}))
29
+ if (specifier.startsWith('workspace:'))
30
+ names.add(name);
31
+ }
32
+ return [...names].sort();
33
+ }
34
+ /**
35
+ * The two arguments `serve` cannot run without. Neither has a default.
36
+ *
37
+ * The LABEL in particular (#3257): it names the supervisor record, and that record is how a second supervisor of the
38
+ * same runtime is refused. Had it kept its old default of `backend-api`, a minion would have been refused as a second
39
+ * backend — or, started first, would have made the backend's own supervisor refuse.
40
+ */
41
+ export function readServeArguments(parsed) {
42
+ const entry = parsed.values.get('entry');
43
+ if (!entry)
44
+ return { refusal: 'serve needs --entry <module> (dist/index.js).' };
45
+ const label = parsed.values.get('label');
46
+ if (!label)
47
+ return { refusal: "serve needs --label <runtime> (backend-api, or the minion's or worker's directory name)." };
48
+ return { entry, label };
49
+ }
50
+ /** Run one `wildo-dev-runtime` invocation to its end; resolves with the process exit code. */
51
+ export async function runDevRuntimeCommand(argv) {
52
+ let parsed;
53
+ try {
54
+ parsed = parseDevRuntimeArguments(argv);
55
+ }
56
+ catch (error) {
57
+ process.stderr.write(`[wildo-dev-runtime] ${error.message}\n`);
58
+ return 2;
59
+ }
60
+ const packageRoot = process.cwd();
61
+ const appRoot = findApplicationRoot(packageRoot);
62
+ if (appRoot === null) {
63
+ process.stderr.write(`[wildo-dev-runtime] No wildo.saas.config.ts above ${packageRoot}: run this from a package of a Wildo application.\n`);
64
+ return 2;
65
+ }
66
+ const manifest = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
67
+ const project = parsed.values.get('project') ?? 'tsconfig.build.json';
68
+ let stoppable;
69
+ /** Set by a verb whose foreground child can end on its own (the frontend's dev server): the process ends with it. */
70
+ let endedOnItsOwn;
71
+ if (parsed.verb === 'compile') {
72
+ stoppable = new PackageCompileLoop({ appRoot, packageRoot, packageName: manifest.name, project, dist: parsed.values.get('dist') ?? 'dist/esm' });
73
+ }
74
+ else if (parsed.verb === 'serve') {
75
+ const serve = readServeArguments(parsed);
76
+ if ('refusal' in serve) {
77
+ process.stderr.write(`[wildo-dev-runtime] ${serve.refusal}\n`);
78
+ return 2;
79
+ }
80
+ const { entry, label } = serve;
81
+ stoppable = new NodeRuntimeDevSupervisor({
82
+ appRoot,
83
+ packageRoot,
84
+ packageName: manifest.name,
85
+ label,
86
+ entry,
87
+ nodeArguments: parsed.nodeArguments,
88
+ compileLoop: new PackageCompileLoop({ appRoot, packageRoot, packageName: manifest.name, project, dist: parsed.values.get('dist') ?? 'dist' }),
89
+ dependencyPackageNames: readWorkspaceDependencyNames(manifest),
90
+ });
91
+ }
92
+ else if (parsed.verb === 'frontend') {
93
+ const command = parsed.values.get('command');
94
+ if (!command) {
95
+ process.stderr.write('[wildo-dev-runtime] frontend needs --command <dev server> (vite).\n');
96
+ return 2;
97
+ }
98
+ stoppable = new FrontendDevRuntime({
99
+ packageRoot,
100
+ command,
101
+ compileLoop: new PackageCompileLoop({ appRoot, packageRoot, packageName: manifest.name, project: parsed.values.get('project') ?? 'tsconfig.library.json', dist: parsed.values.get('dist') ?? 'dist/library' }),
102
+ onCommandExit: (code) => endedOnItsOwn?.(code),
103
+ });
104
+ }
105
+ else {
106
+ process.stderr.write(`[wildo-dev-runtime] Unknown verb "${parsed.verb ?? ''}": expected "compile", "serve" or "frontend".\n`);
107
+ return 2;
108
+ }
109
+ /*
110
+ * Whatever ends this process, its children end with it (#3214): a forced stop or an uncaught error would otherwise
111
+ * leave the backend and the compiler re-parented to init, holding the port and serving stale code. `exit` handlers
112
+ * run synchronously, which is why the kill is the synchronous one. Registered BEFORE starting, so a signal that
113
+ * arrives while the compiler and the backend are being spawned still stops them.
114
+ */
115
+ process.on('exit', () => stoppable.killChildrenNow());
116
+ process.on('uncaughtException', (error) => {
117
+ process.stderr.write(`[wildo-dev-runtime] stopping on an unexpected error: ${error.stack ?? error.message}\n`);
118
+ process.exit(1);
119
+ });
120
+ let resolveStopped = () => { };
121
+ const stopped = new Promise((resolve) => { resolveStopped = resolve; });
122
+ let stopping = false;
123
+ const stop = () => {
124
+ if (stopping)
125
+ return;
126
+ stopping = true;
127
+ void stoppable.stop().finally(() => resolveStopped(0));
128
+ };
129
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'])
130
+ process.on(signal, stop);
131
+ // The dev server ended by itself: stop the rest and end with ITS code, so whatever supervises this process sees it.
132
+ endedOnItsOwn = (code) => {
133
+ if (stopping)
134
+ return;
135
+ stopping = true;
136
+ void stoppable.stop().finally(() => resolveStopped(code));
137
+ };
138
+ try {
139
+ await stoppable.start();
140
+ }
141
+ catch (error) {
142
+ process.stderr.write(`[wildo-dev-runtime] ${error.message}\n`);
143
+ return 1;
144
+ }
145
+ return await stopped;
146
+ }
147
+ //# sourceMappingURL=dev-runtime-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-runtime-command.js","sourceRoot":"","sources":["../../../src/dev-runtime/dev-runtime-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { findApplicationRoot } from './dev-runtime-signals';\nimport { FrontendDevRuntime } from './frontend-dev-runtime';\nimport { NodeRuntimeDevSupervisor } from './node-runtime-dev-supervisor';\nimport { PackageCompileLoop } from './package-compile-loop';\n\n/**\n * `wildo-dev-runtime` — the dev loop of an application created from the PUBLISHED framework (#3214, #3215, #3216,\n * #3257).\n *\n * ```\n * wildo-dev-runtime compile [--project tsconfig.build.json] [--dist dist/esm]\n * Keep one package's dist current and publish its compile state (the shared library's and the specifications' `dev`).\n * wildo-dev-runtime serve --entry dist/index.js --label <runtime> [--project tsconfig.build.json] [--dist dist] [--node-arg <arg>]...\n * Compile a node runtime's package AND supervise its process: the backend's `dev` (`--label backend-api`), a minion's\n * and a node worker's (`--label <its directory>`).\n * wildo-dev-runtime frontend --command vite [--project tsconfig.library.json] [--dist dist/library]\n * Keep the frontend's config library current AND run its dev server (the frontend's `dev`, #3226).\n * ```\n *\n * Runs in the package directory (the generated `dev` scripts are run there by `pnpm`). The application root is found\n * by the `wildo.saas.config.ts` walk; the package's name and its workspace dependencies come from its `package.json`.\n * Exit 2 when it cannot run at all (no application root, an unknown verb), exit 1 when it refuses (another dev process\n * already owns the package).\n *\n * A framework developer's application does not use this: its packages run the monorepo's own observer and supervisor\n * (`scripts/dev-orchestrator/`), which publish the same records.\n */\n\ninterface ParsedArguments {\n readonly verb: string | undefined;\n readonly values: Map<string, string>;\n readonly nodeArguments: string[];\n}\n\nexport function parseDevRuntimeArguments(argv: readonly string[]): ParsedArguments {\n const [verb, ...rest] = argv;\n const values = new Map<string, string>();\n const nodeArguments: string[] = [];\n for (let index = 0; index < rest.length; index += 1) {\n const flag = rest[index]!;\n const value = rest[index + 1];\n if (!flag.startsWith('--') || value === undefined) throw new Error(`Unexpected argument \"${flag}\".`);\n index += 1;\n if (flag === '--node-arg') nodeArguments.push(value);\n else values.set(flag.slice(2), value);\n }\n return { verb, values, nodeArguments };\n}\n\n/** The workspace packages a package depends on (`workspace:` specifiers), in a stable order. */\nexport function readWorkspaceDependencyNames(manifest: { readonly dependencies?: Record<string, string>; readonly devDependencies?: Record<string, string> }): string[] {\n const names = new Set<string>();\n for (const block of [manifest.dependencies, manifest.devDependencies]) {\n for (const [name, specifier] of Object.entries(block ?? {})) if (specifier.startsWith('workspace:')) names.add(name);\n }\n return [...names].sort();\n}\n\n/**\n * The two arguments `serve` cannot run without. Neither has a default.\n *\n * The LABEL in particular (#3257): it names the supervisor record, and that record is how a second supervisor of the\n * same runtime is refused. Had it kept its old default of `backend-api`, a minion would have been refused as a second\n * backend — or, started first, would have made the backend's own supervisor refuse.\n */\nexport function readServeArguments(parsed: ParsedArguments): { readonly entry: string; readonly label: string } | { readonly refusal: string } {\n const entry = parsed.values.get('entry');\n if (!entry) return { refusal: 'serve needs --entry <module> (dist/index.js).' };\n const label = parsed.values.get('label');\n if (!label) return { refusal: \"serve needs --label <runtime> (backend-api, or the minion's or worker's directory name).\" };\n return { entry, label };\n}\n\n/** Run one `wildo-dev-runtime` invocation to its end; resolves with the process exit code. */\nexport async function runDevRuntimeCommand(argv: readonly string[]): Promise<number> {\n let parsed: ParsedArguments;\n try {\n parsed = parseDevRuntimeArguments(argv);\n } catch (error) {\n process.stderr.write(`[wildo-dev-runtime] ${(error as Error).message}\\n`);\n return 2;\n }\n const packageRoot = process.cwd();\n const appRoot = findApplicationRoot(packageRoot);\n if (appRoot === null) {\n process.stderr.write(`[wildo-dev-runtime] No wildo.saas.config.ts above ${packageRoot}: run this from a package of a Wildo application.\\n`);\n return 2;\n }\n const manifest = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')) as { name: string; dependencies?: Record<string, string>; devDependencies?: Record<string, string> };\n const project = parsed.values.get('project') ?? 'tsconfig.build.json';\n\n let stoppable: { start(): Promise<void>; stop(): Promise<void>; killChildrenNow(): void };\n /** Set by a verb whose foreground child can end on its own (the frontend's dev server): the process ends with it. */\n let endedOnItsOwn: ((code: number) => void) | undefined;\n if (parsed.verb === 'compile') {\n stoppable = new PackageCompileLoop({ appRoot, packageRoot, packageName: manifest.name, project, dist: parsed.values.get('dist') ?? 'dist/esm' });\n } else if (parsed.verb === 'serve') {\n const serve = readServeArguments(parsed);\n if ('refusal' in serve) {\n process.stderr.write(`[wildo-dev-runtime] ${serve.refusal}\\n`);\n return 2;\n }\n const { entry, label } = serve;\n stoppable = new NodeRuntimeDevSupervisor({\n appRoot,\n packageRoot,\n packageName: manifest.name,\n label,\n entry,\n nodeArguments: parsed.nodeArguments,\n compileLoop: new PackageCompileLoop({ appRoot, packageRoot, packageName: manifest.name, project, dist: parsed.values.get('dist') ?? 'dist' }),\n dependencyPackageNames: readWorkspaceDependencyNames(manifest),\n });\n } else if (parsed.verb === 'frontend') {\n const command = parsed.values.get('command');\n if (!command) {\n process.stderr.write('[wildo-dev-runtime] frontend needs --command <dev server> (vite).\\n');\n return 2;\n }\n stoppable = new FrontendDevRuntime({\n packageRoot,\n command,\n compileLoop: new PackageCompileLoop({ appRoot, packageRoot, packageName: manifest.name, project: parsed.values.get('project') ?? 'tsconfig.library.json', dist: parsed.values.get('dist') ?? 'dist/library' }),\n onCommandExit: (code) => endedOnItsOwn?.(code),\n });\n } else {\n process.stderr.write(`[wildo-dev-runtime] Unknown verb \"${parsed.verb ?? ''}\": expected \"compile\", \"serve\" or \"frontend\".\\n`);\n return 2;\n }\n\n /*\n * Whatever ends this process, its children end with it (#3214): a forced stop or an uncaught error would otherwise\n * leave the backend and the compiler re-parented to init, holding the port and serving stale code. `exit` handlers\n * run synchronously, which is why the kill is the synchronous one. Registered BEFORE starting, so a signal that\n * arrives while the compiler and the backend are being spawned still stops them.\n */\n process.on('exit', () => stoppable.killChildrenNow());\n process.on('uncaughtException', (error) => {\n process.stderr.write(`[wildo-dev-runtime] stopping on an unexpected error: ${error.stack ?? error.message}\\n`);\n process.exit(1);\n });\n let resolveStopped: (code: number) => void = () => {};\n const stopped = new Promise<number>((resolve) => { resolveStopped = resolve; });\n let stopping = false;\n const stop = (): void => {\n if (stopping) return;\n stopping = true;\n void stoppable.stop().finally(() => resolveStopped(0));\n };\n for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as const) process.on(signal, stop);\n // The dev server ended by itself: stop the rest and end with ITS code, so whatever supervises this process sees it.\n endedOnItsOwn = (code) => {\n if (stopping) return;\n stopping = true;\n void stoppable.stop().finally(() => resolveStopped(code));\n };\n\n try {\n await stoppable.start();\n } catch (error) {\n process.stderr.write(`[wildo-dev-runtime] ${(error as Error).message}\\n`);\n return 1;\n }\n return await stopped;\n}\n"]}
@@ -0,0 +1,47 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ import type { LocalDevPortHolder } from '../runtime/local-dev-runtime-signals.contracts';
3
+ /**
4
+ * Process identity for the dev runtime: is a pid alive, who is it, who listens on a port, and how a process GROUP is
5
+ * stopped. Every question goes to the operating system's own tools (`ps`, `lsof`), which exist on macOS and on the Linux
6
+ * images Wildo targets; when one is missing the answer is "unknown" (null), never a guess.
7
+ */
8
+ /** Alive means the pid exists; `pid` is `unknown` because readers ask it of values parsed from records. */
9
+ export declare function isProcessAlive(pid: unknown): boolean;
10
+ export interface ProcessIdentity {
11
+ readonly pid: number;
12
+ readonly ppid: number | null;
13
+ readonly startedAt: string | null;
14
+ readonly cwd: string | null;
15
+ readonly command: string | null;
16
+ }
17
+ /** What the operating system says about `pid`; null when the process does not exist (or `ps` cannot be run). */
18
+ export declare function readProcessIdentity(pid: number): ProcessIdentity | null;
19
+ /** The working directory of `pid`, from `lsof`'s `cwd` descriptor; null when it cannot be read. */
20
+ export declare function readProcessWorkingDirectory(pid: number): string | null;
21
+ /**
22
+ * Does anything accept connections on TCP `port` of this machine? Asked by CONNECTING (IPv4 then IPv6 loopback), which
23
+ * needs no tool and cannot mistake "the tool is missing" for "the port is free". Bounded by `timeoutMs` per address.
24
+ */
25
+ export declare function isPortListening(port: number, timeoutMs?: number): Promise<boolean>;
26
+ /**
27
+ * The process LISTENING on TCP `port`, or `null` when NOTHING listens there — established by connecting
28
+ * ({@link isPortListening}), never by the absence of a tool's answer. When something listens but `lsof` cannot say who
29
+ * (a host without it), the holder comes back with `pid: null`: held, by an unknown process.
30
+ */
31
+ export declare function identifyPortHolder(port: number): Promise<LocalDevPortHolder | null>;
32
+ /** Process groups are a POSIX notion; Windows has none, and a negative pid there is an error. */
33
+ export declare function usesProcessGroups(): boolean;
34
+ /**
35
+ * Stop a process and everything it spawned: SIGTERM to its group, then SIGKILL to the group after `graceMs` if the
36
+ * group's LEADER is still alive (the leader is what is observable; the group signal reaches the rest). Resolves once
37
+ * the leader is gone.
38
+ */
39
+ export declare function terminateProcessGroup(pid: number, graceMs?: number): Promise<void>;
40
+ /**
41
+ * SIGKILL a child's whole group NOW, synchronously: the last act of a process that is exiting and cannot wait (an
42
+ * uncaught error, a forced stop), so nothing it spawned outlives it re-parented to init.
43
+ */
44
+ export declare function killProcessGroupNow(pid: number | null | undefined): void;
45
+ /** Resolve when a spawned child has exited (immediately if it already has). */
46
+ export declare function childExited(child: ChildProcess): Promise<void>;
47
+ //# sourceMappingURL=dev-runtime-processes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-runtime-processes.d.ts","sourceRoot":"","sources":["../../../src/dev-runtime/dev-runtime-processes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gDAAgD,CAAC;AAEzF;;;;GAIG;AAEH,2GAA2G;AAC3G,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAQpD;AAYD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAID,gHAAgH;AAChH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAevE;AAED,mGAAmG;AACnG,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKtE;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBtF;AASD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAYzF;AAED,iGAAiG;AACjG,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAgBD;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAatF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAGxE;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9D"}
@@ -0,0 +1,163 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { connect } from 'node:net';
3
+ /**
4
+ * Process identity for the dev runtime: is a pid alive, who is it, who listens on a port, and how a process GROUP is
5
+ * stopped. Every question goes to the operating system's own tools (`ps`, `lsof`), which exist on macOS and on the Linux
6
+ * images Wildo targets; when one is missing the answer is "unknown" (null), never a guess.
7
+ */
8
+ /** Alive means the pid exists; `pid` is `unknown` because readers ask it of values parsed from records. */
9
+ export function isProcessAlive(pid) {
10
+ if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0)
11
+ return false;
12
+ try {
13
+ process.kill(pid, 0);
14
+ return true;
15
+ }
16
+ catch (error) {
17
+ return error.code === 'EPERM';
18
+ }
19
+ }
20
+ function runTool(command, args) {
21
+ try {
22
+ const result = spawnSync(command, [...args], { encoding: 'utf8', timeout: 5000 });
23
+ if (result.error || typeof result.stdout !== 'string')
24
+ return null;
25
+ return result.stdout;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ const MAXIMUM_COMMAND_LENGTH = 400;
32
+ /** What the operating system says about `pid`; null when the process does not exist (or `ps` cannot be run). */
33
+ export function readProcessIdentity(pid) {
34
+ if (!isProcessAlive(pid))
35
+ return null;
36
+ // `lstart` is the full start instant; `command` last because it holds spaces.
37
+ const ps = runTool('ps', ['-o', 'ppid=,lstart=,command=', '-p', String(pid)]);
38
+ let ppid = null;
39
+ let startedAt = null;
40
+ let command = null;
41
+ const match = ps === null ? null : /^\s*(\d+)\s+(\w{3}\s+\w{3}\s+\d+\s+[\d:]+\s+\d{4})\s+(.*)$/u.exec(ps.trim());
42
+ if (match) {
43
+ ppid = Number(match[1]);
44
+ const started = Date.parse(match[2]);
45
+ startedAt = Number.isFinite(started) ? new Date(started).toISOString() : match[2];
46
+ command = match[3].slice(0, MAXIMUM_COMMAND_LENGTH);
47
+ }
48
+ return { pid, ppid, startedAt, cwd: readProcessWorkingDirectory(pid), command };
49
+ }
50
+ /** The working directory of `pid`, from `lsof`'s `cwd` descriptor; null when it cannot be read. */
51
+ export function readProcessWorkingDirectory(pid) {
52
+ const output = runTool('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn']);
53
+ if (output === null)
54
+ return null;
55
+ const line = output.split('\n').find((candidate) => candidate.startsWith('n'));
56
+ return line ? line.slice(1) : null;
57
+ }
58
+ /**
59
+ * Does anything accept connections on TCP `port` of this machine? Asked by CONNECTING (IPv4 then IPv6 loopback), which
60
+ * needs no tool and cannot mistake "the tool is missing" for "the port is free". Bounded by `timeoutMs` per address.
61
+ */
62
+ export async function isPortListening(port, timeoutMs = 1000) {
63
+ for (const host of ['127.0.0.1', '::1']) {
64
+ const accepted = await new Promise((resolve) => {
65
+ const socket = connect({ host, port });
66
+ const settle = (value) => {
67
+ socket.removeAllListeners();
68
+ socket.destroy();
69
+ resolve(value);
70
+ };
71
+ socket.setTimeout(timeoutMs, () => settle(false));
72
+ socket.once('connect', () => settle(true));
73
+ socket.once('error', () => settle(false));
74
+ });
75
+ if (accepted)
76
+ return true;
77
+ }
78
+ return false;
79
+ }
80
+ /** The pid `lsof` reports LISTENING on TCP `port`; `null` when it reports none or cannot be run. */
81
+ function findListeningPid(port) {
82
+ const output = runTool('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-t']);
83
+ if (output === null)
84
+ return null;
85
+ return output.split('\n').map((line) => Number(line.trim())).find((candidate) => Number.isInteger(candidate) && candidate > 0) ?? null;
86
+ }
87
+ /**
88
+ * The process LISTENING on TCP `port`, or `null` when NOTHING listens there — established by connecting
89
+ * ({@link isPortListening}), never by the absence of a tool's answer. When something listens but `lsof` cannot say who
90
+ * (a host without it), the holder comes back with `pid: null`: held, by an unknown process.
91
+ */
92
+ export async function identifyPortHolder(port) {
93
+ if (!(await isPortListening(port)))
94
+ return null;
95
+ const pid = findListeningPid(port);
96
+ const identity = pid === null ? null : readProcessIdentity(pid);
97
+ return {
98
+ port,
99
+ pid,
100
+ ppid: identity?.ppid ?? null,
101
+ startedAt: identity?.startedAt ?? null,
102
+ cwd: identity?.cwd ?? null,
103
+ command: identity?.command ?? null,
104
+ };
105
+ }
106
+ /** Process groups are a POSIX notion; Windows has none, and a negative pid there is an error. */
107
+ export function usesProcessGroups() {
108
+ return process.platform !== 'win32';
109
+ }
110
+ /** Signal a child's whole group when it leads one (it was spawned `detached`), else the child alone. */
111
+ function signalTree(pid, signal) {
112
+ try {
113
+ if (usesProcessGroups())
114
+ process.kill(-pid, signal);
115
+ else
116
+ process.kill(pid, signal);
117
+ }
118
+ catch {
119
+ try {
120
+ process.kill(pid, signal);
121
+ }
122
+ catch {
123
+ // Already gone.
124
+ }
125
+ }
126
+ }
127
+ /**
128
+ * Stop a process and everything it spawned: SIGTERM to its group, then SIGKILL to the group after `graceMs` if the
129
+ * group's LEADER is still alive (the leader is what is observable; the group signal reaches the rest). Resolves once
130
+ * the leader is gone.
131
+ */
132
+ export async function terminateProcessGroup(pid, graceMs = 5000) {
133
+ if (!isProcessAlive(pid))
134
+ return;
135
+ signalTree(pid, 'SIGTERM');
136
+ const deadline = Date.now() + graceMs;
137
+ while (Date.now() < deadline) {
138
+ if (!isProcessAlive(pid))
139
+ return;
140
+ await new Promise((resolve) => setTimeout(resolve, 100));
141
+ }
142
+ signalTree(pid, 'SIGKILL');
143
+ const killDeadline = Date.now() + 2000;
144
+ while (Date.now() < killDeadline && isProcessAlive(pid)) {
145
+ await new Promise((resolve) => setTimeout(resolve, 50));
146
+ }
147
+ }
148
+ /**
149
+ * SIGKILL a child's whole group NOW, synchronously: the last act of a process that is exiting and cannot wait (an
150
+ * uncaught error, a forced stop), so nothing it spawned outlives it re-parented to init.
151
+ */
152
+ export function killProcessGroupNow(pid) {
153
+ if (typeof pid !== 'number' || !isProcessAlive(pid))
154
+ return;
155
+ signalTree(pid, 'SIGKILL');
156
+ }
157
+ /** Resolve when a spawned child has exited (immediately if it already has). */
158
+ export function childExited(child) {
159
+ if (child.exitCode !== null || child.signalCode !== null)
160
+ return Promise.resolve();
161
+ return new Promise((resolve) => child.once('exit', () => resolve()));
162
+ }
163
+ //# sourceMappingURL=dev-runtime-processes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-runtime-processes.js","sourceRoot":"","sources":["../../../src/dev-runtime/dev-runtime-processes.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync, type ChildProcess } from 'node:child_process';\nimport { connect } from 'node:net';\n\nimport type { LocalDevPortHolder } from '../runtime/local-dev-runtime-signals.contracts';\n\n/**\n * Process identity for the dev runtime: is a pid alive, who is it, who listens on a port, and how a process GROUP is\n * stopped. Every question goes to the operating system's own tools (`ps`, `lsof`), which exist on macOS and on the Linux\n * images Wildo targets; when one is missing the answer is \"unknown\" (null), never a guess.\n */\n\n/** Alive means the pid exists; `pid` is `unknown` because readers ask it of values parsed from records. */\nexport function isProcessAlive(pid: unknown): boolean {\n if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\nfunction runTool(command: string, args: readonly string[]): string | null {\n try {\n const result = spawnSync(command, [...args], { encoding: 'utf8', timeout: 5000 });\n if (result.error || typeof result.stdout !== 'string') return null;\n return result.stdout;\n } catch {\n return null;\n }\n}\n\nexport interface ProcessIdentity {\n readonly pid: number;\n readonly ppid: number | null;\n readonly startedAt: string | null;\n readonly cwd: string | null;\n readonly command: string | null;\n}\n\nconst MAXIMUM_COMMAND_LENGTH = 400;\n\n/** What the operating system says about `pid`; null when the process does not exist (or `ps` cannot be run). */\nexport function readProcessIdentity(pid: number): ProcessIdentity | null {\n if (!isProcessAlive(pid)) return null;\n // `lstart` is the full start instant; `command` last because it holds spaces.\n const ps = runTool('ps', ['-o', 'ppid=,lstart=,command=', '-p', String(pid)]);\n let ppid: number | null = null;\n let startedAt: string | null = null;\n let command: string | null = null;\n const match = ps === null ? null : /^\\s*(\\d+)\\s+(\\w{3}\\s+\\w{3}\\s+\\d+\\s+[\\d:]+\\s+\\d{4})\\s+(.*)$/u.exec(ps.trim());\n if (match) {\n ppid = Number(match[1]);\n const started = Date.parse(match[2]!);\n startedAt = Number.isFinite(started) ? new Date(started).toISOString() : match[2]!;\n command = match[3]!.slice(0, MAXIMUM_COMMAND_LENGTH);\n }\n return { pid, ppid, startedAt, cwd: readProcessWorkingDirectory(pid), command };\n}\n\n/** The working directory of `pid`, from `lsof`'s `cwd` descriptor; null when it cannot be read. */\nexport function readProcessWorkingDirectory(pid: number): string | null {\n const output = runTool('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn']);\n if (output === null) return null;\n const line = output.split('\\n').find((candidate) => candidate.startsWith('n'));\n return line ? line.slice(1) : null;\n}\n\n/**\n * Does anything accept connections on TCP `port` of this machine? Asked by CONNECTING (IPv4 then IPv6 loopback), which\n * needs no tool and cannot mistake \"the tool is missing\" for \"the port is free\". Bounded by `timeoutMs` per address.\n */\nexport async function isPortListening(port: number, timeoutMs = 1000): Promise<boolean> {\n for (const host of ['127.0.0.1', '::1']) {\n const accepted = await new Promise<boolean>((resolve) => {\n const socket = connect({ host, port });\n const settle = (value: boolean): void => {\n socket.removeAllListeners();\n socket.destroy();\n resolve(value);\n };\n socket.setTimeout(timeoutMs, () => settle(false));\n socket.once('connect', () => settle(true));\n socket.once('error', () => settle(false));\n });\n if (accepted) return true;\n }\n return false;\n}\n\n/** The pid `lsof` reports LISTENING on TCP `port`; `null` when it reports none or cannot be run. */\nfunction findListeningPid(port: number): number | null {\n const output = runTool('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-t']);\n if (output === null) return null;\n return output.split('\\n').map((line) => Number(line.trim())).find((candidate) => Number.isInteger(candidate) && candidate > 0) ?? null;\n}\n\n/**\n * The process LISTENING on TCP `port`, or `null` when NOTHING listens there — established by connecting\n * ({@link isPortListening}), never by the absence of a tool's answer. When something listens but `lsof` cannot say who\n * (a host without it), the holder comes back with `pid: null`: held, by an unknown process.\n */\nexport async function identifyPortHolder(port: number): Promise<LocalDevPortHolder | null> {\n if (!(await isPortListening(port))) return null;\n const pid = findListeningPid(port);\n const identity = pid === null ? null : readProcessIdentity(pid);\n return {\n port,\n pid,\n ppid: identity?.ppid ?? null,\n startedAt: identity?.startedAt ?? null,\n cwd: identity?.cwd ?? null,\n command: identity?.command ?? null,\n };\n}\n\n/** Process groups are a POSIX notion; Windows has none, and a negative pid there is an error. */\nexport function usesProcessGroups(): boolean {\n return process.platform !== 'win32';\n}\n\n/** Signal a child's whole group when it leads one (it was spawned `detached`), else the child alone. */\nfunction signalTree(pid: number, signal: NodeJS.Signals): void {\n try {\n if (usesProcessGroups()) process.kill(-pid, signal);\n else process.kill(pid, signal);\n } catch {\n try {\n process.kill(pid, signal);\n } catch {\n // Already gone.\n }\n }\n}\n\n/**\n * Stop a process and everything it spawned: SIGTERM to its group, then SIGKILL to the group after `graceMs` if the\n * group's LEADER is still alive (the leader is what is observable; the group signal reaches the rest). Resolves once\n * the leader is gone.\n */\nexport async function terminateProcessGroup(pid: number, graceMs = 5000): Promise<void> {\n if (!isProcessAlive(pid)) return;\n signalTree(pid, 'SIGTERM');\n const deadline = Date.now() + graceMs;\n while (Date.now() < deadline) {\n if (!isProcessAlive(pid)) return;\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n signalTree(pid, 'SIGKILL');\n const killDeadline = Date.now() + 2000;\n while (Date.now() < killDeadline && isProcessAlive(pid)) {\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n}\n\n/**\n * SIGKILL a child's whole group NOW, synchronously: the last act of a process that is exiting and cannot wait (an\n * uncaught error, a forced stop), so nothing it spawned outlives it re-parented to init.\n */\nexport function killProcessGroupNow(pid: number | null | undefined): void {\n if (typeof pid !== 'number' || !isProcessAlive(pid)) return;\n signalTree(pid, 'SIGKILL');\n}\n\n/** Resolve when a spawned child has exited (immediately if it already has). */\nexport function childExited(child: ChildProcess): Promise<void> {\n if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve();\n return new Promise((resolve) => child.once('exit', () => resolve()));\n}\n"]}
@@ -0,0 +1,54 @@
1
+ import { type LocalDevCompileObservation, type LocalDevSupervisorObservation } from '../runtime/local-dev-runtime-signals.contracts';
2
+ /**
3
+ * File-system half of the local dev runtime-signal protocol: where a record lives, how it is written, how it is read.
4
+ * Node-only, which is why it is NOT exported from this package's root (a browser bundle imports the contracts beside it);
5
+ * the `wildo-dev-runtime` bin and the dev companion are its readers and writers.
6
+ */
7
+ /** The marker every Wildo application root carries; the walk that finds it is the one every Wildo tool uses. */
8
+ export declare const APPLICATION_ROOT_MARKER = "wildo.saas.config.ts";
9
+ /** The nearest ancestor of `from` (itself included) holding `wildo.saas.config.ts`, or null. */
10
+ export declare function findApplicationRoot(from: string): string | null;
11
+ /**
12
+ * The compile observation's file name for a package. MUST equal `localDevCompileFactFileName` in
13
+ * `scripts/dev-orchestrator/local-dev-protocol.mjs`, because a framework developer's observer and this bin write the
14
+ * same population and the companion reads it without knowing which wrote it; a test pins the two together.
15
+ */
16
+ export declare function localDevCompileObservationFileName(packageName: string): string;
17
+ export declare function compileObservationPath(appRoot: string, packageName: string): string;
18
+ export declare function compileObservationsDirectory(appRoot: string): string;
19
+ export declare function supervisorObservationPath(appRoot: string, label: string): string;
20
+ /**
21
+ * The RESTART-REQUEST file of a supervised runtime: `<app>/.wildo-saas/runtime-signals/<label>.signal` (#2134). A change
22
+ * to its modification time asks the supervisor to restart the runtime, so the runtime reads its `.env` again.
23
+ *
24
+ * The same path the monorepo's `dev-supervised.sh` scripts hand `runtime-supervisor.mjs`, and the one `wildo secrets set`
25
+ * touches (`runtimeSignalFiles` in the CLI's application-secrets service, which names it after the runtime's folder,
26
+ * the label every generated `dev` script passes). One file per label, so restarting the backend never restarts a minion.
27
+ */
28
+ export declare function runtimeRestartSignalPath(appRoot: string, label: string): string;
29
+ /**
30
+ * Write a record atomically (temporary file, then rename), owner-only like the monorepo observer's: a reader never sees
31
+ * a half-written record, and the records name local paths and process ids nobody else on the machine needs.
32
+ */
33
+ export declare function writeRuntimeSignalRecord(path: string, value: unknown): void;
34
+ /** A compile observation, or undefined when there is none or it is not one this protocol version can read. */
35
+ export declare function readCompileObservation(path: string): LocalDevCompileObservation | undefined;
36
+ /** A supervisor observation, or undefined when there is none or it cannot be read. */
37
+ export declare function readSupervisorObservation(path: string): LocalDevSupervisorObservation | undefined;
38
+ /**
39
+ * How old a record's `heartbeatAt` may be before the record is treated as left behind. Both writers refresh it every
40
+ * 5 s; six missed beats is a producer that is gone, wedged, or reusing a dead producer's pid.
41
+ */
42
+ export declare const RUNTIME_SIGNAL_HEARTBEAT_STALE_AFTER_MS = 30000;
43
+ /**
44
+ * Does a live producer still stand behind this compile record? Its process must exist, it must not have said it
45
+ * STOPPED, and its heartbeat must be fresh: a pid alone can be reused after a reboot, and a record left by a dead or
46
+ * stopped compiler describes nothing anyone is keeping current.
47
+ */
48
+ export declare function isLiveCompileObservation(record: LocalDevCompileObservation, nowMs?: number): boolean;
49
+ /**
50
+ * Does a live producer still stand behind this supervisor record? The monorepo's supervisor writes no heartbeat (it
51
+ * writes on transitions), so a heartbeat is required to be fresh only when the record carries one.
52
+ */
53
+ export declare function isLiveSupervisorObservation(record: LocalDevSupervisorObservation, nowMs?: number): boolean;
54
+ //# sourceMappingURL=dev-runtime-signals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-runtime-signals.d.ts","sourceRoot":"","sources":["../../../src/dev-runtime/dev-runtime-signals.ts"],"names":[],"mappings":"AAKA,OAAO,EAQL,KAAK,0BAA0B,EAC/B,KAAK,6BAA6B,EACnC,MAAM,gDAAgD,CAAC;AAExD;;;;GAIG;AAEH,gHAAgH;AAChH,eAAO,MAAM,uBAAuB,yBAAyB,CAAC;AAE9D,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQ/D;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAI9E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAK3E;AAED,8GAA8G;AAC9G,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,CAO3F;AAED,sFAAsF;AACtF,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,6BAA6B,GAAG,SAAS,CAOjG;AAED;;;GAGG;AACH,eAAO,MAAM,uCAAuC,QAAS,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,0BAA0B,EAAE,KAAK,GAAE,MAAmB,GAAG,OAAO,CAIhH;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,6BAA6B,EAAE,KAAK,GAAE,MAAmB,GAAG,OAAO,CAKtH"}
@@ -0,0 +1,113 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { isProcessAlive } from './dev-runtime-processes.js';
5
+ import { APPLICATION_RUNTIME_SIGNALS_RELATIVE_DIRECTORY, LocalDevCompileState, LocalDevSupervisorState, LOCAL_DEV_COMPILE_OBSERVATIONS_DIRECTORY, LOCAL_DEV_SUPERVISOR_OBSERVATIONS_DIRECTORY, LocalDevCompileObservation_Schema, LocalDevSupervisorObservation_Schema, } from '../runtime/local-dev-runtime-signals.contracts.js';
6
+ /**
7
+ * File-system half of the local dev runtime-signal protocol: where a record lives, how it is written, how it is read.
8
+ * Node-only, which is why it is NOT exported from this package's root (a browser bundle imports the contracts beside it);
9
+ * the `wildo-dev-runtime` bin and the dev companion are its readers and writers.
10
+ */
11
+ /** The marker every Wildo application root carries; the walk that finds it is the one every Wildo tool uses. */
12
+ export const APPLICATION_ROOT_MARKER = 'wildo.saas.config.ts';
13
+ /** The nearest ancestor of `from` (itself included) holding `wildo.saas.config.ts`, or null. */
14
+ export function findApplicationRoot(from) {
15
+ let directory = resolve(from);
16
+ for (;;) {
17
+ if (existsSync(join(directory, APPLICATION_ROOT_MARKER)))
18
+ return directory;
19
+ const parent = dirname(directory);
20
+ if (parent === directory)
21
+ return null;
22
+ directory = parent;
23
+ }
24
+ }
25
+ /**
26
+ * The compile observation's file name for a package. MUST equal `localDevCompileFactFileName` in
27
+ * `scripts/dev-orchestrator/local-dev-protocol.mjs`, because a framework developer's observer and this bin write the
28
+ * same population and the companion reads it without knowing which wrote it; a test pins the two together.
29
+ */
30
+ export function localDevCompileObservationFileName(packageName) {
31
+ const readable = packageName.replace(/^@/, '').replaceAll('/', '__').replaceAll(/[^A-Za-z0-9_.-]/g, '_');
32
+ const digest = createHash('sha256').update(packageName).digest('hex').slice(0, 12);
33
+ return `${readable}-${digest}.json`;
34
+ }
35
+ export function compileObservationPath(appRoot, packageName) {
36
+ return join(appRoot, APPLICATION_RUNTIME_SIGNALS_RELATIVE_DIRECTORY, LOCAL_DEV_COMPILE_OBSERVATIONS_DIRECTORY, localDevCompileObservationFileName(packageName));
37
+ }
38
+ export function compileObservationsDirectory(appRoot) {
39
+ return join(appRoot, APPLICATION_RUNTIME_SIGNALS_RELATIVE_DIRECTORY, LOCAL_DEV_COMPILE_OBSERVATIONS_DIRECTORY);
40
+ }
41
+ export function supervisorObservationPath(appRoot, label) {
42
+ return join(appRoot, APPLICATION_RUNTIME_SIGNALS_RELATIVE_DIRECTORY, LOCAL_DEV_SUPERVISOR_OBSERVATIONS_DIRECTORY, `${label}.json`);
43
+ }
44
+ /**
45
+ * The RESTART-REQUEST file of a supervised runtime: `<app>/.wildo-saas/runtime-signals/<label>.signal` (#2134). A change
46
+ * to its modification time asks the supervisor to restart the runtime, so the runtime reads its `.env` again.
47
+ *
48
+ * The same path the monorepo's `dev-supervised.sh` scripts hand `runtime-supervisor.mjs`, and the one `wildo secrets set`
49
+ * touches (`runtimeSignalFiles` in the CLI's application-secrets service, which names it after the runtime's folder,
50
+ * the label every generated `dev` script passes). One file per label, so restarting the backend never restarts a minion.
51
+ */
52
+ export function runtimeRestartSignalPath(appRoot, label) {
53
+ return join(appRoot, APPLICATION_RUNTIME_SIGNALS_RELATIVE_DIRECTORY, `${label}.signal`);
54
+ }
55
+ /**
56
+ * Write a record atomically (temporary file, then rename), owner-only like the monorepo observer's: a reader never sees
57
+ * a half-written record, and the records name local paths and process ids nobody else on the machine needs.
58
+ */
59
+ export function writeRuntimeSignalRecord(path, value) {
60
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
61
+ const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
62
+ writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
63
+ renameSync(temporary, path);
64
+ }
65
+ /** A compile observation, or undefined when there is none or it is not one this protocol version can read. */
66
+ export function readCompileObservation(path) {
67
+ try {
68
+ const parsed = LocalDevCompileObservation_Schema.safeParse(JSON.parse(readFileSync(path, 'utf8')));
69
+ return parsed.success ? parsed.data : undefined;
70
+ }
71
+ catch {
72
+ return undefined;
73
+ }
74
+ }
75
+ /** A supervisor observation, or undefined when there is none or it cannot be read. */
76
+ export function readSupervisorObservation(path) {
77
+ try {
78
+ const parsed = LocalDevSupervisorObservation_Schema.safeParse(JSON.parse(readFileSync(path, 'utf8')));
79
+ return parsed.success ? parsed.data : undefined;
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ }
85
+ /**
86
+ * How old a record's `heartbeatAt` may be before the record is treated as left behind. Both writers refresh it every
87
+ * 5 s; six missed beats is a producer that is gone, wedged, or reusing a dead producer's pid.
88
+ */
89
+ export const RUNTIME_SIGNAL_HEARTBEAT_STALE_AFTER_MS = 30_000;
90
+ /**
91
+ * Does a live producer still stand behind this compile record? Its process must exist, it must not have said it
92
+ * STOPPED, and its heartbeat must be fresh: a pid alone can be reused after a reboot, and a record left by a dead or
93
+ * stopped compiler describes nothing anyone is keeping current.
94
+ */
95
+ export function isLiveCompileObservation(record, nowMs = Date.now()) {
96
+ if (record.state === LocalDevCompileState.STOPPED || !isProcessAlive(record.producerPid))
97
+ return false;
98
+ const heartbeatAtMs = Date.parse(record.heartbeatAt);
99
+ return Number.isFinite(heartbeatAtMs) && nowMs - heartbeatAtMs <= RUNTIME_SIGNAL_HEARTBEAT_STALE_AFTER_MS;
100
+ }
101
+ /**
102
+ * Does a live producer still stand behind this supervisor record? The monorepo's supervisor writes no heartbeat (it
103
+ * writes on transitions), so a heartbeat is required to be fresh only when the record carries one.
104
+ */
105
+ export function isLiveSupervisorObservation(record, nowMs = Date.now()) {
106
+ if (record.state === LocalDevSupervisorState.STOPPED || !isProcessAlive(record.producerPid))
107
+ return false;
108
+ if (typeof record.heartbeatAt !== 'string')
109
+ return true;
110
+ const heartbeatAtMs = Date.parse(record.heartbeatAt);
111
+ return Number.isFinite(heartbeatAtMs) && nowMs - heartbeatAtMs <= RUNTIME_SIGNAL_HEARTBEAT_STALE_AFTER_MS;
112
+ }
113
+ //# sourceMappingURL=dev-runtime-signals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-runtime-signals.js","sourceRoot":"","sources":["../../../src/dev-runtime/dev-runtime-signals.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\n\nimport { isProcessAlive } from './dev-runtime-processes';\nimport {\n APPLICATION_RUNTIME_SIGNALS_RELATIVE_DIRECTORY,\n LocalDevCompileState,\n LocalDevSupervisorState,\n LOCAL_DEV_COMPILE_OBSERVATIONS_DIRECTORY,\n LOCAL_DEV_SUPERVISOR_OBSERVATIONS_DIRECTORY,\n LocalDevCompileObservation_Schema,\n LocalDevSupervisorObservation_Schema,\n type LocalDevCompileObservation,\n type LocalDevSupervisorObservation,\n} from '../runtime/local-dev-runtime-signals.contracts';\n\n/**\n * File-system half of the local dev runtime-signal protocol: where a record lives, how it is written, how it is read.\n * Node-only, which is why it is NOT exported from this package's root (a browser bundle imports the contracts beside it);\n * the `wildo-dev-runtime` bin and the dev companion are its readers and writers.\n */\n\n/** The marker every Wildo application root carries; the walk that finds it is the one every Wildo tool uses. */\nexport const APPLICATION_ROOT_MARKER = 'wildo.saas.config.ts';\n\n/** The nearest ancestor of `from` (itself included) holding `wildo.saas.config.ts`, or null. */\nexport function findApplicationRoot(from: string): string | null {\n let directory = resolve(from);\n for (;;) {\n if (existsSync(join(directory, APPLICATION_ROOT_MARKER))) return directory;\n const parent = dirname(directory);\n if (parent === directory) return null;\n directory = parent;\n }\n}\n\n/**\n * The compile observation's file name for a package. MUST equal `localDevCompileFactFileName` in\n * `scripts/dev-orchestrator/local-dev-protocol.mjs`, because a framework developer's observer and this bin write the\n * same population and the companion reads it without knowing which wrote it; a test pins the two together.\n */\nexport function localDevCompileObservationFileName(packageName: string): string {\n const readable = packageName.replace(/^@/, '').replaceAll('/', '__').replaceAll(/[^A-Za-z0-9_.-]/g, '_');\n const digest = createHash('sha256').update(packageName).digest('hex').slice(0, 12);\n return `${readable}-${digest}.json`;\n}\n\nexport function compileObservationPath(appRoot: string, packageName: string): string {\n return join(appRoot, APPLICATION_RUNTIME_SIGNALS_RELATIVE_DIRECTORY, LOCAL_DEV_COMPILE_OBSERVATIONS_DIRECTORY, localDevCompileObservationFileName(packageName));\n}\n\nexport function compileObservationsDirectory(appRoot: string): string {\n return join(appRoot, APPLICATION_RUNTIME_SIGNALS_RELATIVE_DIRECTORY, LOCAL_DEV_COMPILE_OBSERVATIONS_DIRECTORY);\n}\n\nexport function supervisorObservationPath(appRoot: string, label: string): string {\n return join(appRoot, APPLICATION_RUNTIME_SIGNALS_RELATIVE_DIRECTORY, LOCAL_DEV_SUPERVISOR_OBSERVATIONS_DIRECTORY, `${label}.json`);\n}\n\n/**\n * The RESTART-REQUEST file of a supervised runtime: `<app>/.wildo-saas/runtime-signals/<label>.signal` (#2134). A change\n * to its modification time asks the supervisor to restart the runtime, so the runtime reads its `.env` again.\n *\n * The same path the monorepo's `dev-supervised.sh` scripts hand `runtime-supervisor.mjs`, and the one `wildo secrets set`\n * touches (`runtimeSignalFiles` in the CLI's application-secrets service, which names it after the runtime's folder,\n * the label every generated `dev` script passes). One file per label, so restarting the backend never restarts a minion.\n */\nexport function runtimeRestartSignalPath(appRoot: string, label: string): string {\n return join(appRoot, APPLICATION_RUNTIME_SIGNALS_RELATIVE_DIRECTORY, `${label}.signal`);\n}\n\n/**\n * Write a record atomically (temporary file, then rename), owner-only like the monorepo observer's: a reader never sees\n * a half-written record, and the records name local paths and process ids nobody else on the machine needs.\n */\nexport function writeRuntimeSignalRecord(path: string, value: unknown): void {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;\n writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\\n`, { encoding: 'utf8', mode: 0o600 });\n renameSync(temporary, path);\n}\n\n/** A compile observation, or undefined when there is none or it is not one this protocol version can read. */\nexport function readCompileObservation(path: string): LocalDevCompileObservation | undefined {\n try {\n const parsed = LocalDevCompileObservation_Schema.safeParse(JSON.parse(readFileSync(path, 'utf8')));\n return parsed.success ? parsed.data : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** A supervisor observation, or undefined when there is none or it cannot be read. */\nexport function readSupervisorObservation(path: string): LocalDevSupervisorObservation | undefined {\n try {\n const parsed = LocalDevSupervisorObservation_Schema.safeParse(JSON.parse(readFileSync(path, 'utf8')));\n return parsed.success ? parsed.data : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * How old a record's `heartbeatAt` may be before the record is treated as left behind. Both writers refresh it every\n * 5 s; six missed beats is a producer that is gone, wedged, or reusing a dead producer's pid.\n */\nexport const RUNTIME_SIGNAL_HEARTBEAT_STALE_AFTER_MS = 30_000;\n\n/**\n * Does a live producer still stand behind this compile record? Its process must exist, it must not have said it\n * STOPPED, and its heartbeat must be fresh: a pid alone can be reused after a reboot, and a record left by a dead or\n * stopped compiler describes nothing anyone is keeping current.\n */\nexport function isLiveCompileObservation(record: LocalDevCompileObservation, nowMs: number = Date.now()): boolean {\n if (record.state === LocalDevCompileState.STOPPED || !isProcessAlive(record.producerPid)) return false;\n const heartbeatAtMs = Date.parse(record.heartbeatAt);\n return Number.isFinite(heartbeatAtMs) && nowMs - heartbeatAtMs <= RUNTIME_SIGNAL_HEARTBEAT_STALE_AFTER_MS;\n}\n\n/**\n * Does a live producer still stand behind this supervisor record? The monorepo's supervisor writes no heartbeat (it\n * writes on transitions), so a heartbeat is required to be fresh only when the record carries one.\n */\nexport function isLiveSupervisorObservation(record: LocalDevSupervisorObservation, nowMs: number = Date.now()): boolean {\n if (record.state === LocalDevSupervisorState.STOPPED || !isProcessAlive(record.producerPid)) return false;\n if (typeof record.heartbeatAt !== 'string') return true;\n const heartbeatAtMs = Date.parse(record.heartbeatAt);\n return Number.isFinite(heartbeatAtMs) && nowMs - heartbeatAtMs <= RUNTIME_SIGNAL_HEARTBEAT_STALE_AFTER_MS;\n}\n"]}
@@ -0,0 +1,14 @@
1
+ export interface UnresolvableEmittedSpecifier {
2
+ /** Path relative to the scanned tree. */
3
+ readonly file: string;
4
+ readonly specifier: string;
5
+ }
6
+ /** Every extensionless relative specifier in one file's source text. */
7
+ export declare function findUnresolvableRelativeSpecifiers(sourceText: string): string[];
8
+ /**
9
+ * Scan the emitted JavaScript under `distRoot` that was written at or after `sinceMs` (an incremental build rewrites
10
+ * only what changed, and every older file was verified when it was written). `sinceMs` of 0 scans the whole tree.
11
+ * Returns at most `limit` findings: one is enough to refuse, a few are enough to name the cause.
12
+ */
13
+ export declare function scanEmittedTreeForUnresolvableSpecifiers(distRoot: string, sinceMs?: number, limit?: number): UnresolvableEmittedSpecifier[];
14
+ //# sourceMappingURL=emitted-relative-specifiers.d.ts.map