@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 +3 -3
- package/dist/cli.js +6 -6
- package/dist/dev-server.d.ts +1 -1
- package/dist/dev-server.js +3 -3
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +3 -0
- package/dist/slots.js +1 -1
- package/dist/workspace.d.ts +2 -2
- package/dist/workspace.js +13 -14
- package/package.json +1 -1
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/
|
|
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>/
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
182
|
+
" Remove a workspace by branch, or the current one when omitted.",
|
|
183
183
|
" list",
|
|
184
|
-
" List all registered
|
|
184
|
+
" List all registered workspaces (slot, status, branch, path, owner, created).",
|
|
185
185
|
" info [-s|--slot <port>]",
|
|
186
|
-
" Print a
|
|
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
|
|
190
|
+
" Update the current workspace's owner (no rebuild).",
|
|
191
191
|
"",
|
|
192
192
|
"Global options:",
|
|
193
193
|
" -v, --verbose Show intermediate output.",
|
package/dist/dev-server.d.ts
CHANGED
|
@@ -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/
|
|
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
|
*/
|
package/dist/dev-server.js
CHANGED
|
@@ -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 =
|
|
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(`
|
|
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
|
|
77
|
+
console.error("Error: No workspace here. Run `workspace setup` first.");
|
|
78
78
|
process.exit(1);
|
|
79
79
|
}
|
|
80
80
|
return slot;
|
package/dist/workspace.d.ts
CHANGED
|
@@ -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/
|
|
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>/
|
|
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
|
|
90
|
-
mkdirSync(
|
|
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(`
|
|
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 =
|
|
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
|
|
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 ${
|
|
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
|
|
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: "
|
|
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 =
|
|
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 /
|
|
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
|
|
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
|
|
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;
|