@powerlines/engine 0.15.0 → 0.15.1

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 (195) hide show
  1. package/dist/api/build.cjs +66 -0
  2. package/dist/api/build.d.cts +14 -0
  3. package/dist/api/build.d.cts.map +1 -0
  4. package/dist/api/build.d.mts +14 -0
  5. package/dist/api/build.d.mts.map +1 -0
  6. package/dist/api/build.mjs +64 -0
  7. package/dist/api/build.mjs.map +1 -0
  8. package/dist/api/clean.cjs +29 -0
  9. package/dist/api/clean.d.cts +14 -0
  10. package/dist/api/clean.d.cts.map +1 -0
  11. package/dist/api/clean.d.mts +14 -0
  12. package/dist/api/clean.d.mts.map +1 -0
  13. package/dist/api/clean.mjs +28 -0
  14. package/dist/api/clean.mjs.map +1 -0
  15. package/dist/api/create.cjs +59 -0
  16. package/dist/api/create.d.cts +14 -0
  17. package/dist/api/create.d.cts.map +1 -0
  18. package/dist/api/create.d.mts +14 -0
  19. package/dist/api/create.d.mts.map +1 -0
  20. package/dist/api/create.mjs +57 -0
  21. package/dist/api/create.mjs.map +1 -0
  22. package/dist/api/deploy.cjs +25 -0
  23. package/dist/api/deploy.d.cts +17 -0
  24. package/dist/api/deploy.d.cts.map +1 -0
  25. package/dist/api/deploy.d.mts +17 -0
  26. package/dist/api/deploy.d.mts.map +1 -0
  27. package/dist/api/deploy.mjs +24 -0
  28. package/dist/api/deploy.mjs.map +1 -0
  29. package/dist/api/docs.cjs +25 -0
  30. package/dist/api/docs.d.cts +17 -0
  31. package/dist/api/docs.d.cts.map +1 -0
  32. package/dist/api/docs.d.mts +17 -0
  33. package/dist/api/docs.d.mts.map +1 -0
  34. package/dist/api/docs.mjs +24 -0
  35. package/dist/api/docs.mjs.map +1 -0
  36. package/dist/api/lint.cjs +28 -0
  37. package/dist/api/lint.d.cts +17 -0
  38. package/dist/api/lint.d.cts.map +1 -0
  39. package/dist/api/lint.d.mts +17 -0
  40. package/dist/api/lint.d.mts.map +1 -0
  41. package/dist/api/lint.mjs +27 -0
  42. package/dist/api/lint.mjs.map +1 -0
  43. package/dist/api/prepare.cjs +69 -0
  44. package/dist/api/prepare.d.cts +14 -0
  45. package/dist/api/prepare.d.cts.map +1 -0
  46. package/dist/api/prepare.d.mts +14 -0
  47. package/dist/api/prepare.d.mts.map +1 -0
  48. package/dist/api/prepare.mjs +68 -0
  49. package/dist/api/prepare.mjs.map +1 -0
  50. package/dist/api/test.cjs +28 -0
  51. package/dist/api/test.d.cts +17 -0
  52. package/dist/api/test.d.cts.map +1 -0
  53. package/dist/api/test.d.mts +17 -0
  54. package/dist/api/test.d.mts.map +1 -0
  55. package/dist/api/test.mjs +27 -0
  56. package/dist/api/test.mjs.map +1 -0
  57. package/dist/api/types.cjs +71 -0
  58. package/dist/api/types.d.cts +17 -0
  59. package/dist/api/types.d.cts.map +1 -0
  60. package/dist/api/types.d.mts +17 -0
  61. package/dist/api/types.d.mts.map +1 -0
  62. package/dist/api/types.mjs +70 -0
  63. package/dist/api/types.mjs.map +1 -0
  64. package/dist/api-6w4hZL6n.d.cts +135 -0
  65. package/dist/api-6w4hZL6n.d.cts.map +1 -0
  66. package/dist/api-Cdw4v0W4.d.mts +135 -0
  67. package/dist/api-Cdw4v0W4.d.mts.map +1 -0
  68. package/dist/config-BNe23XHx.d.mts +204 -0
  69. package/dist/config-BNe23XHx.d.mts.map +1 -0
  70. package/dist/config-D6xUniHh.d.cts +204 -0
  71. package/dist/config-D6xUniHh.d.cts.map +1 -0
  72. package/dist/context/engine-context.cjs +175 -0
  73. package/dist/context/engine-context.d.cts +2 -0
  74. package/dist/context/engine-context.d.mts +2 -0
  75. package/dist/context/engine-context.mjs +173 -0
  76. package/dist/context/engine-context.mjs.map +1 -0
  77. package/dist/context/index.cjs +2 -9
  78. package/dist/context/index.d.cts +2 -660
  79. package/dist/context/index.d.mts +2 -660
  80. package/dist/context/index.mjs +2 -4
  81. package/dist/context-DzgsMSWr.d.mts +149 -0
  82. package/dist/context-DzgsMSWr.d.mts.map +1 -0
  83. package/dist/context-epL7NPvL.d.cts +149 -0
  84. package/dist/context-epL7NPvL.d.cts.map +1 -0
  85. package/dist/engine-context-DEotmVzB.d.mts +54 -0
  86. package/dist/engine-context-DEotmVzB.d.mts.map +1 -0
  87. package/dist/engine-context-Dw8odBCo.d.cts +54 -0
  88. package/dist/engine-context-Dw8odBCo.d.cts.map +1 -0
  89. package/dist/engine.cjs +268 -0
  90. package/dist/engine.d.cts +143 -0
  91. package/dist/engine.d.cts.map +1 -0
  92. package/dist/engine.d.mts +143 -0
  93. package/dist/engine.d.mts.map +1 -0
  94. package/dist/engine.mjs +264 -0
  95. package/dist/engine.mjs.map +1 -0
  96. package/dist/execution-host.cjs +44 -0
  97. package/dist/execution-host.d.cts +14 -0
  98. package/dist/execution-host.d.cts.map +1 -0
  99. package/dist/execution-host.d.mts +14 -0
  100. package/dist/execution-host.d.mts.map +1 -0
  101. package/dist/execution-host.mjs +36 -0
  102. package/dist/execution-host.mjs.map +1 -0
  103. package/dist/helpers/create-execution-host.cjs +51 -0
  104. package/dist/helpers/create-execution-host.d.cts +21 -0
  105. package/dist/helpers/create-execution-host.d.cts.map +1 -0
  106. package/dist/helpers/create-execution-host.d.mts +21 -0
  107. package/dist/helpers/create-execution-host.d.mts.map +1 -0
  108. package/dist/helpers/create-execution-host.mjs +50 -0
  109. package/dist/helpers/create-execution-host.mjs.map +1 -0
  110. package/dist/helpers/execution-host-worker.cjs +308 -0
  111. package/dist/helpers/execution-host-worker.d.cts +68 -0
  112. package/dist/helpers/execution-host-worker.d.cts.map +1 -0
  113. package/dist/helpers/execution-host-worker.d.mts +68 -0
  114. package/dist/helpers/execution-host-worker.d.mts.map +1 -0
  115. package/dist/helpers/execution-host-worker.mjs +307 -0
  116. package/dist/helpers/execution-host-worker.mjs.map +1 -0
  117. package/dist/helpers/finalize.cjs +27 -0
  118. package/dist/helpers/finalize.d.cts +12 -0
  119. package/dist/helpers/finalize.d.cts.map +1 -0
  120. package/dist/helpers/finalize.d.mts +12 -0
  121. package/dist/helpers/finalize.d.mts.map +1 -0
  122. package/dist/helpers/finalize.mjs +26 -0
  123. package/dist/helpers/finalize.mjs.map +1 -0
  124. package/dist/helpers/rpc.cjs +140 -0
  125. package/dist/helpers/rpc.d.cts +7 -0
  126. package/dist/helpers/rpc.d.cts.map +1 -0
  127. package/dist/helpers/rpc.d.mts +7 -0
  128. package/dist/helpers/rpc.d.mts.map +1 -0
  129. package/dist/helpers/rpc.mjs +139 -0
  130. package/dist/helpers/rpc.mjs.map +1 -0
  131. package/dist/index-CNgSR_kt.d.mts +1 -0
  132. package/dist/index-D_TYgLX3.d.cts +1 -0
  133. package/dist/index.cjs +13 -567
  134. package/dist/index.d.cts +9 -126
  135. package/dist/index.d.mts +9 -126
  136. package/dist/index.mjs +7 -567
  137. package/package.json +101 -129
  138. package/dist/_internal/worker.cjs +0 -4843
  139. package/dist/_internal/worker.d.cts +0 -38
  140. package/dist/_internal/worker.d.cts.map +0 -1
  141. package/dist/_internal/worker.d.mts +0 -38
  142. package/dist/_internal/worker.d.mts.map +0 -1
  143. package/dist/_internal/worker.mjs +0 -4804
  144. package/dist/_internal/worker.mjs.map +0 -1
  145. package/dist/api.cjs +0 -1274
  146. package/dist/api.d.cts +0 -265
  147. package/dist/api.d.cts.map +0 -1
  148. package/dist/api.d.mts +0 -265
  149. package/dist/api.d.mts.map +0 -1
  150. package/dist/api.mjs +0 -1270
  151. package/dist/api.mjs.map +0 -1
  152. package/dist/base-context-BUnL_9z8.mjs +0 -241
  153. package/dist/base-context-BUnL_9z8.mjs.map +0 -1
  154. package/dist/base-context-CFvO2N9I.cjs +0 -248
  155. package/dist/context/index.d.cts.map +0 -1
  156. package/dist/context/index.d.mts.map +0 -1
  157. package/dist/engine-context-BuD9AGfd.mjs +0 -41
  158. package/dist/engine-context-BuD9AGfd.mjs.map +0 -1
  159. package/dist/engine-context-NO6enYev.cjs +0 -45
  160. package/dist/execution-context-BgGV4xyW.cjs +0 -2614
  161. package/dist/execution-context-D_CXpe9I.mjs +0 -2570
  162. package/dist/execution-context-D_CXpe9I.mjs.map +0 -1
  163. package/dist/fs-D1nIP45P.mjs +0 -226
  164. package/dist/fs-D1nIP45P.mjs.map +0 -1
  165. package/dist/fs-XogSgMqT.cjs +0 -262
  166. package/dist/index.d.cts.map +0 -1
  167. package/dist/index.d.mts.map +0 -1
  168. package/dist/index.mjs.map +0 -1
  169. package/dist/schemas.cjs +0 -9
  170. package/dist/schemas.d.cts +0 -127
  171. package/dist/schemas.d.cts.map +0 -1
  172. package/dist/schemas.d.mts +0 -127
  173. package/dist/schemas.d.mts.map +0 -1
  174. package/dist/schemas.mjs +0 -3
  175. package/dist/storage/index.cjs +0 -9
  176. package/dist/storage/index.d.cts +0 -419
  177. package/dist/storage/index.d.cts.map +0 -1
  178. package/dist/storage/index.d.mts +0 -419
  179. package/dist/storage/index.d.mts.map +0 -1
  180. package/dist/storage/index.mjs +0 -3
  181. package/dist/ts-morph-BaLPVAdB.cjs +0 -114
  182. package/dist/ts-morph-D0CaA37w.mjs +0 -102
  183. package/dist/ts-morph-D0CaA37w.mjs.map +0 -1
  184. package/dist/tsconfig-Cstsoprg.mjs +0 -155
  185. package/dist/tsconfig-Cstsoprg.mjs.map +0 -1
  186. package/dist/tsconfig-DeyWQC2N.cjs +0 -198
  187. package/dist/typescript/index.cjs +0 -13
  188. package/dist/typescript/index.d.cts +0 -106
  189. package/dist/typescript/index.d.cts.map +0 -1
  190. package/dist/typescript/index.d.mts +0 -106
  191. package/dist/typescript/index.d.mts.map +0 -1
  192. package/dist/typescript/index.mjs +0 -4
  193. package/dist/virtual-BNdKVkRw.cjs +0 -548
  194. package/dist/virtual-gIlTc3Lj.mjs +0 -513
  195. package/dist/virtual-gIlTc3Lj.mjs.map +0 -1
@@ -0,0 +1,307 @@
1
+ import { getDefaultMode } from "@powerlines/core/lib/config";
2
+ import { resolve } from "@stryke/fs/resolve";
3
+ import { appendPath } from "@stryke/path/append";
4
+ import { isNumber } from "@stryke/type-checks/is-number";
5
+ import { isSet } from "@stryke/type-checks/is-set";
6
+ import { isSetObject } from "@stryke/type-checks/is-set-object";
7
+ import { isString } from "@stryke/type-checks/is-string";
8
+ import { formatDuration } from "date-fns/formatDuration";
9
+ import { Worker } from "jest-worker";
10
+ import { Transform } from "node:stream";
11
+ import { parseArgs } from "node:util";
12
+
13
+ //#region src/helpers/execution-host-worker.ts
14
+ const RESTARTED = Symbol("powerlines-worker:restarted");
15
+ /**
16
+ * Formats the debug address into a string.
17
+ */
18
+ const formatDebugAddress = ({ host, port }) => {
19
+ return host ? `${host}:${port}` : `${port}`;
20
+ };
21
+ /**
22
+ * Get's the debug address from the `NODE_OPTIONS` environment variable. If the
23
+ * address is not found, it returns the default host (`undefined`) and port
24
+ * (`9229`).
25
+ *
26
+ * @returns An object with the host and port of the debug address.
27
+ */
28
+ const getParsedDebugAddress = (address) => {
29
+ if (!address || !isString(address)) return {
30
+ host: void 0,
31
+ port: 9229
32
+ };
33
+ if (address.includes(":")) {
34
+ const [host, port] = address.split(":");
35
+ if (!host || !port) throw new Error(`Invalid debug address: ${address}`);
36
+ return {
37
+ host,
38
+ port: Number.parseInt(port, 10)
39
+ };
40
+ }
41
+ return {
42
+ host: void 0,
43
+ port: Number.parseInt(address, 10)
44
+ };
45
+ };
46
+ /**
47
+ * Get the debug type from the `NODE_OPTIONS` environment variable.
48
+ */
49
+ function getNodeDebugType(nodeOptions) {
50
+ if (nodeOptions.inspect) return "inspect";
51
+ if (nodeOptions["inspect-brk"] || nodeOptions.inspect_brk) return "inspect-brk";
52
+ }
53
+ const cleanupWorkers = (worker) => {
54
+ for (const curWorker of worker._workerPool?._workers || []) curWorker._child?.kill("SIGINT");
55
+ };
56
+ var ExecutionHostWorker = class ExecutionHostWorker {
57
+ #worker;
58
+ /**
59
+ * Creates a new instance of the ExecutionHostWorker class, which manages a worker process for executing tasks related to the Powerlines Engine. The worker is initialized with the specified options and can be used to run tasks in an isolated environment, with support for automatic restarts and activity monitoring.
60
+ *
61
+ * @param executionHostPath - The path to the Execution Host file.
62
+ * @param options - The options for configuring the worker, including the execution context, exposed methods, timeout, and mode.
63
+ * @returns A promise that resolves to an instance of the ExecutionHostWorker class.
64
+ */
65
+ static async from(executionHostPath, options) {
66
+ const mode = await getDefaultMode(options.context.cwd);
67
+ const resolvedPath = await resolve(executionHostPath, { paths: [options.context.cwd, options.root ? appendPath(options.root, options.context.cwd) : void 0].filter(Boolean) });
68
+ if (!resolvedPath) throw new Error(`Could not resolve the provided Execution Host path: \`${executionHostPath}\`.`);
69
+ return new ExecutionHostWorker(resolvedPath, {
70
+ mode,
71
+ ...options
72
+ });
73
+ }
74
+ /**
75
+ * Create a new worker instance.
76
+ *
77
+ * @param executionHostPath - The path to the worker file.
78
+ * @param options - The options for the worker, including exposed methods, timeout, and hooks for activity and restart.
79
+ */
80
+ constructor(executionHostPath, options) {
81
+ this.executionHostPath = executionHostPath;
82
+ this.options = options;
83
+ const { timeout = 9e5, isolatedMemory = false, mode = "production", context, executionMethods } = this.options;
84
+ const logger = context.extendLogger({ category: "communication" });
85
+ let restartPromise;
86
+ let resolveRestartPromise;
87
+ let activeTasks = 0;
88
+ this.#worker = void 0;
89
+ process.on("exit", () => {
90
+ this.close();
91
+ });
92
+ let nodeOptions = {};
93
+ const args = [...process.execArgv];
94
+ if (process.env.NODE_OPTIONS) {
95
+ let isInString = false;
96
+ let willStartNewArg = true;
97
+ for (let i = 0; i < process.env.NODE_OPTIONS.length; i++) {
98
+ let char = process.env.NODE_OPTIONS[i];
99
+ if (char) {
100
+ if (char === "\\" && isInString) {
101
+ if (process.env.NODE_OPTIONS.length === i + 1) throw new Error("Invalid escape character at the end.");
102
+ char = process.env.NODE_OPTIONS[++i];
103
+ if (!char) continue;
104
+ } else if (char === " " && !isInString) {
105
+ willStartNewArg = true;
106
+ continue;
107
+ } else if (char === "\"") {
108
+ isInString = !isInString;
109
+ continue;
110
+ }
111
+ if (willStartNewArg) {
112
+ args.push(char);
113
+ willStartNewArg = false;
114
+ } else args[args.length - 1] += char;
115
+ }
116
+ }
117
+ if (isInString) throw new Error("Unterminated string");
118
+ }
119
+ if (args.length > 0) {
120
+ const { values, tokens } = parseArgs({
121
+ args,
122
+ strict: false,
123
+ tokens: true
124
+ });
125
+ nodeOptions = values;
126
+ let orphan = null;
127
+ for (let i = 0; i < tokens.length; i++) {
128
+ const token = tokens[i];
129
+ if (!token) continue;
130
+ if (token.kind === "option-terminator") break;
131
+ if (token.kind === "option") {
132
+ orphan = !isSet(token.value) ? token : null;
133
+ continue;
134
+ }
135
+ if (token.kind !== "positional") {
136
+ orphan = null;
137
+ continue;
138
+ }
139
+ if (!orphan) continue;
140
+ if (orphan.name in nodeOptions && isString(nodeOptions[orphan.name])) nodeOptions[orphan.name] += ` ${token.value}`;
141
+ else nodeOptions[orphan.name] = token.value;
142
+ }
143
+ }
144
+ const originalOptions = { ...nodeOptions };
145
+ delete nodeOptions.inspect;
146
+ delete nodeOptions["inspect-brk"];
147
+ delete nodeOptions.inspect_brk;
148
+ if (mode === "development") {
149
+ const nodeDebugType = getNodeDebugType(originalOptions);
150
+ if (nodeDebugType) {
151
+ const debuggerAddress = getParsedDebugAddress(originalOptions[nodeDebugType]);
152
+ const address = {
153
+ host: debuggerAddress.host,
154
+ port: debuggerAddress.port === 0 ? 0 : debuggerAddress.port + 1 + 1
155
+ };
156
+ nodeOptions[nodeDebugType] = formatDebugAddress(address);
157
+ }
158
+ nodeOptions["enable-source-maps"] = true;
159
+ }
160
+ if (isolatedMemory) {
161
+ delete nodeOptions["max-old-space-size"];
162
+ delete nodeOptions.max_old_space_size;
163
+ }
164
+ const execArgv = [];
165
+ const nodeOptionsParts = [];
166
+ for (const [key, value] of Object.entries(nodeOptions)) {
167
+ let formatted = null;
168
+ if (value === true) formatted = `--${key}`;
169
+ else if (value) formatted = `--${key}=${value.includes(" ") && !value.startsWith("\"") ? JSON.stringify(value) : value}`;
170
+ if (formatted === null) continue;
171
+ if ([
172
+ "experimental-network-inspection",
173
+ "experimental-storage-inspection",
174
+ "experimental-worker-inspection",
175
+ "experimental-inspector-network-resource"
176
+ ].includes(key)) execArgv.push(formatted);
177
+ else nodeOptionsParts.push(formatted);
178
+ }
179
+ const onHanging = () => {
180
+ const worker = this.#worker;
181
+ if (!worker) return;
182
+ const resolve = resolveRestartPromise;
183
+ createWorker();
184
+ logger.warn(`Sending SIGTERM signal to worker due to timeout${timeout ? ` of ${formatDuration({ seconds: timeout / 1e3 })}` : ""}. Subsequent errors may be a result of the worker exiting.`);
185
+ worker.end().then(() => {
186
+ resolve(RESTARTED);
187
+ });
188
+ };
189
+ let hangingTimer = false;
190
+ const onActivity = () => {
191
+ if (hangingTimer) clearTimeout(hangingTimer);
192
+ hangingTimer = activeTasks > 0 && setTimeout(onHanging, timeout);
193
+ };
194
+ const createWorker = () => {
195
+ const env = {
196
+ ...process.env,
197
+ NODE_ENV: mode,
198
+ NODE_OPTIONS: nodeOptionsParts.join(" "),
199
+ POWERLINES_EXECUTION_HOST_WORKER: "true"
200
+ };
201
+ if (env.FORCE_COLOR === void 0) {
202
+ if (!env.NO_COLOR && !env.CI && env.TERM !== "dumb" && (process.stdout.isTTY || process.stderr?.isTTY)) env.FORCE_COLOR = "1";
203
+ }
204
+ this.#worker = new Worker(executionHostPath, {
205
+ maxRetries: 0,
206
+ computeWorkerKey: (_, ...args) => {
207
+ let executionId = "default";
208
+ let configIndex = 0;
209
+ if (args.length > 0 && isSetObject(args[0])) {
210
+ const arg = args[0];
211
+ if (isSetObject(arg.options)) {
212
+ configIndex = arg.options.configIndex ?? 0;
213
+ executionId = arg.options.executionId || "default";
214
+ }
215
+ }
216
+ return `${executionId}-${configIndex}`;
217
+ },
218
+ forkOptions: {
219
+ execArgv,
220
+ env
221
+ }
222
+ });
223
+ restartPromise = new Promise((resolve) => {
224
+ resolveRestartPromise = resolve;
225
+ });
226
+ for (const worker of this.#worker._workerPool?._workers || []) {
227
+ worker._child?.on("exit", (code, signal) => {
228
+ logger.debug(`Worker process exited with code ${code} and signal ${signal}`);
229
+ if ((code || signal && signal !== "SIGINT") && this.#worker) {
230
+ const error = /* @__PURE__ */ new Error(`Execution Host Worker exited unexpectedly with code ${code} and signal ${signal}`);
231
+ logger.error(error);
232
+ throw error;
233
+ }
234
+ });
235
+ worker._child?.on("error", (error) => {
236
+ logger.error({
237
+ meta: { category: "communication" },
238
+ message: `Worker process emitted an error: ${error.message}`,
239
+ error
240
+ });
241
+ });
242
+ worker._child?.on("message", (data) => {
243
+ onActivity();
244
+ if (Array.isArray(data) && data.length > 1 && isNumber(data[0])) if (data[0] === 0) logger.trace(`Received message from worker: ${JSON.stringify(data.slice(1), null, 2)}`);
245
+ else logger.debug(`Received error message from worker: ${JSON.stringify(data.slice(1), null, 2)}`);
246
+ logger.trace(`Received message from worker: ${JSON.stringify(data, null, 2)}`);
247
+ });
248
+ }
249
+ let aborted = false;
250
+ const onActivityAbort = () => {
251
+ if (!aborted) aborted = true;
252
+ };
253
+ const abortActivityStreamOnLog = new Transform({ transform(_chunk, _encoding, callback) {
254
+ onActivityAbort();
255
+ callback();
256
+ } });
257
+ this.#worker.getStdout().pipe(abortActivityStreamOnLog);
258
+ this.#worker.getStderr().pipe(abortActivityStreamOnLog);
259
+ this.#worker.getStdout().pipe(process.stdout);
260
+ this.#worker.getStderr().pipe(process.stderr);
261
+ };
262
+ createWorker();
263
+ for (const method of executionMethods) {
264
+ if (method.startsWith("_")) continue;
265
+ this[method] = timeout ? async (...args) => {
266
+ activeTasks++;
267
+ try {
268
+ let attempts = 0;
269
+ for (;;) {
270
+ onActivity();
271
+ const result = await Promise.race([this.#worker[method](args.length > 0 && args[0] ? args[0] : {}), restartPromise]);
272
+ if (result !== RESTARTED) return result;
273
+ logger.warn(`Execution Host Worker was restarted while calling method "${method}" (attempt ${attempts++}). Retrying the call...`);
274
+ }
275
+ } finally {
276
+ activeTasks--;
277
+ onActivity();
278
+ }
279
+ } : this.#worker[method].bind(this.#worker);
280
+ }
281
+ }
282
+ /**
283
+ * Ends the worker process and cleans up any resources associated with it. This method should be called when the worker is no longer needed, to ensure that it is properly terminated and does not continue to consume system resources. If the worker is already terminated or was never initialized, this method will throw an error.
284
+ *
285
+ * @returns A promise that resolves when the worker has been successfully terminated.
286
+ */
287
+ async end() {
288
+ const worker = this.#worker;
289
+ if (!worker) throw new Error("Execution Host Worker is not initialized");
290
+ cleanupWorkers(worker);
291
+ this.#worker = void 0;
292
+ return worker.end();
293
+ }
294
+ /**
295
+ * Quietly end the worker if it exists
296
+ */
297
+ close() {
298
+ if (this.#worker) {
299
+ cleanupWorkers(this.#worker);
300
+ this.#worker.end();
301
+ }
302
+ }
303
+ };
304
+
305
+ //#endregion
306
+ export { ExecutionHostWorker };
307
+ //# sourceMappingURL=execution-host-worker.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-host-worker.mjs","names":["#worker","JestWorker"],"sources":["../../src/helpers/execution-host-worker.ts"],"sourcesContent":["/* -------------------------------------------------------------------\n\n ⚡ Storm Software - Powerlines\n\n This code was released as part of the Powerlines project. Powerlines\n is maintained by Storm Software under the Apache-2.0 license, and is\n free for commercial and private use. For more information, please visit\n our licensing page at https://stormsoftware.com/licenses/projects/powerlines.\n\n Website: https://stormsoftware.com\n Repository: https://github.com/storm-software/powerlines\n Documentation: https://docs.stormsoftware.com/projects/powerlines\n Contact: https://stormsoftware.com/contact\n\n SPDX-License-Identifier: Apache-2.0\n\n ------------------------------------------------------------------- */\n\nimport { Mode } from \"@powerlines/core\";\nimport { getDefaultMode } from \"@powerlines/core/lib/config\";\nimport { resolve } from \"@stryke/fs/resolve\";\nimport { appendPath } from \"@stryke/path/append\";\nimport { isNumber } from \"@stryke/type-checks/is-number\";\nimport { isSet } from \"@stryke/type-checks/is-set\";\nimport { isSetObject } from \"@stryke/type-checks/is-set-object\";\nimport { isString } from \"@stryke/type-checks/is-string\";\nimport { formatDuration } from \"date-fns/formatDuration\";\nimport { Worker as JestWorker } from \"jest-worker\";\nimport type { ChildProcess } from \"node:child_process\";\nimport { Transform } from \"node:stream\";\nimport { parseArgs } from \"node:util\";\nimport { ExecutionHost, ExecutionHostParams } from \"../types/api\";\nimport { EngineContext } from \"../types/context\";\n\nconst RESTARTED = Symbol(\"powerlines-worker:restarted\");\n\n/**\n * The debug address is in the form of `[host:]port`. The host is optional.\n */\ninterface DebugAddress {\n host?: string;\n port: number;\n}\n\n/**\n * Formats the debug address into a string.\n */\nconst formatDebugAddress = ({ host, port }: DebugAddress): string => {\n return host ? `${host}:${port}` : `${port}`;\n};\n\nexport type NodeOptions = Record<string, string | boolean | undefined>;\n\n/**\n * Get's the debug address from the `NODE_OPTIONS` environment variable. If the\n * address is not found, it returns the default host (`undefined`) and port\n * (`9229`).\n *\n * @returns An object with the host and port of the debug address.\n */\nconst getParsedDebugAddress = (\n address: string | boolean | undefined\n): DebugAddress => {\n if (!address || !isString(address)) {\n return { host: undefined, port: 9229 };\n }\n\n // The address is in the form of `[host:]port`. Let's parse the address.\n if (address.includes(\":\")) {\n const [host, port] = address.split(\":\");\n if (!host || !port) {\n throw new Error(`Invalid debug address: ${address}`);\n }\n\n return { host, port: Number.parseInt(port, 10) };\n }\n\n return { host: undefined, port: Number.parseInt(address, 10) };\n};\n\ntype NodeInspectType = \"inspect\" | \"inspect-brk\" | undefined;\n\n/**\n * Get the debug type from the `NODE_OPTIONS` environment variable.\n */\nfunction getNodeDebugType(nodeOptions: NodeOptions): NodeInspectType {\n if (nodeOptions.inspect) {\n return \"inspect\";\n }\n if (nodeOptions[\"inspect-brk\"] || nodeOptions.inspect_brk) {\n return \"inspect-brk\";\n }\n\n return undefined;\n}\n\nconst cleanupWorkers = (worker: JestWorker) => {\n for (const curWorker of ((worker as any)._workerPool?._workers || []) as {\n _child?: ChildProcess;\n }[]) {\n curWorker._child?.kill(\"SIGINT\");\n }\n};\n\nexport interface ExecutionHostWorkerOptions<\n TExecutionAPI extends ReadonlyArray<string>\n> {\n // /**\n // * `-1` if not inspectable\n // */\n // debuggerPortOffset?: number;\n\n // /**\n // * Whether to enable source maps support in the worker, which can improve the quality of stack traces at the cost of increased memory usage and slower performance. Defaults to `false`.\n // */\n // enableSourceMaps?: boolean;\n\n /**\n * The maximum time in milliseconds a worker can run before being terminated.\n *\n * @defaultValue 900000 (15 minutes)\n */\n timeout?: number;\n\n /**\n * True if `--max-old-space-size` should not be forwarded to the worker.\n */\n isolatedMemory?: boolean;\n\n /**\n * The mode to run the worker in, which can affect how the worker is initialized and how it behaves. This is determined based on the resolved configuration of the engine, and can be used to optimize the worker for different environments (e.g., development, production, etc.).\n */\n mode?: Mode;\n\n /**\n * An optional root to resolve the execution host path from, which can be used to specify a custom root directory for the worker to use when resolving paths and loading configuration files. If this option is not provided, the worker will use the current working directory as the root directory by default.\n */\n root?: string;\n\n /**\n * The context of the {@link @powerlines/engine#Engine | Engine instance}, which can be used to access the engine's state and services within the worker.\n */\n context: EngineContext;\n\n /**\n * An array of method names that the worker exposes. These methods will be available on the Worker instance and can be called to execute tasks in the worker process.\n */\n executionMethods: TExecutionAPI;\n}\n\nexport class ExecutionHostWorker<TExecutionAPI extends ReadonlyArray<string>> {\n #worker: JestWorker | undefined;\n\n /**\n * Creates a new instance of the ExecutionHostWorker class, which manages a worker process for executing tasks related to the Powerlines Engine. The worker is initialized with the specified options and can be used to run tasks in an isolated environment, with support for automatic restarts and activity monitoring.\n *\n * @param executionHostPath - The path to the Execution Host file.\n * @param options - The options for configuring the worker, including the execution context, exposed methods, timeout, and mode.\n * @returns A promise that resolves to an instance of the ExecutionHostWorker class.\n */\n public static async from<TExecutionAPI extends ReadonlyArray<string>>(\n executionHostPath: string,\n options: ExecutionHostWorkerOptions<TExecutionAPI>\n ) {\n const mode = await getDefaultMode(options.context.cwd);\n\n const resolvedPath = await resolve(executionHostPath, {\n paths: [\n options.context.cwd,\n options.root ? appendPath(options.root, options.context.cwd) : undefined\n ].filter(Boolean) as string[]\n });\n if (!resolvedPath) {\n throw new Error(\n `Could not resolve the provided Execution Host path: \\`${executionHostPath}\\`.`\n );\n }\n\n return new ExecutionHostWorker<TExecutionAPI>(resolvedPath, {\n mode,\n ...options\n }) as unknown as ExecutionHost<TExecutionAPI>;\n }\n\n /**\n * Create a new worker instance.\n *\n * @param executionHostPath - The path to the worker file.\n * @param options - The options for the worker, including exposed methods, timeout, and hooks for activity and restart.\n */\n public constructor(\n protected executionHostPath: string,\n protected options: ExecutionHostWorkerOptions<TExecutionAPI>\n ) {\n const {\n timeout = 900_000,\n isolatedMemory = false,\n mode = \"production\",\n context,\n executionMethods\n } = this.options;\n\n const logger = context.extendLogger({ category: \"communication\" });\n\n let restartPromise: Promise<typeof RESTARTED>;\n let resolveRestartPromise: (arg: typeof RESTARTED) => void;\n let activeTasks = 0;\n\n this.#worker = undefined;\n\n // ensure we end workers if they weren't before exit\n process.on(\"exit\", () => {\n this.close();\n });\n\n let nodeOptions = {} as {\n [longOption: string]: string | boolean | undefined;\n };\n\n const args: string[] = [...process.execArgv];\n if (process.env.NODE_OPTIONS) {\n let isInString = false;\n let willStartNewArg = true;\n for (let i = 0; i < process.env.NODE_OPTIONS.length; i++) {\n let char = process.env.NODE_OPTIONS[i];\n if (char) {\n // Skip any escaped characters in strings.\n if (char === \"\\\\\" && isInString) {\n // Ensure we don't have an escape character at the end.\n if (process.env.NODE_OPTIONS.length === i + 1) {\n throw new Error(\"Invalid escape character at the end.\");\n }\n\n // Skip the next character.\n char = process.env.NODE_OPTIONS[++i];\n if (!char) {\n continue;\n }\n }\n // If we find a space outside of a string, we should start a new argument.\n else if (char === \" \" && !isInString) {\n willStartNewArg = true;\n continue;\n }\n\n // If we find a quote, we should toggle the string flag.\n else if (char === '\"') {\n isInString = !isInString;\n continue;\n }\n\n // If we're starting a new argument, we should add it to the array.\n if (willStartNewArg) {\n args.push(char);\n willStartNewArg = false;\n }\n // Otherwise, add it to the last argument.\n else {\n args[args.length - 1] += char;\n }\n }\n }\n\n if (isInString) {\n throw new Error(\"Unterminated string\");\n }\n }\n\n if (args.length > 0) {\n const { values, tokens } = parseArgs({\n args,\n strict: false,\n tokens: true\n });\n nodeOptions = values;\n\n // For the `NODE_OPTIONS`, we support arguments with values without the `=`\n // sign. We need to parse them manually.\n let orphan = null;\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (!token) continue;\n\n if (token.kind === \"option-terminator\") {\n break;\n }\n\n // When we encounter an option, if it's value is undefined, we should check\n // to see if the following tokens are positional parameters. If they are,\n // then the option is orphaned, and we can assign it.\n if (token.kind === \"option\") {\n orphan = !isSet(token.value) ? token : null;\n continue;\n }\n\n // If the token isn't a positional one, then we can't assign it to the found\n // orphaned option.\n if (token.kind !== \"positional\") {\n orphan = null;\n continue;\n }\n\n // If we don't have an orphan, then we can skip this token.\n if (!orphan) {\n continue;\n }\n\n // If the token is a positional one, and it has a value, so add it to the\n // values object. If it already exists, append it with a space.\n if (orphan.name in nodeOptions && isString(nodeOptions[orphan.name])) {\n nodeOptions[orphan.name] += ` ${token.value}`;\n } else {\n nodeOptions[orphan.name] = token.value;\n }\n }\n }\n\n const originalOptions = { ...nodeOptions };\n\n delete nodeOptions.inspect;\n delete nodeOptions[\"inspect-brk\"];\n delete nodeOptions.inspect_brk;\n\n if (mode === \"development\") {\n const nodeDebugType = getNodeDebugType(originalOptions);\n if (nodeDebugType) {\n const debuggerAddress = getParsedDebugAddress(\n originalOptions[nodeDebugType]\n );\n const address: DebugAddress = {\n host: debuggerAddress.host,\n // current process runs on `address.port`\n port: debuggerAddress.port === 0 ? 0 : debuggerAddress.port + 1 + 1\n };\n nodeOptions[nodeDebugType] = formatDebugAddress(address);\n }\n\n nodeOptions[\"enable-source-maps\"] = true;\n }\n\n if (isolatedMemory) {\n delete nodeOptions[\"max-old-space-size\"];\n delete nodeOptions.max_old_space_size;\n }\n\n const execArgv: string[] = [];\n const nodeOptionsParts: string[] = [];\n for (const [key, value] of Object.entries(nodeOptions)) {\n let formatted: string | null = null;\n if (value === true) {\n formatted = `--${key}`;\n } else if (value) {\n formatted = `--${key}=${\n // Values with spaces need to be quoted. We use JSON.stringify to\n // also escape any nested quotes.\n value.includes(\" \") && !value.startsWith('\"')\n ? JSON.stringify(value)\n : value\n }`;\n }\n\n if (formatted === null) {\n continue;\n }\n\n if (\n [\n \"experimental-network-inspection\",\n \"experimental-storage-inspection\",\n \"experimental-worker-inspection\",\n \"experimental-inspector-network-resource\"\n ].includes(key)\n ) {\n execArgv.push(formatted);\n } else {\n nodeOptionsParts.push(formatted);\n }\n }\n\n const onHanging = () => {\n const worker = this.#worker;\n if (!worker) {\n return;\n }\n\n const resolve = resolveRestartPromise;\n // eslint-disable-next-line ts/no-use-before-define\n createWorker();\n\n logger.warn(\n `Sending SIGTERM signal to worker due to timeout${\n timeout ? ` of ${formatDuration({ seconds: timeout / 1000 })}` : \"\"\n }. Subsequent errors may be a result of the worker exiting.`\n );\n\n void worker.end().then(() => {\n resolve(RESTARTED);\n });\n };\n\n let hangingTimer: NodeJS.Timeout | false = false;\n\n const onActivity = () => {\n if (hangingTimer) {\n clearTimeout(hangingTimer);\n }\n\n hangingTimer = activeTasks > 0 && setTimeout(onHanging, timeout);\n };\n\n const createWorker = () => {\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n NODE_ENV: mode,\n NODE_OPTIONS: nodeOptionsParts.join(\" \"),\n POWERLINES_EXECUTION_HOST_WORKER: \"true\"\n };\n\n if (env.FORCE_COLOR === undefined) {\n // Mirror the enablement heuristic from picocolors (see https://github.com/vercel/next.js/blob/6a40da0345939fe4f7b1ae519b296a86dd103432/packages/next/src/lib/picocolors.ts#L21-L24).\n // Picocolors snapshots `process.env`/`stdout.isTTY` at module load time, so when the worker\n // process bootstraps with piped stdio its own check would disable colors. Re-evaluating the\n // same conditions here lets us opt the worker into color output only when the parent would\n // have seen colors, while still respecting explicit opt-outs like NO_COLOR.\n const supportsColors =\n !env.NO_COLOR &&\n !env.CI &&\n env.TERM !== \"dumb\" &&\n (process.stdout.isTTY || process.stderr?.isTTY);\n\n if (supportsColors) {\n env.FORCE_COLOR = \"1\";\n }\n }\n\n this.#worker = new JestWorker(executionHostPath, {\n maxRetries: 0,\n computeWorkerKey: (_, ...args: Array<unknown>) => {\n let executionId = \"default\";\n let configIndex = 0;\n if (args.length > 0 && isSetObject(args[0])) {\n const arg = args[0] as ExecutionHostParams;\n if (isSetObject(arg.options)) {\n configIndex = arg.options.configIndex ?? 0;\n executionId = arg.options.executionId || \"default\";\n }\n }\n\n return `${executionId}-${configIndex}`;\n },\n forkOptions: {\n execArgv,\n env\n }\n });\n restartPromise = new Promise(resolve => {\n resolveRestartPromise = resolve;\n });\n\n for (const worker of ((this.#worker as any)._workerPool?._workers ||\n []) as {\n _child?: ChildProcess;\n }[]) {\n worker._child?.on(\"exit\", (code, signal) => {\n logger.debug(\n `Worker process exited with code ${code} and signal ${signal}`\n );\n\n if ((code || (signal && signal !== \"SIGINT\")) && this.#worker) {\n const error = new Error(\n `Execution Host Worker exited unexpectedly with code ${\n code\n } and signal ${signal}`\n );\n logger.error(error);\n\n throw error;\n }\n });\n\n worker._child?.on(\"error\", error => {\n logger.error({\n meta: { category: \"communication\" },\n message: `Worker process emitted an error: ${error.message}`,\n error\n });\n });\n\n // if a child process emits a particular message, we track that as activity\n // so the parent process can keep track of progress\n worker._child?.on(\"message\", data => {\n onActivity();\n\n if (Array.isArray(data) && data.length > 1 && isNumber(data[0])) {\n if (data[0] === 0) {\n logger.trace(\n `Received message from worker: ${JSON.stringify(data.slice(1), null, 2)}`\n );\n } else {\n logger.debug(\n `Received error message from worker: ${JSON.stringify(\n data.slice(1),\n null,\n 2\n )}`\n );\n }\n }\n\n logger.trace(\n `Received message from worker: ${JSON.stringify(data, null, 2)}`\n );\n });\n }\n\n let aborted = false;\n const onActivityAbort = () => {\n if (!aborted) {\n aborted = true;\n }\n };\n\n // Listen to the worker's stdout and stderr, if there's any thing logged, abort the activity first\n const abortActivityStreamOnLog = new Transform({\n transform(_chunk, _encoding, callback) {\n onActivityAbort();\n callback();\n }\n });\n // Stop the activity if there's any output from the worker\n this.#worker.getStdout().pipe(abortActivityStreamOnLog);\n this.#worker.getStderr().pipe(abortActivityStreamOnLog);\n\n // Pipe the worker's stdout and stderr to the parent process\n this.#worker.getStdout().pipe(process.stdout);\n this.#worker.getStderr().pipe(process.stderr);\n };\n createWorker();\n\n for (const method of executionMethods) {\n if (method.startsWith(\"_\")) {\n continue;\n }\n\n (this as any)[method] = timeout\n ? async (...args: any[]) => {\n activeTasks++;\n try {\n let attempts = 0;\n for (;;) {\n onActivity();\n\n const result = await Promise.race([\n // eslint-disable-next-line ts/no-unsafe-call\n (this.#worker as any)[method](\n args.length > 0 && args[0] ? args[0] : {}\n ),\n restartPromise\n ]);\n if (result !== RESTARTED) {\n return result;\n }\n\n logger.warn(\n `Execution Host Worker was restarted while calling method \"${\n method\n }\" (attempt ${attempts++}). Retrying the call...`\n );\n }\n } finally {\n activeTasks--;\n onActivity();\n }\n }\n : // eslint-disable-next-line ts/no-unsafe-call\n (this.#worker as any)[method].bind(this.#worker);\n }\n }\n\n /**\n * Ends the worker process and cleans up any resources associated with it. This method should be called when the worker is no longer needed, to ensure that it is properly terminated and does not continue to consume system resources. If the worker is already terminated or was never initialized, this method will throw an error.\n *\n * @returns A promise that resolves when the worker has been successfully terminated.\n */\n public async end(): ReturnType<JestWorker[\"end\"]> {\n const worker = this.#worker;\n if (!worker) {\n throw new Error(\"Execution Host Worker is not initialized\");\n }\n\n cleanupWorkers(worker);\n this.#worker = undefined;\n return worker.end();\n }\n\n /**\n * Quietly end the worker if it exists\n */\n public close(): void {\n if (this.#worker) {\n cleanupWorkers(this.#worker);\n void this.#worker.end();\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_chunk = require('../chunk-C0xms8kb.cjs');
3
+ let _powerlines_core_lib_environment = require("@powerlines/core/lib/environment");
4
+ let node_fs = require("node:fs");
5
+ let _stryke_fs_helpers = require("@stryke/fs/helpers");
6
+ let _stryke_path_join = require("@stryke/path/join");
7
+ let _stryke_fs_list_files = require("@stryke/fs/list-files");
8
+ let _powerlines_core_lib_hooks = require("@powerlines/core/lib/hooks");
9
+
10
+ //#region src/helpers/finalize.ts
11
+ /**
12
+ * Finalize the execution context by disposing resources and cleaning up.
13
+ *
14
+ * @param context - The execution context to finalize
15
+ */
16
+ async function finalize(context) {
17
+ const timer = context.timer("Finalization");
18
+ await (0, _powerlines_core_lib_environment.executeEnvironments)(context, async (env) => {
19
+ await (0, _powerlines_core_lib_hooks.callHook)(context, "finalize", { environment: env });
20
+ await env.fs.dispose();
21
+ if ((0, node_fs.existsSync)(env.cachePath) && !(await (0, _stryke_fs_list_files.listFiles)((0, _stryke_path_join.joinPaths)(env.cachePath, "**/*")))?.length) await (0, _stryke_fs_helpers.removeDirectory)(env.cachePath);
22
+ });
23
+ timer();
24
+ }
25
+
26
+ //#endregion
27
+ exports.finalize = finalize;
@@ -0,0 +1,12 @@
1
+ import { ExecutionContext, ResolvedConfig } from "@powerlines/core";
2
+
3
+ //#region src/helpers/finalize.d.ts
4
+ /**
5
+ * Finalize the execution context by disposing resources and cleaning up.
6
+ *
7
+ * @param context - The execution context to finalize
8
+ */
9
+ declare function finalize<TResolvedConfig extends ResolvedConfig = ResolvedConfig>(context: ExecutionContext<TResolvedConfig>): Promise<void>;
10
+ //#endregion
11
+ export { finalize };
12
+ //# sourceMappingURL=finalize.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finalize.d.cts","names":[],"sources":["../../src/helpers/finalize.ts"],"mappings":";;;;;AA+BA;;;iBAAsB,QAAA,yBACI,cAAA,GAAiB,cAAA,CAAA,CACzC,OAAA,EAAS,gBAAA,CAAiB,eAAA,IAAgB,OAAA"}
@@ -0,0 +1,12 @@
1
+ import { ExecutionContext, ResolvedConfig } from "@powerlines/core";
2
+
3
+ //#region src/helpers/finalize.d.ts
4
+ /**
5
+ * Finalize the execution context by disposing resources and cleaning up.
6
+ *
7
+ * @param context - The execution context to finalize
8
+ */
9
+ declare function finalize<TResolvedConfig extends ResolvedConfig = ResolvedConfig>(context: ExecutionContext<TResolvedConfig>): Promise<void>;
10
+ //#endregion
11
+ export { finalize };
12
+ //# sourceMappingURL=finalize.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finalize.d.mts","names":[],"sources":["../../src/helpers/finalize.ts"],"mappings":";;;;;AA+BA;;;iBAAsB,QAAA,yBACI,cAAA,GAAiB,cAAA,CAAA,CACzC,OAAA,EAAS,gBAAA,CAAiB,eAAA,IAAgB,OAAA"}
@@ -0,0 +1,26 @@
1
+ import { executeEnvironments } from "@powerlines/core/lib/environment";
2
+ import { existsSync } from "node:fs";
3
+ import { removeDirectory } from "@stryke/fs/helpers";
4
+ import { joinPaths } from "@stryke/path/join";
5
+ import { listFiles } from "@stryke/fs/list-files";
6
+ import { callHook } from "@powerlines/core/lib/hooks";
7
+
8
+ //#region src/helpers/finalize.ts
9
+ /**
10
+ * Finalize the execution context by disposing resources and cleaning up.
11
+ *
12
+ * @param context - The execution context to finalize
13
+ */
14
+ async function finalize(context) {
15
+ const timer = context.timer("Finalization");
16
+ await executeEnvironments(context, async (env) => {
17
+ await callHook(context, "finalize", { environment: env });
18
+ await env.fs.dispose();
19
+ if (existsSync(env.cachePath) && !(await listFiles(joinPaths(env.cachePath, "**/*")))?.length) await removeDirectory(env.cachePath);
20
+ });
21
+ timer();
22
+ }
23
+
24
+ //#endregion
25
+ export { finalize };
26
+ //# sourceMappingURL=finalize.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finalize.mjs","names":[],"sources":["../../src/helpers/finalize.ts"],"sourcesContent":["/* -------------------------------------------------------------------\n\n ⚡ Storm Software - Powerlines\n\n This code was released as part of the Powerlines project. Powerlines\n is maintained by Storm Software under the Apache-2.0 license, and is\n free for commercial and private use. For more information, please visit\n our licensing page at https://stormsoftware.com/licenses/projects/powerlines.\n\n Website: https://stormsoftware.com\n Repository: https://github.com/storm-software/powerlines\n Documentation: https://docs.stormsoftware.com/projects/powerlines\n Contact: https://stormsoftware.com/contact\n\n SPDX-License-Identifier: Apache-2.0\n\n ------------------------------------------------------------------- */\n\nimport type { ExecutionContext, ResolvedConfig } from \"@powerlines/core\";\nimport { executeEnvironments } from \"@powerlines/core/lib/environment\";\nimport { callHook } from \"@powerlines/core/lib/hooks\";\nimport { removeDirectory } from \"@stryke/fs/helpers\";\nimport { listFiles } from \"@stryke/fs/list-files\";\nimport { joinPaths } from \"@stryke/path/join\";\nimport { existsSync } from \"node:fs\";\n\n/**\n * Finalize the execution context by disposing resources and cleaning up.\n *\n * @param context - The execution context to finalize\n */\nexport async function finalize<\n TResolvedConfig extends ResolvedConfig = ResolvedConfig\n>(context: ExecutionContext<TResolvedConfig>) {\n const timer = context.timer(\"Finalization\");\n\n await executeEnvironments(context, async env => {\n await callHook(context, \"finalize\", { environment: env });\n await env.fs.dispose();\n\n if (\n existsSync(env.cachePath) &&\n !(await listFiles(joinPaths(env.cachePath, \"**/*\")))?.length\n ) {\n await removeDirectory(env.cachePath);\n }\n });\n\n timer();\n}\n"],"mappings":";;;;;;;;;;;;;AA+BA,eAAsB,SAEpB,SAA4C;CAC5C,MAAM,QAAQ,QAAQ,MAAM,eAAe;AAE3C,OAAM,oBAAoB,SAAS,OAAM,QAAO;AAC9C,QAAM,SAAS,SAAS,YAAY,EAAE,aAAa,KAAK,CAAC;AACzD,QAAM,IAAI,GAAG,SAAS;AAEtB,MACE,WAAW,IAAI,UAAU,IACzB,EAAE,MAAM,UAAU,UAAU,IAAI,WAAW,OAAO,CAAC,GAAG,OAEtD,OAAM,gBAAgB,IAAI,UAAU;GAEtC;AAEF,QAAO"}
@@ -0,0 +1,140 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_chunk = require('../chunk-C0xms8kb.cjs');
3
+ let node_events = require("node:events");
4
+ let _stryke_type_checks_is_number = require("@stryke/type-checks/is-number");
5
+ let _stryke_type_checks_is_set = require("@stryke/type-checks/is-set");
6
+ let _powerlines_core_lib_events = require("@powerlines/core/lib/events");
7
+ let _stryke_url = require("@stryke/url");
8
+ let devframe_client = require("devframe/client");
9
+ let devframe_rpc = require("devframe/rpc");
10
+ let devframe_rpc_client = require("devframe/rpc/client");
11
+ let devframe_rpc_transports_ws_client = require("devframe/rpc/transports/ws-client");
12
+ let devframe_utils_promise = require("devframe/utils/promise");
13
+ let human_id = require("human-id");
14
+
15
+ //#region src/helpers/rpc.ts
16
+ function createWsRpcClientMode(baseURL, connectionMeta, events, clientRpc, authToken = (0, human_id.humanId)({
17
+ separator: "-",
18
+ capitalize: false
19
+ }), rpcOptions = {}, wsOptions = {}) {
20
+ let isTrusted = false;
21
+ const trustedPromise = (0, devframe_utils_promise.promiseWithResolver)();
22
+ const url = (0, _stryke_type_checks_is_number.isNumber)(connectionMeta.websocket) || (0, _stryke_type_checks_is_set.isSet)(connectionMeta.websocket) && `${+connectionMeta.websocket}` === `${connectionMeta.websocket}` ? `${baseURL.protocol.replace("http", "ws")}//${baseURL.hostname}:${connectionMeta.websocket}` : connectionMeta.websocket;
23
+ const definitions = /* @__PURE__ */ new Map();
24
+ for (const name of connectionMeta.jsonSerializableMethods ?? []) definitions.set(name, { jsonSerializable: true });
25
+ const serverRpc = (0, devframe_rpc_client.createRpcClient)(clientRpc.functions, {
26
+ channel: (0, devframe_rpc_transports_ws_client.createWsRpcChannel)({
27
+ url,
28
+ authToken,
29
+ definitions,
30
+ ...wsOptions
31
+ }),
32
+ rpcOptions
33
+ });
34
+ clientRpc.register({
35
+ name: "devframe:auth:revoked",
36
+ type: "event",
37
+ handler: () => {
38
+ isTrusted = false;
39
+ events.emit("rpc:is-trusted:updated", false);
40
+ }
41
+ });
42
+ let currentAuthToken = authToken;
43
+ async function requestTrustWithToken(token) {
44
+ currentAuthToken = token;
45
+ return true;
46
+ }
47
+ async function requestTrust() {
48
+ if (isTrusted) return true;
49
+ return requestTrustWithToken(currentAuthToken);
50
+ }
51
+ async function ensureTrusted(timeout = 6e4) {
52
+ if (isTrusted) trustedPromise.resolve(true);
53
+ if (timeout <= 0) return trustedPromise.promise;
54
+ let clear = () => {};
55
+ await Promise.race([trustedPromise.promise.then(clear), new Promise((resolve, reject) => {
56
+ const id = setTimeout(() => {
57
+ reject(/* @__PURE__ */ new Error("Timeout waiting for rpc to be trusted"));
58
+ }, timeout);
59
+ clear = () => clearTimeout(id);
60
+ })]);
61
+ return isTrusted;
62
+ }
63
+ return {
64
+ get isTrusted() {
65
+ return isTrusted;
66
+ },
67
+ requestTrust,
68
+ requestTrustWithToken,
69
+ ensureTrusted,
70
+ call: (...args) => {
71
+ return serverRpc.$call(...args);
72
+ },
73
+ callEvent: (...args) => {
74
+ return serverRpc.$callEvent(...args);
75
+ },
76
+ callOptional: (...args) => {
77
+ return serverRpc.$callOptional(...args);
78
+ }
79
+ };
80
+ }
81
+ const CONNECTION_AUTH_TOKEN_KEY = "__DEVTOOLS_CONNECTION_AUTH_TOKEN__";
82
+ function createRpcClient(options) {
83
+ const baseURL = new _stryke_url.StormURL(options.baseURL);
84
+ const cacheManager = new devframe_rpc.RpcCacheManager({
85
+ functions: [],
86
+ ...typeof options.cacheOptions === "object" ? options.cacheOptions : {}
87
+ });
88
+ const context = { rpc: void 0 };
89
+ const clientRpc = new devframe_rpc.RpcFunctionsCollectorBase(context);
90
+ node_events.EventEmitter.setMaxListeners(100);
91
+ const events = (0, _powerlines_core_lib_events.createEventEmitter)();
92
+ const mode = createWsRpcClientMode(baseURL, options.connection, events, clientRpc, void 0, {
93
+ ...options.rpcOptions,
94
+ async onRequest(req, next, resolve) {
95
+ await options.rpcOptions?.onRequest?.call(this, req, next, resolve);
96
+ if (options.cacheOptions && cacheManager?.validate(req.m)) {
97
+ const cached = cacheManager.cached(req.m, req.a);
98
+ if (cached) return resolve(cached);
99
+ else {
100
+ const res = await next(req);
101
+ cacheManager?.apply(req, res);
102
+ }
103
+ } else await next(req);
104
+ }
105
+ }, options.wsOptions);
106
+ const rpc = {
107
+ events,
108
+ get isTrusted() {
109
+ return mode.isTrusted;
110
+ },
111
+ connectionMeta: options.connection,
112
+ ensureTrusted: mode.ensureTrusted,
113
+ requestTrust: mode.requestTrust,
114
+ requestTrustWithToken: async (token) => {
115
+ localStorage.setItem(CONNECTION_AUTH_TOKEN_KEY, token);
116
+ globalThis[CONNECTION_AUTH_TOKEN_KEY] = token;
117
+ return mode.requestTrustWithToken(token);
118
+ },
119
+ call: mode.call,
120
+ callEvent: mode.callEvent,
121
+ callOptional: mode.callOptional,
122
+ client: clientRpc,
123
+ sharedState: void 0,
124
+ streaming: void 0,
125
+ cacheManager
126
+ };
127
+ rpc.streaming = (0, devframe_client.createRpcStreamingClientHost)(rpc);
128
+ context.rpc = rpc;
129
+ mode.requestTrust();
130
+ try {
131
+ const bc = new BroadcastChannel("vite-devtools-auth");
132
+ bc.onmessage = (event) => {
133
+ if (event.data?.type === "auth-update" && event.data.authToken) rpc.requestTrustWithToken(event.data.authToken);
134
+ };
135
+ } catch {}
136
+ return rpc;
137
+ }
138
+
139
+ //#endregion
140
+ exports.createRpcClient = createRpcClient;
@@ -0,0 +1,7 @@
1
+ import { i as RpcClientOptions, u as RpcClient } from "../config-D6xUniHh.cjs";
2
+
3
+ //#region src/helpers/rpc.d.ts
4
+ declare function createRpcClient(options: RpcClientOptions): RpcClient;
5
+ //#endregion
6
+ export { createRpcClient };
7
+ //# sourceMappingURL=rpc.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.d.cts","names":[],"sources":["../../src/helpers/rpc.ts"],"mappings":";;;iBA0LgB,eAAA,CAAgB,OAAA,EAAS,gBAAA,GAAgB,SAAA"}
@@ -0,0 +1,7 @@
1
+ import { i as RpcClientOptions, u as RpcClient } from "../config-BNe23XHx.mjs";
2
+
3
+ //#region src/helpers/rpc.d.ts
4
+ declare function createRpcClient(options: RpcClientOptions): RpcClient;
5
+ //#endregion
6
+ export { createRpcClient };
7
+ //# sourceMappingURL=rpc.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.d.mts","names":[],"sources":["../../src/helpers/rpc.ts"],"mappings":";;;iBA0LgB,eAAA,CAAgB,OAAA,EAAS,gBAAA,GAAgB,SAAA"}