@paleo/workspace 0.21.0 → 0.22.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/dist/index.d.ts +1 -1
- package/dist/slots.d.ts +4 -1
- package/dist/slots.js +7 -1
- package/dist/workspace.d.ts +20 -3
- package/dist/workspace.js +36 -16
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { runWorkspace } from "./workspace.js";
|
|
2
2
|
export { defaultWorktreeDirName } from "./worktree.js";
|
|
3
3
|
export type { WorktreeDirNameFn } from "./worktree.js";
|
|
4
|
-
export type { WorkspaceConfig, SetupContext, SummaryContext, PatchContext, ConfigFileEntry, ConfigFileSource, ConfigFileSourceSpec, PurgeContext, } from "./workspace.js";
|
|
4
|
+
export type { WorkspaceConfig, SetupContext, FinalizeResult, SummaryContext, PatchContext, ConfigFileEntry, ConfigFileSource, ConfigFileSourceSpec, PurgeContext, } from "./workspace.js";
|
|
5
5
|
export { runDevServer } from "./dev-server.js";
|
|
6
6
|
export type { DevServerConfig, DevServerSummaryContext, ServerDescriptor, ServerContext, SpawnServer, CallbackServer, } from "./dev-server.js";
|
|
7
7
|
export type { ResolvedSlot } from "./slots.js";
|
package/dist/slots.d.ts
CHANGED
|
@@ -25,6 +25,9 @@ export interface SlotEntry {
|
|
|
25
25
|
};
|
|
26
26
|
/** `true` for the main-worktree entry. Absent on linked entries. */
|
|
27
27
|
main?: boolean;
|
|
28
|
+
/** Opaque blob the consumer returns from `finalizeWorktree`, handed back to `purgeInfrastructure`
|
|
29
|
+
* so an orphan's infrastructure can be torn down by name after its worktree (and config) is gone. */
|
|
30
|
+
extra?: unknown;
|
|
28
31
|
}
|
|
29
32
|
export interface SlotsRegistry {
|
|
30
33
|
slots: Record<string, SlotEntry>;
|
|
@@ -50,7 +53,7 @@ export declare function resolveAndRegisterSlot(input: RegisterSlotInput): {
|
|
|
50
53
|
owner: string | undefined;
|
|
51
54
|
status: SlotStatus;
|
|
52
55
|
};
|
|
53
|
-
export declare function markSlotReady(mainWorktree: string, registryDir: string, slotPort: number): void;
|
|
56
|
+
export declare function markSlotReady(mainWorktree: string, registryDir: string, slotPort: number, extra?: unknown): void;
|
|
54
57
|
export declare function markSlotFailed(mainWorktree: string, registryDir: string, slotPort: number, message: string): void;
|
|
55
58
|
export declare function validateSlotAvailability(slotArg: string | undefined, ctx: {
|
|
56
59
|
currentWorktree: string;
|
package/dist/slots.js
CHANGED
|
@@ -50,17 +50,21 @@ export function resolveAndRegisterSlot(input) {
|
|
|
50
50
|
entry.main = true;
|
|
51
51
|
if (owner !== undefined)
|
|
52
52
|
entry.owner = owner;
|
|
53
|
+
if (existing?.extra !== undefined)
|
|
54
|
+
entry.extra = existing.extra;
|
|
53
55
|
registry.slots[String(port)] = entry;
|
|
54
56
|
writeSlots(input.mainWorktree, input.registryDir, registry);
|
|
55
57
|
return { port, owner, status };
|
|
56
58
|
}
|
|
57
|
-
export function markSlotReady(mainWorktree, registryDir, slotPort) {
|
|
59
|
+
export function markSlotReady(mainWorktree, registryDir, slotPort, extra) {
|
|
58
60
|
const registry = readSlots(mainWorktree, registryDir);
|
|
59
61
|
const entry = registry.slots[String(slotPort)];
|
|
60
62
|
if (!entry)
|
|
61
63
|
return;
|
|
62
64
|
entry.status = "ready";
|
|
63
65
|
delete entry.failure;
|
|
66
|
+
if (extra !== undefined)
|
|
67
|
+
entry.extra = extra;
|
|
64
68
|
writeSlots(mainWorktree, registryDir, registry);
|
|
65
69
|
}
|
|
66
70
|
export function markSlotFailed(mainWorktree, registryDir, slotPort, message) {
|
|
@@ -116,6 +120,8 @@ export function handleSetOwner(input) {
|
|
|
116
120
|
};
|
|
117
121
|
if (slotData.failure)
|
|
118
122
|
updated.failure = slotData.failure;
|
|
123
|
+
if (slotData.extra !== undefined)
|
|
124
|
+
updated.extra = slotData.extra;
|
|
119
125
|
if (input.newOwner !== undefined)
|
|
120
126
|
updated.owner = input.newOwner;
|
|
121
127
|
registry.slots[slotPort] = updated;
|
package/dist/workspace.d.ts
CHANGED
|
@@ -49,11 +49,19 @@ export interface WorkspaceConfig {
|
|
|
49
49
|
*
|
|
50
50
|
* Runs in a detached child whose stdout/stderr are already redirected to
|
|
51
51
|
* `<runtimeDir>/logs/workspace-setup.log`. `console.log` and child-process `stdio: "inherit"` land there.
|
|
52
|
+
*
|
|
53
|
+
* May return `{ extra }` — an opaque blob persisted on the slot entry and handed back to
|
|
54
|
+
* {@link purgeInfrastructure}. Use it to record what infrastructure to tear down by name (e.g.
|
|
55
|
+
* container and volume names) so an orphaned worktree can still be cleaned up after its config is gone.
|
|
52
56
|
*/
|
|
53
|
-
finalizeWorktree: (ctx: SetupContext) => Promise<
|
|
57
|
+
finalizeWorktree: (ctx: SetupContext) => Promise<FinalizeResult | undefined> | FinalizeResult | undefined;
|
|
54
58
|
/**
|
|
55
|
-
* Destructive infrastructure teardown
|
|
56
|
-
*
|
|
59
|
+
* Destructive infrastructure teardown (e.g. `docker compose down -v` to wipe volumes). Runs after
|
|
60
|
+
* the dev-server stop on `workspace remove`, and on `workspace prune` / removing an orphaned
|
|
61
|
+
* worktree. MUST be idempotent and tolerate already-absent infrastructure: it may run when the
|
|
62
|
+
* worktree directory is gone (`ctx.extra` carries the recorded teardown identifiers; `ctx.worktree`
|
|
63
|
+
* no longer exists), so branch on the worktree's presence and tear down by name in that case.
|
|
64
|
+
* Best-effort; errors should be swallowed.
|
|
57
65
|
*/
|
|
58
66
|
purgeInfrastructure?: (ctx: PurgeContext) => Promise<void> | void;
|
|
59
67
|
/** Builds the post-setup summary printed to stdout. */
|
|
@@ -77,6 +85,10 @@ export interface PreSetupContext {
|
|
|
77
85
|
/** Writes to stdout and the setup log. */
|
|
78
86
|
log: (msg: string) => void;
|
|
79
87
|
}
|
|
88
|
+
/** Return value of {@link WorkspaceConfig.finalizeWorktree}. */
|
|
89
|
+
export interface FinalizeResult {
|
|
90
|
+
extra: unknown;
|
|
91
|
+
}
|
|
80
92
|
/** Context passed to {@link WorkspaceConfig.finalizeWorktree}. */
|
|
81
93
|
export interface SetupContext {
|
|
82
94
|
currentWorktree: string;
|
|
@@ -111,8 +123,13 @@ export interface SummaryContext {
|
|
|
111
123
|
}
|
|
112
124
|
/** Context passed to {@link WorkspaceConfig.purgeInfrastructure}. */
|
|
113
125
|
export interface PurgeContext {
|
|
126
|
+
/** The target worktree. May no longer exist on disk when purging an orphan — check before
|
|
127
|
+
* running cwd-bound commands; tear down by name (from {@link extra}) in that case. */
|
|
114
128
|
worktree: string;
|
|
115
129
|
mainWorktree: string;
|
|
130
|
+
slot: number;
|
|
131
|
+
/** The blob the consumer returned from `finalizeWorktree`, if any. */
|
|
132
|
+
extra?: unknown;
|
|
116
133
|
verbose: boolean;
|
|
117
134
|
}
|
|
118
135
|
/** A `{ path }` (relative to the main worktree) or `{ content }` (verbatim) initial source. */
|
package/dist/workspace.js
CHANGED
|
@@ -58,7 +58,7 @@ export async function runWorkspace(config) {
|
|
|
58
58
|
runList(registryDir);
|
|
59
59
|
return;
|
|
60
60
|
case "prune":
|
|
61
|
-
await runPrune(registryDir);
|
|
61
|
+
await runPrune(config, registryDir, verbose);
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
const ctx = detectWorktree();
|
|
@@ -223,8 +223,8 @@ async function runFinalize(command, config, registryDir) {
|
|
|
223
223
|
verbose: false,
|
|
224
224
|
};
|
|
225
225
|
try {
|
|
226
|
-
await config.finalizeWorktree(setupContext);
|
|
227
|
-
markSlotReady(ctx.mainWorktree, registryDir, slot);
|
|
226
|
+
const result = await config.finalizeWorktree(setupContext);
|
|
227
|
+
markSlotReady(ctx.mainWorktree, registryDir, slot, result?.extra);
|
|
228
228
|
appendLog("============================================================");
|
|
229
229
|
appendLog(`READY: branch ${branch} (slot ${slot})`);
|
|
230
230
|
appendLog("============================================================");
|
|
@@ -399,7 +399,7 @@ function hintLiveOrphans(liveOrphans) {
|
|
|
399
399
|
console.log(`\nNote: ${liveOrphans.length} workspace(s) have a deleted worktree but a still-running ` +
|
|
400
400
|
"dev-server. Run `workspace prune` to stop them and clean up.");
|
|
401
401
|
}
|
|
402
|
-
async function runPrune(registryDir) {
|
|
402
|
+
async function runPrune(config, registryDir, verbose) {
|
|
403
403
|
const ctx = detectWorktree();
|
|
404
404
|
const registry = readSlots(ctx.mainWorktree, registryDir);
|
|
405
405
|
const orphanPorts = findOrphanPorts(registry);
|
|
@@ -407,6 +407,13 @@ async function runPrune(registryDir) {
|
|
|
407
407
|
for (const port of orphanPorts) {
|
|
408
408
|
const entry = registry.slots[port];
|
|
409
409
|
stoppedProcesses += await stopOrphanedDevServer(ctx.mainWorktree, registryDir, entry.worktree);
|
|
410
|
+
await runPurgeInfrastructure(config, {
|
|
411
|
+
worktree: entry.worktree,
|
|
412
|
+
mainWorktree: ctx.mainWorktree,
|
|
413
|
+
slot: Number(port),
|
|
414
|
+
extra: entry.extra,
|
|
415
|
+
verbose,
|
|
416
|
+
});
|
|
410
417
|
delete registry.slots[port];
|
|
411
418
|
const ownerSuffix = entry.owner ? `, owner ${entry.owner}` : "";
|
|
412
419
|
console.log(`Pruned slot ${port} (${entry.worktree}${ownerSuffix}).`);
|
|
@@ -419,16 +426,18 @@ async function runPrune(registryDir) {
|
|
|
419
426
|
return;
|
|
420
427
|
}
|
|
421
428
|
console.log(`Pruned ${orphanPorts.length} orphaned workspace(s).`);
|
|
422
|
-
if (stoppedProcesses > 0)
|
|
423
|
-
console.log(`Stopped ${stoppedProcesses} orphaned process(es)
|
|
424
|
-
|
|
429
|
+
if (stoppedProcesses > 0)
|
|
430
|
+
console.log(`Stopped ${stoppedProcesses} orphaned process(es).`);
|
|
431
|
+
if (config.purgeInfrastructure === undefined) {
|
|
432
|
+
console.log("Note: infrastructure managed by callback servers (e.g. `docker compose`) is not torn down " +
|
|
433
|
+
"automatically — check for leftover containers.");
|
|
425
434
|
}
|
|
426
435
|
}
|
|
427
436
|
/**
|
|
428
437
|
* Stop a gone worktree's dev-server the only way left: its dir (and `dev-server.mjs`) is deleted, so
|
|
429
438
|
* we can't shell out to `dev down` to run callback stop() — we kill the recorded spawn PIDs directly
|
|
430
|
-
* and drop the dev-server entry. Returns the count of live PIDs stopped.
|
|
431
|
-
*
|
|
439
|
+
* and drop the dev-server entry. Returns the count of live PIDs stopped. The caller separately runs
|
|
440
|
+
* `purgeInfrastructure` (by name, from the slot's `extra`) to tear down callback-managed infra.
|
|
432
441
|
*/
|
|
433
442
|
async function stopOrphanedDevServer(mainWorktree, registryDir, worktree) {
|
|
434
443
|
const devEntry = findOwnEntry(mainWorktree, registryDir, worktree);
|
|
@@ -509,6 +518,13 @@ async function handleRemove(command, ctx, run, config, registryDir) {
|
|
|
509
518
|
if (!existsSync(target.worktreePath)) {
|
|
510
519
|
console.warn(`Warning: Worktree directory ${target.worktreePath} not found. Cleaning up registry only.`);
|
|
511
520
|
await stopOrphanedDevServer(ctx.mainWorktree, registryDir, target.worktreePath);
|
|
521
|
+
await runPurgeInfrastructure(config, {
|
|
522
|
+
worktree: target.worktreePath,
|
|
523
|
+
mainWorktree: ctx.mainWorktree,
|
|
524
|
+
slot: Number(target.slotPort),
|
|
525
|
+
extra: registry.slots[target.slotPort]?.extra,
|
|
526
|
+
verbose: run.verbose,
|
|
527
|
+
});
|
|
512
528
|
delete registry.slots[target.slotPort];
|
|
513
529
|
writeSlots(ctx.mainWorktree, registryDir, registry);
|
|
514
530
|
pruneGitWorktrees(ctx.mainWorktree);
|
|
@@ -526,13 +542,13 @@ async function handleRemove(command, ctx, run, config, registryDir) {
|
|
|
526
542
|
else {
|
|
527
543
|
verboseLog(`No dev-server running in ${target.worktreePath}; skipping stop.`);
|
|
528
544
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
545
|
+
await runPurgeInfrastructure(config, {
|
|
546
|
+
worktree: target.worktreePath,
|
|
547
|
+
mainWorktree: ctx.mainWorktree,
|
|
548
|
+
slot: Number(target.slotPort),
|
|
549
|
+
extra: registry.slots[target.slotPort]?.extra,
|
|
550
|
+
verbose: run.verbose,
|
|
551
|
+
});
|
|
536
552
|
delete registry.slots[target.slotPort];
|
|
537
553
|
writeSlots(ctx.mainWorktree, registryDir, registry);
|
|
538
554
|
removeDevServerEntryByWorktree(ctx.mainWorktree, registryDir, target.worktreePath);
|
|
@@ -546,6 +562,10 @@ async function handleRemove(command, ctx, run, config, registryDir) {
|
|
|
546
562
|
console.log(`Now run: cd ${ctx.mainWorktree}`);
|
|
547
563
|
}
|
|
548
564
|
}
|
|
565
|
+
async function runPurgeInfrastructure(config, ctx) {
|
|
566
|
+
if (config.purgeInfrastructure)
|
|
567
|
+
await config.purgeInfrastructure(ctx);
|
|
568
|
+
}
|
|
549
569
|
function handleSetOwnerMode(command, ctx, registryDir) {
|
|
550
570
|
const newOwner = command.name;
|
|
551
571
|
const { slotPort } = handleSetOwner({
|