@paleo/workspace 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,7 +48,7 @@ await runWorkspace({
48
48
  portNames: ["server", "frontend", "db"],
49
49
  sharedDirs: [".local", ".plans"],
50
50
  runtimeDir: ".local-wt",
51
- registryDir: ".local/wt-registry",
51
+ registryDir: ".local/_workspace-registry",
52
52
  configFiles: [
53
53
  {
54
54
  path: ".env",
@@ -74,7 +74,7 @@ await runWorkspace({
74
74
 
75
75
  `branch` is resolved live from the worktree on each call (not persisted in the registry — `git checkout` makes any stored value stale). For detached HEAD or missing directory, it falls back to `"(detached)"`. `status` is the slot's finalize status: `"pending"` until `finalizeWorktree` succeeds, then `"ready"` (or `"failed"`).
76
76
 
77
- Setup runs in two phases: a fast foreground Part 1 creates the worktree and config, then a detached Part 2 runs `finalizeWorktree` and writes progress to `<runtimeDir>/wt-setup.log`. If Part 2 fails, `cd` into the worktree and run `workspace setup` — it is idempotent and retries the finalize step. To block until Part 2 finishes (CI, agent orchestration), run `workspace wait` from inside the worktree (or `workspace wait --slot 8110` from anywhere) — exits 0 on `READY`, 1 on `FAILED`.
77
+ Setup runs in two phases: a fast foreground Part 1 creates the worktree and config, then a detached Part 2 runs `finalizeWorktree` and writes progress to `<runtimeDir>/logs/workspace-setup.log`. If Part 2 fails, `cd` into the worktree and run `workspace setup` — it is idempotent and retries the finalize step. To block until Part 2 finishes (CI, agent orchestration), run `workspace wait` from inside the worktree (or `workspace wait --slot 8110` from anywhere) — exits 0 on `READY`, 1 on `FAILED`.
78
78
 
79
79
  **Bootstrap the main worktree first.** Linked-worktree setup copies config sources from the main worktree, so the main must already have those files. Run `workspace setup` once on the main checkout. Use `preSetup` (with `isMainWorktree === true`) to seed sources from examples or templates. `configFiles` entries are required by default; mark `optional: true` for sources that may legitimately be missing.
80
80
 
@@ -86,7 +86,7 @@ import { runDevServer, helpers } from "@paleo/workspace";
86
86
  await runDevServer({
87
87
  basePort: 8100,
88
88
  runtimeDir: ".local-wt",
89
- registryDir: ".local/wt-registry",
89
+ registryDir: ".local/_workspace-registry",
90
90
  devLimit: 5,
91
91
  servers: [
92
92
  {
package/dist/cli.js CHANGED
@@ -170,24 +170,24 @@ export function printWorkspaceHelp() {
170
170
  console.log([
171
171
  "Usage: workspace <command> [options]",
172
172
  "",
173
- "Manage worktree lifecycle: creation, local environment setup, and removal.",
173
+ "Manage workspaces: a git worktree plus its own dev setup (ports, config, database, dev server).",
174
174
  "",
175
175
  "Commands:",
176
176
  " setup [<branch>] [-c|--new-branch] [--owner <name>] [-s|--slot <port>] [--force] [--wait]",
177
- " Set up the local environment. With <branch>, create a sibling worktree for it",
177
+ " Set up the workspace. With <branch>, create a sibling worktree for it",
178
178
  " (add -c to create the branch first). Without, set up the current worktree",
179
179
  " (idempotent; bootstrap and retry path).",
180
180
  " Finalize runs in the background; add --wait to block until it reaches READY.",
181
181
  " remove [<branch>] [--no-remote-check]",
182
- " Remove a worktree by branch, or the current one when omitted.",
182
+ " Remove a workspace by branch, or the current one when omitted.",
183
183
  " list",
184
- " List all registered worktrees (slot, status, branch, path, owner, created).",
184
+ " List all registered workspaces (slot, status, branch, path, owner, created).",
185
185
  " info [-s|--slot <port>]",
186
- " Print a worktree summary (ports, branch, readiness).",
186
+ " Print a workspace summary (ports, branch, readiness).",
187
187
  " wait [-s|--slot <port>]",
188
188
  " Block until the background finalize reaches READY (exit 0) or FAILED (exit 1).",
189
189
  " set-owner <name>",
190
- " Update the current linked worktree's slot owner (no rebuild).",
190
+ " Update the current workspace's owner (no rebuild).",
191
191
  "",
192
192
  "Global options:",
193
193
  " -v, --verbose Show intermediate output.",
@@ -8,7 +8,7 @@ export interface DevServerConfig {
8
8
  /** Per-worktree runtime directory, relative to the worktree root (e.g. `.local-wt`). */
9
9
  runtimeDir: string;
10
10
  /**
11
- * Shared registry directory, relative to a worktree root (e.g. `.local/wt-registry`).
11
+ * Shared registry directory, relative to a worktree root (e.g. `.local/_workspace-registry`).
12
12
  * Holds `slots.json` and `dev-servers.json`. Must resolve to the same physical directory
13
13
  * across linked worktrees — typically via a symlink (e.g. `.local`).
14
14
  */
@@ -4,7 +4,7 @@ import { dirname, join } from "node:path";
4
4
  import { parseDevServerArgs, printDevServerHelp, validateDevServerFlags, } from "./cli.js";
5
5
  import { evictOldest, findOwnEntry, listDevServers, printActiveServers, pruneAndPersist, registerDevServer, removeDevServerEntryByWorktree, stopAllRegistered, unregisterDevServer, } from "./dev-servers-registry.js";
6
6
  import { ConfigError, StartupError } from "./errors.js";
7
- import { detectCommonJsError, formatDuration } from "./helpers.js";
7
+ import { detectCommonJsError, formatDuration, setupLogPath } from "./helpers.js";
8
8
  import { awaitAllReady, handleStartupFailure } from "./log-polling.js";
9
9
  import { canonicalCwd, detectPortConflicts, sweepStalePorts, waitForPortsFree, } from "./port-holder.js";
10
10
  import { isProcessAlive, stopProcessGroup } from "./process-control.js";
@@ -152,7 +152,7 @@ export function buildWorktreeReadyMessage(input) {
152
152
  const { slotPort, worktreePath, runtimeDir, entry, now } = input;
153
153
  if (!entry || entry.status === "ready")
154
154
  return { ok: true };
155
- const logPath = join(worktreePath, runtimeDir, "wt-setup.log");
155
+ const logPath = setupLogPath(worktreePath, runtimeDir);
156
156
  if (entry.status === "pending") {
157
157
  const elapsed = formatDuration(now - Date.parse(entry.createdAt));
158
158
  return {
@@ -315,7 +315,7 @@ async function stopLocal(config, mainWorktree) {
315
315
  function defaultPrintSummary(slot, servers, runtimeDir) {
316
316
  console.log("\nDev servers started!");
317
317
  const ownerSuffix = slot.owner ? `, owner ${slot.owner}` : "";
318
- console.log(` Worktree: slot ${slot.slot}${ownerSuffix}`);
318
+ console.log(` Workspace: slot ${slot.slot}${ownerSuffix}`);
319
319
  for (const { server, port, pid } of servers) {
320
320
  if (server.kind === "spawn") {
321
321
  const url = `http://localhost:${port}/`;
package/dist/helpers.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export declare function setupLogPath(worktreeRoot: string, runtimeDir: string): string;
1
2
  export declare function patchEnvFile(content: string, patches: Record<string, string>): string;
2
3
  export declare function extractHost(content: string, key: string, fallback?: string): string;
3
4
  /**
package/dist/helpers.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
+ export function setupLogPath(worktreeRoot, runtimeDir) {
4
+ return join(worktreeRoot, runtimeDir, "logs", "workspace-setup.log");
5
+ }
3
6
  export function patchEnvFile(content, patches) {
4
7
  const lines = content.trimEnd().split("\n");
5
8
  for (const [key, value] of Object.entries(patches)) {
package/dist/slots.js CHANGED
@@ -74,7 +74,7 @@ export function validateSlotAvailability(slotArg, ctx) {
74
74
  export function resolveCurrentSlot(basePort, registryDir) {
75
75
  const slot = lookupSlotForCwd(registryDir) ?? synthesizeMainSlot(basePort);
76
76
  if (!slot) {
77
- console.error("Error: No slot found for this worktree. Run `workspace setup` first.");
77
+ console.error("Error: No workspace here. Run `workspace setup` first.");
78
78
  process.exit(1);
79
79
  }
80
80
  return slot;
@@ -33,7 +33,7 @@ export interface WorkspaceConfig {
33
33
  */
34
34
  runtimeDir: string;
35
35
  /**
36
- * Shared registry directory, relative to a worktree root (e.g. `.local/wt-registry`).
36
+ * Shared registry directory, relative to a worktree root (e.g. `.local/_workspace-registry`).
37
37
  * Holds `slots.json` and `dev-servers.json`. Must resolve to the same physical directory
38
38
  * across linked worktrees — typically via a symlink listed in `sharedDirs` (e.g. `.local`).
39
39
  */
@@ -54,7 +54,7 @@ export interface WorkspaceConfig {
54
54
  * installed deps, etc.).
55
55
  *
56
56
  * Runs in a detached child whose stdout/stderr are already redirected to
57
- * `<runtimeDir>/wt-setup.log`. `console.log` and child-process `stdio: "inherit"` land there.
57
+ * `<runtimeDir>/logs/workspace-setup.log`. `console.log` and child-process `stdio: "inherit"` land there.
58
58
  */
59
59
  finalizeWorktree: (ctx: SetupContext) => Promise<void> | void;
60
60
  /**
package/dist/workspace.js CHANGED
@@ -4,7 +4,7 @@ import { dirname, join, relative, resolve } from "node:path";
4
4
  import { parseWorkspaceArgs, printWorkspaceHelp } from "./cli.js";
5
5
  import { findOwnEntry, removeDevServerEntryByWorktree } from "./dev-servers-registry.js";
6
6
  import { ConfigError } from "./errors.js";
7
- import { copyAndPatchFile, formatDuration } from "./helpers.js";
7
+ import { copyAndPatchFile, formatDuration, setupLogPath } from "./helpers.js";
8
8
  import { isProcessAlive } from "./process-control.js";
9
9
  import { defaultComputePorts, isValidPort, resolvePortScheme } from "./ports.js";
10
10
  import { handleSetOwner, markSlotFailed, markSlotReady, readSlots, resolveAndRegisterSlot, resolveCurrentSlot, validateSlotAvailability, writeSlots, } from "./slots.js";
@@ -86,9 +86,8 @@ async function runSetup(command, ctx, run, config) {
86
86
  force: command.force,
87
87
  });
88
88
  const ports = portsFn(slot);
89
- const runtimeDir = join(setupCtx.currentWorktree, config.runtimeDir);
90
- mkdirSync(runtimeDir, { recursive: true });
91
- const logPath = join(runtimeDir, "wt-setup.log");
89
+ const logPath = setupLogPath(setupCtx.currentWorktree, config.runtimeDir);
90
+ mkdirSync(dirname(logPath), { recursive: true });
92
91
  // Truncate any prior log so `workspace setup` retries start with a clean record (the previous run's
93
92
  // FAILED: banner would otherwise linger and produce false positives for grep-based tooling).
94
93
  writeFileSync(logPath, "");
@@ -128,7 +127,7 @@ async function runSetup(command, ctx, run, config) {
128
127
  isMainWorktree: setupCtx.isMainWorktree,
129
128
  status,
130
129
  }));
131
- teeLog(`WORKTREE_CREATED path=${setupCtx.currentWorktree} branch=${branch} slot=${slot}`);
130
+ teeLog(`WORKSPACE_CREATED path=${setupCtx.currentWorktree} branch=${branch} slot=${slot}`);
132
131
  if (status !== "ready") {
133
132
  teeLog(`Setup continuing in background. Tail: ${logPath}`);
134
133
  teeLog(`Block until ready: workspace wait --slot ${slot}`);
@@ -162,7 +161,7 @@ function refuseIfFinalizePending(ctx, registryDir, force) {
162
161
  async function runFinalize(command, config) {
163
162
  const slot = Number(command.slot);
164
163
  const ctx = detectWorktree();
165
- const logPath = join(ctx.currentWorktree, config.runtimeDir, "wt-setup.log");
164
+ const logPath = setupLogPath(ctx.currentWorktree, config.runtimeDir);
166
165
  const appendLog = (message) => {
167
166
  appendFileSync(logPath, `${message}\n`);
168
167
  };
@@ -227,7 +226,7 @@ function printWorktreeInfo(config, slot, worktreeForLog, fallback) {
227
226
  const ports = resolvePortsFn(config)(slot);
228
227
  const owner = entry?.owner ?? fallback.owner;
229
228
  const status = entry?.status ?? "pending";
230
- const setupLogPath = join(worktreeForLog, config.runtimeDir, "wt-setup.log");
229
+ const setupLog = setupLogPath(worktreeForLog, config.runtimeDir);
231
230
  const now = Date.now();
232
231
  const isMainWorktree = entry?.main ?? false;
233
232
  const targetWorktree = entry?.worktree ?? ctx.currentWorktree;
@@ -246,7 +245,7 @@ function printWorktreeInfo(config, slot, worktreeForLog, fallback) {
246
245
  const at = entry?.failure?.at ?? entry?.createdAt;
247
246
  const elapsed = at ? formatDuration(now - Date.parse(at)) : "?";
248
247
  const reason = entry?.failure?.message ?? "(no message)";
249
- console.log(`Failure: ${reason} (${elapsed} ago, tail ${setupLogPath})`);
248
+ console.log(`Failure: ${reason} (${elapsed} ago, tail ${setupLog})`);
250
249
  }
251
250
  else if (status === "pending" && entry) {
252
251
  const elapsed = formatDuration(now - Date.parse(entry.createdAt));
@@ -293,7 +292,7 @@ function runList(config) {
293
292
  const ctx = detectWorktree();
294
293
  const entries = Object.entries(readSlots(ctx.mainWorktree, config.registryDir).slots).sort(([a], [b]) => Number(a) - Number(b));
295
294
  if (entries.length === 0) {
296
- console.log("No worktrees registered.");
295
+ console.log("No workspaces registered.");
297
296
  return;
298
297
  }
299
298
  const rows = entries.map(([port, e]) => ({
@@ -310,7 +309,7 @@ function runList(config) {
310
309
  type: "TYPE",
311
310
  status: "STATUS",
312
311
  branch: "BRANCH",
313
- worktree: "WORKTREE",
312
+ worktree: "PATH",
314
313
  owner: "OWNER",
315
314
  created: "CREATED",
316
315
  };
@@ -359,7 +358,7 @@ async function waitForSlot(slot, config, options = {}) {
359
358
  return;
360
359
  }
361
360
  if (entry.status === "failed") {
362
- const logPath = join(entry.worktree, config.runtimeDir, "wt-setup.log");
361
+ const logPath = setupLogPath(entry.worktree, config.runtimeDir);
363
362
  console.error(`FAILED: ${entry.failure?.message ?? "(no message)"}`);
364
363
  console.error(`Full log: ${logPath}`);
365
364
  process.exit(1);
@@ -372,7 +371,7 @@ async function handleRemove(command, ctx, run, config) {
372
371
  const removeHere = command.branch === undefined;
373
372
  const registry = readSlots(ctx.mainWorktree, config.registryDir);
374
373
  const target = resolveRemoveTarget(command, ctx, registry);
375
- // Refuse to remove while the detached finalize is still writing to slots.json / wt-setup.log:
374
+ // Refuse to remove while the detached finalize is still writing to slots.json / workspace-setup.log:
376
375
  // racing the two corrupts the registry and leaves the worktree directory orphaned.
377
376
  if (registry.slots[target.slotPort]?.status === "pending") {
378
377
  console.error(`Error: Setup is still in progress for slot ${target.slotPort}. ` +
@@ -411,7 +410,7 @@ async function handleRemove(command, ctx, run, config) {
411
410
  process.chdir(ctx.mainWorktree);
412
411
  }
413
412
  removeWorktree(target.worktreePath, run);
414
- console.log(`Removed worktree for branch "${target.branch}" (slot ${target.slotPort}${ownerSuffix}). ` +
413
+ console.log(`Removed workspace for branch "${target.branch}" (slot ${target.slotPort}${ownerSuffix}). ` +
415
414
  `Branch "${target.branch}" kept.`);
416
415
  if (removeHere) {
417
416
  console.log(`Now run: cd ${ctx.mainWorktree}`);
@@ -505,7 +504,7 @@ function resolveRemoveTarget(command, ctx, registry) {
505
504
  const branch = command.branch;
506
505
  const entry = Object.entries(registry.slots).find(([, v]) => getWorktreeBranch(v.worktree) === branch);
507
506
  if (!entry) {
508
- console.error(`Error: No worktree found for branch "${branch}" in the slot registry.`);
507
+ console.error(`Error: No workspace found for branch "${branch}" in the registry.`);
509
508
  process.exit(1);
510
509
  }
511
510
  const worktreePath = entry[1].worktree;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paleo/workspace",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Run multiple git-worktree dev environments side by side.",
5
5
  "keywords": [
6
6
  "workspace",