@paleo/workspace 0.13.0 → 0.14.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/README.md CHANGED
@@ -31,6 +31,8 @@ npm run dev # start in the foreground; holds the ter
31
31
  npm run dev -- up # start in the background (no-op if already running here)
32
32
  npm run dev -- up --restart # stop the dev-server in this worktree if running, then start fresh
33
33
  npm run dev -- up --evict # if devLimit is reached, evict the oldest dev-server and start
34
+ npm run dev -- restart # stop the dev-server in this worktree if running, then start in the background
35
+ npm run dev -- status # report whether this worktree's dev-server is UP or DOWN
34
36
  npm run dev -- list # active dev-servers across all worktrees
35
37
  npm run dev -- down # stop dev server (infrastructure stays up)
36
38
  npm run workspace -- remove feat/42 # full teardown
package/dist/cli.d.ts CHANGED
@@ -13,7 +13,7 @@ export type WorkspaceCommand = {
13
13
  } | {
14
14
  kind: "list";
15
15
  } | {
16
- kind: "info";
16
+ kind: "status";
17
17
  slot?: string;
18
18
  } | {
19
19
  kind: "wait";
@@ -42,11 +42,16 @@ export type DevCommand = {
42
42
  kind: "up";
43
43
  evict: boolean;
44
44
  restart: boolean;
45
+ } | {
46
+ kind: "restart";
47
+ evict: boolean;
45
48
  } | {
46
49
  kind: "down";
47
50
  all: boolean;
48
51
  } | {
49
52
  kind: "list";
53
+ } | {
54
+ kind: "status";
50
55
  } | {
51
56
  kind: "help";
52
57
  };
package/dist/cli.js CHANGED
@@ -24,8 +24,8 @@ function parseSubcommand(subcommand, tokens) {
24
24
  return parseRemove(tokens);
25
25
  case "list":
26
26
  return parseList(tokens);
27
- case "info":
28
- return parseInfo(tokens);
27
+ case "status":
28
+ return parseStatus(tokens);
29
29
  case "wait":
30
30
  return parseWait(tokens);
31
31
  case "set-owner":
@@ -94,7 +94,7 @@ function parseList(tokens) {
94
94
  rejectPositionals(positionals, "list");
95
95
  return { command: { kind: "list" }, verbose: values.verbose ?? false };
96
96
  }
97
- function parseInfo(tokens) {
97
+ function parseStatus(tokens) {
98
98
  const { values, positionals } = parseArgs({
99
99
  args: tokens,
100
100
  options: {
@@ -104,8 +104,8 @@ function parseInfo(tokens) {
104
104
  allowPositionals: true,
105
105
  strict: true,
106
106
  });
107
- rejectPositionals(positionals, "info");
108
- return { command: { kind: "info", slot: values.slot }, verbose: values.verbose ?? false };
107
+ rejectPositionals(positionals, "status");
108
+ return { command: { kind: "status", slot: values.slot }, verbose: values.verbose ?? false };
109
109
  }
110
110
  function parseWait(tokens) {
111
111
  const { values, positionals } = parseArgs({
@@ -173,8 +173,8 @@ export function printWorkspaceHelp() {
173
173
  " Remove a workspace by branch, or the current one when omitted.",
174
174
  " list",
175
175
  " List all registered workspaces (slot, status, branch, path, owner, created).",
176
- " info [-s|--slot <port>]",
177
- " Print a workspace summary (ports, branch, readiness).",
176
+ " status [-s|--slot <port>]",
177
+ " Print a workspace summary (ports, branch, readiness, dev-server).",
178
178
  " wait [-s|--slot <port>]",
179
179
  " Block until the background finalize reaches READY (exit 0) or FAILED (exit 1).",
180
180
  " set-owner <name>",
@@ -203,10 +203,14 @@ function parseDevSubcommand(subcommand, tokens) {
203
203
  switch (subcommand) {
204
204
  case "up":
205
205
  return parseUp(tokens);
206
+ case "restart":
207
+ return parseRestart(tokens);
206
208
  case "down":
207
209
  return parseDown(tokens);
208
210
  case "list":
209
211
  return parseDevList(tokens);
212
+ case "status":
213
+ return parseDevStatus(tokens);
210
214
  default:
211
215
  throw new ConfigError(`Unknown command "${subcommand}". Run \`dev --help\`.`);
212
216
  }
@@ -229,6 +233,16 @@ function parseEvictRestart(tokens, command) {
229
233
  rejectDevPositionals(positionals, command);
230
234
  return { evict: values.evict ?? false, restart: values.restart ?? false };
231
235
  }
236
+ function parseRestart(tokens) {
237
+ const { values, positionals } = parseArgs({
238
+ args: tokens,
239
+ options: { evict: { type: "boolean" } },
240
+ allowPositionals: true,
241
+ strict: true,
242
+ });
243
+ rejectDevPositionals(positionals, "dev restart");
244
+ return { kind: "restart", evict: values.evict ?? false };
245
+ }
232
246
  function parseDown(tokens) {
233
247
  const { values, positionals } = parseArgs({
234
248
  args: tokens,
@@ -249,6 +263,16 @@ function parseDevList(tokens) {
249
263
  rejectDevPositionals(positionals, "dev list");
250
264
  return { kind: "list" };
251
265
  }
266
+ function parseDevStatus(tokens) {
267
+ const { positionals } = parseArgs({
268
+ args: tokens,
269
+ options: {},
270
+ allowPositionals: true,
271
+ strict: true,
272
+ });
273
+ rejectDevPositionals(positionals, "dev status");
274
+ return { kind: "status" };
275
+ }
252
276
  function rejectDevPositionals(positionals, command) {
253
277
  if (positionals.length > 0) {
254
278
  throw new ConfigError(`\`${command}\` takes no positional arguments.`);
@@ -263,12 +287,15 @@ export function printDevHelp() {
263
287
  "Commands:",
264
288
  " dev Start in the foreground; holds the terminal, stops on CTRL+C.",
265
289
  " dev up Start in the background and return once ready.",
290
+ " dev restart Stop this worktree's dev-server if running, then start in the background.",
266
291
  " dev down [--all] Stop this worktree's dev-server, or every dev-server with --all.",
267
292
  " dev list List active dev-servers across all worktrees.",
293
+ " dev status Report whether this worktree's dev-server is UP or DOWN.",
268
294
  " dev --help Show this help message.",
269
295
  "",
270
- "Options (dev, dev up):",
296
+ "Options (dev, dev up, dev restart):",
271
297
  " --evict Evict the oldest dev-server when the cap is reached.",
298
+ "Options (dev, dev up):",
272
299
  " --restart If a dev-server is already running here, stop it first, then start.",
273
300
  ].join("\n"));
274
301
  }
@@ -33,6 +33,12 @@ export interface DevServerSummaryContext {
33
33
  }[];
34
34
  }
35
35
  export declare function runDevServer(config: DevServerConfig): Promise<void>;
36
+ /**
37
+ * Poll the foreground's own spawn PIDs; when none remain alive, the servers were stopped from
38
+ * outside this process (`dev down`, `down --all`, eviction, or a manual kill). Fires `onStopped`
39
+ * once so the foreground can exit instead of hanging on dead servers.
40
+ */
41
+ export declare function watchForExternalStop(pids: number[], onStopped: () => void, isAlive?: (pid: number) => boolean, intervalMs?: number): NodeJS.Timeout | undefined;
36
42
  export type WorktreeReadyCheck = {
37
43
  ok: true;
38
44
  } | {
@@ -50,6 +50,12 @@ export async function runDevServer(config) {
50
50
  case "up":
51
51
  await start(config, mainWorktree, { evict: command.evict, restart: command.restart });
52
52
  return;
53
+ case "restart":
54
+ await start(config, mainWorktree, { evict: command.evict, restart: true });
55
+ return;
56
+ case "status":
57
+ printStatus(config, mainWorktree);
58
+ return;
53
59
  case "foreground":
54
60
  await runForeground(config, mainWorktree, {
55
61
  evict: command.evict,
@@ -58,6 +64,16 @@ export async function runDevServer(config) {
58
64
  return;
59
65
  }
60
66
  }
67
+ function printStatus(config, mainWorktree) {
68
+ const entry = findOwnEntry(mainWorktree, config.registryDir, process.cwd());
69
+ if (!entry || !Object.values(entry.pids).some(isProcessAlive)) {
70
+ console.log("Dev-server status: DOWN.");
71
+ return;
72
+ }
73
+ console.log("Dev-server status: UP.");
74
+ const slot = resolveCurrentSlot(config.basePort, config.registryDir);
75
+ printStartSummary(config, slot, entry.pids);
76
+ }
61
77
  function callbackServersOf(config) {
62
78
  return config.servers.filter((s) => s.kind === "callback");
63
79
  }
@@ -120,6 +136,13 @@ async function runForeground(config, mainWorktree, options) {
120
136
  started = true;
121
137
  printStartSummary(config, slot, state.spawnPids);
122
138
  tailLogs(config, state.spawnPids);
139
+ watchForExternalStop(Object.values(state.spawnPids), () => {
140
+ if (shuttingDown)
141
+ return;
142
+ shuttingDown = true;
143
+ console.log("\nDev-server stopped externally (e.g. `dev down`). Exiting.");
144
+ process.exit(0);
145
+ });
123
146
  await new Promise(() => { });
124
147
  }
125
148
  async function shutdownForeground(config, mainWorktree) {
@@ -187,6 +210,7 @@ function printStartSummary(config, slot, spawnPids) {
187
210
  }
188
211
  }
189
212
  const TAIL_INTERVAL_MS = 300;
213
+ const LIVENESS_POLL_MS = 1000;
190
214
  function tailLogs(config, spawnPids) {
191
215
  const names = Object.keys(spawnPids);
192
216
  const prefixed = names.length > 1;
@@ -215,6 +239,22 @@ function followLogFile(path, prefix) {
215
239
  process.stdout.write(prefix === "" ? text : text.replace(/^(?=.)/gm, prefix));
216
240
  }, TAIL_INTERVAL_MS);
217
241
  }
242
+ /**
243
+ * Poll the foreground's own spawn PIDs; when none remain alive, the servers were stopped from
244
+ * outside this process (`dev down`, `down --all`, eviction, or a manual kill). Fires `onStopped`
245
+ * once so the foreground can exit instead of hanging on dead servers.
246
+ */
247
+ export function watchForExternalStop(pids, onStopped, isAlive = isProcessAlive, intervalMs = LIVENESS_POLL_MS) {
248
+ if (pids.length === 0)
249
+ return;
250
+ const timer = setInterval(() => {
251
+ if (pids.some(isAlive))
252
+ return;
253
+ clearInterval(timer);
254
+ onStopped();
255
+ }, intervalMs);
256
+ return timer;
257
+ }
218
258
  async function rollbackStart(spawnPids, startedCallbacks, ctx) {
219
259
  console.error("\nStopping dev servers...");
220
260
  for (const pid of Object.values(spawnPids)) {
package/dist/workspace.js CHANGED
@@ -39,8 +39,8 @@ export async function runWorkspace(config) {
39
39
  case "wait":
40
40
  await runWait(command, config);
41
41
  return;
42
- case "info":
43
- runInfo(command, config);
42
+ case "status":
43
+ runStatus(command, config);
44
44
  return;
45
45
  case "list":
46
46
  runList(config);
@@ -272,7 +272,7 @@ function printDevServerBlock(config, mainWorktree, targetWorktree, now) {
272
272
  console.log(` log: ${join(targetWorktree, config.runtimeDir, "logs", `${name}.log`)}`);
273
273
  }
274
274
  }
275
- function runInfo(command, config) {
275
+ function runStatus(command, config) {
276
276
  if (command.slot !== undefined) {
277
277
  const slot = resolveTargetSlot(command.slot, config);
278
278
  const ctx = detectWorktree();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paleo/workspace",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "Run multiple git-worktree dev environments side by side.",
5
5
  "keywords": [
6
6
  "workspace",