poe-code 14.0.0 → 14.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-code",
3
- "version": "14.0.0",
3
+ "version": "14.0.1",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,8 +16,11 @@ const TEMP_WRITE_MAX_ATTEMPTS = 3;
16
16
  export async function startManagedProcess(options) {
17
17
  assertOptionalFiniteDuration(options.startupTimeoutMs, "startup timeout");
18
18
  assertOptionalFiniteDuration(options.pollIntervalMs, "poll interval");
19
- const fs = options.fs ?? defaultFs();
20
19
  const spec = normalizeSpec(options.spec);
20
+ return await withManagedProcessOperation({ ...options, id: spec.id }, async (fs) => await startReservedProcess({ ...options, fs, spec }));
21
+ }
22
+ async function startReservedProcess(options) {
23
+ const { fs, spec } = options;
21
24
  const existing = await readManagedProcess({
22
25
  baseDir: options.baseDir,
23
26
  fs,
@@ -60,7 +63,10 @@ export async function startManagedProcess(options) {
60
63
  export async function stopManagedProcess(options) {
61
64
  assertOptionalFiniteDuration(options.stopTimeoutMs, "stop timeout");
62
65
  assertOptionalFiniteDuration(options.pollIntervalMs, "poll interval");
63
- const fs = options.fs ?? defaultFs();
66
+ return await withManagedProcessOperation(options, async (fs) => await stopReservedProcess({ ...options, fs }));
67
+ }
68
+ async function stopReservedProcess(options) {
69
+ const { fs } = options;
64
70
  const record = await readManagedProcess({
65
71
  baseDir: options.baseDir,
66
72
  fs,
@@ -106,37 +112,75 @@ export async function stopManagedProcess(options) {
106
112
  return stopped;
107
113
  }
108
114
  export async function restartManagedProcess(options) {
109
- const fs = options.fs ?? defaultFs();
110
- const record = await readManagedProcess({
111
- baseDir: options.baseDir,
112
- fs,
113
- id: options.id,
114
- isPidRunning: options.isPidRunning
115
+ assertOptionalFiniteDuration(options.startupTimeoutMs, "startup timeout");
116
+ assertOptionalFiniteDuration(options.stopTimeoutMs, "stop timeout");
117
+ assertOptionalFiniteDuration(options.pollIntervalMs, "poll interval");
118
+ return await withManagedProcessOperation(options, async (fs) => {
119
+ const record = await readManagedProcess({
120
+ baseDir: options.baseDir,
121
+ fs,
122
+ id: options.id,
123
+ isPidRunning: options.isPidRunning
124
+ });
125
+ if (record.spec === null) {
126
+ throw await managedProcessNotFound(fs, options.baseDir, options.id);
127
+ }
128
+ await stopReservedProcess({
129
+ ...options,
130
+ force: false,
131
+ fs
132
+ });
133
+ return await startReservedProcess({
134
+ ...options,
135
+ fs,
136
+ spec: normalizeSpec(record.spec)
137
+ });
115
138
  });
116
- if (record.spec === null) {
117
- throw await managedProcessNotFound(fs, options.baseDir, options.id);
139
+ }
140
+ async function withManagedProcessOperation(options, operation) {
141
+ const fs = options.fs ?? defaultFs();
142
+ await assertProcessDirectorySafe(fs, options.baseDir, options.id);
143
+ await fs.mkdir(options.baseDir, { recursive: true });
144
+ const baseDir = path.resolve(options.baseDir);
145
+ const operationsDir = path.join(path.dirname(baseDir), ".process-operations", path.basename(baseDir));
146
+ await assertPathHasNoSymbolicLinks(fs, operationsDir);
147
+ await fs.mkdir(operationsDir, { recursive: true });
148
+ const lockPath = path.join(operationsDir, options.id);
149
+ const owner = `${JSON.stringify({ token: randomUUID(), pid: process.pid })}\n`;
150
+ try {
151
+ await fs.writeFile(lockPath, owner, { encoding: "utf8", flag: "wx" });
118
152
  }
119
- await stopManagedProcess({
120
- baseDir: options.baseDir,
121
- force: false,
122
- fs,
123
- id: options.id,
124
- isPidRunning: options.isPidRunning,
125
- pollIntervalMs: options.pollIntervalMs,
126
- signalProcess: options.signalProcess,
127
- stopRuntimeArtifacts: options.stopRuntimeArtifacts,
128
- stopTimeoutMs: options.stopTimeoutMs
129
- });
130
- return await startManagedProcess({
131
- baseDir: options.baseDir,
132
- fs,
133
- isPidRunning: options.isPidRunning,
134
- pollIntervalMs: options.pollIntervalMs,
135
- signalProcess: options.signalProcess,
136
- spawnDaemon: options.spawnDaemon,
137
- spec: record.spec,
138
- startupTimeoutMs: options.startupTimeoutMs
139
- });
153
+ catch (error) {
154
+ if (hasOwnErrorCode(error, "EEXIST")) {
155
+ throw new UserError(`Managed process "${options.id}" has another operation in progress.\n` +
156
+ "Retry after it finishes.\n" +
157
+ "If the caller exited unexpectedly, confirm that all operations on this name have stopped,\n" +
158
+ `then remove this reservation file:\n${lockPath}`);
159
+ }
160
+ throw error;
161
+ }
162
+ let outcome;
163
+ try {
164
+ outcome = { result: await operation(fs) };
165
+ }
166
+ catch (error) {
167
+ outcome = { error };
168
+ }
169
+ try {
170
+ if (await fs.readFile(lockPath, "utf8") !== owner) {
171
+ throw new UserError(`Managed process operation ownership changed before release: ${lockPath}`);
172
+ }
173
+ await fs.rm(lockPath);
174
+ }
175
+ catch (releaseError) {
176
+ if ("error" in outcome) {
177
+ throw new AggregateError([outcome.error, releaseError], "Managed process operation and ownership release failed");
178
+ }
179
+ throw releaseError;
180
+ }
181
+ if ("error" in outcome)
182
+ throw outcome.error;
183
+ return outcome.result;
140
184
  }
141
185
  async function describeStartFailure(fs, baseDir, id, record) {
142
186
  const state = record.state;
@@ -339,21 +383,22 @@ function getFileId(stat) {
339
383
  : null;
340
384
  }
341
385
  export async function removeManagedProcess(options) {
342
- const fs = options.fs ?? defaultFs();
343
- const record = await readManagedProcess({
344
- baseDir: options.baseDir,
345
- fs,
346
- id: options.id,
347
- isPidRunning: options.isPidRunning
386
+ await withManagedProcessOperation(options, async (fs) => {
387
+ const record = await readManagedProcess({
388
+ baseDir: options.baseDir,
389
+ fs,
390
+ id: options.id,
391
+ isPidRunning: options.isPidRunning
392
+ });
393
+ if (isActiveRecord(record)) {
394
+ throw new Error(`Managed process "${options.id}" must be stopped before removal.`);
395
+ }
396
+ const stateStore = createStateStore(options.baseDir, fs);
397
+ await stateStore.remove(options.id);
398
+ if (record.spec !== null && options.removeRuntimeArtifacts) {
399
+ await options.removeRuntimeArtifacts({ record });
400
+ }
348
401
  });
349
- if (isActiveRecord(record)) {
350
- throw new Error(`Managed process "${options.id}" must be stopped before removal.`);
351
- }
352
- const stateStore = createStateStore(options.baseDir, fs);
353
- await stateStore.remove(options.id);
354
- if (record.spec !== null && options.removeRuntimeArtifacts) {
355
- await options.removeRuntimeArtifacts({ record });
356
- }
357
402
  }
358
403
  export async function runManagedProcess(options) {
359
404
  if (options.signal?.aborted) {