@paleo/worktree-env 0.5.0 → 0.5.2

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/cli.js CHANGED
@@ -80,6 +80,7 @@ export function printDevServerHelp() {
80
80
  console.log(formatHelp("dev-server [options]", "Start, stop, or list background dev-server processes.", DEV_SERVER_OPTIONS));
81
81
  }
82
82
  export function validateSetupFlags(args) {
83
+ // --wait may chain after --use/--create/--here; --info is standalone.
83
84
  const modeFlags = [
84
85
  args.use,
85
86
  args.create,
@@ -87,17 +88,19 @@ export function validateSetupFlags(args) {
87
88
  isRemoveMode(args),
88
89
  isSetOwnerMode(args),
89
90
  isFinalizeMode(args),
90
- isWaitMode(args),
91
91
  isInfoMode(args),
92
92
  ].filter(Boolean);
93
93
  if (modeFlags.length > 1) {
94
- throw new ConfigError("Error: --use, --create, --here, --remove, --remove-here, --set-owner, --wait, and --info are mutually exclusive.");
94
+ throw new ConfigError("Error: --use, --create, --here, --remove, --remove-here, --set-owner, and --info are mutually exclusive.");
95
+ }
96
+ if (isWaitMode(args) && (isRemoveMode(args) || isSetOwnerMode(args) || isInfoMode(args))) {
97
+ throw new ConfigError("Error: --wait can only be combined with --use, --create, or --here (or used standalone).");
95
98
  }
96
99
  if (args.remove !== undefined && args["remove-here"]) {
97
100
  throw new ConfigError("Error: --remove and --remove-here are mutually exclusive.");
98
101
  }
99
- if (args.slot !== undefined && !isSetupMode(args) && !isWaitMode(args)) {
100
- throw new ConfigError("Error: --slot can only be used with --use, --create, --here, or --wait.");
102
+ if (args.slot !== undefined && !isSetupMode(args) && !isWaitMode(args) && !isInfoMode(args)) {
103
+ throw new ConfigError("Error: --slot can only be used with --use, --create, --here, --wait, or --info.");
101
104
  }
102
105
  if (args.force && !isSetupMode(args) && !isFinalizeMode(args)) {
103
106
  throw new ConfigError("Error: --force can only be used with --use, --create, or --here.");
@@ -42,12 +42,12 @@ export async function runSetupWorktree(config) {
42
42
  await runFinalize(args, config);
43
43
  return;
44
44
  }
45
- if (isWaitMode(args)) {
45
+ if (isWaitMode(args) && !isSetupMode(args)) {
46
46
  await runWait(args, config);
47
47
  return;
48
48
  }
49
49
  if (isInfoMode(args)) {
50
- runInfo(config);
50
+ runInfo(args, config);
51
51
  return;
52
52
  }
53
53
  if (!isSetupMode(args) && !isRemoveMode(args) && !isSetOwnerMode(args)) {
@@ -65,7 +65,10 @@ export async function runSetupWorktree(config) {
65
65
  handleSetOwnerMode(args, ctx, config);
66
66
  return;
67
67
  }
68
- await runSetup(args, ctx, run, config);
68
+ const { slot } = await runSetup(args, ctx, run, config);
69
+ if (isWaitMode(args)) {
70
+ await waitForSlot(slot, config);
71
+ }
69
72
  }
70
73
  async function runSetup(args, ctx, run, config) {
71
74
  const scheme = resolvePortScheme(config);
@@ -91,6 +94,9 @@ async function runSetup(args, ctx, run, config) {
91
94
  const runtimeDir = join(setupCtx.currentWorktree, config.runtimeDir);
92
95
  mkdirSync(runtimeDir, { recursive: true });
93
96
  const logPath = join(runtimeDir, "wt-setup.log");
97
+ // Truncate any prior log so `--here` retries start with a clean record (the previous run's
98
+ // FAILED: banner would otherwise linger and produce false positives for grep-based tooling).
99
+ writeFileSync(logPath, "");
94
100
  // Opened "a" so the same fd can be inherited by the detached finalize child below.
95
101
  const logFd = openSync(logPath, "a");
96
102
  const teeLog = (message) => {
@@ -117,7 +123,7 @@ async function runSetup(args, ctx, run, config) {
117
123
  mainWorktree: setupCtx.mainWorktree,
118
124
  }));
119
125
  teeLog(`WORKTREE_CREATED path=${setupCtx.currentWorktree} branch=${branch} slot=${slot}`);
120
- teeLog(`Setup continuing in background. Tail: ${config.runtimeDir}/wt-setup.log`);
126
+ teeLog(`Setup continuing in background. Tail: ${logPath}`);
121
127
  const child = spawn(process.execPath, [config.scriptPath, "--__finalize", String(slot)], {
122
128
  detached: true,
123
129
  stdio: ["ignore", logFd, logFd],
@@ -125,6 +131,7 @@ async function runSetup(args, ctx, run, config) {
125
131
  });
126
132
  child.unref();
127
133
  closeSync(logFd);
134
+ return { slot };
128
135
  }
129
136
  async function runFinalize(args, config) {
130
137
  const slot = Number(args.__finalize);
@@ -173,7 +180,7 @@ async function runFinalize(args, config) {
173
180
  process.exit(1);
174
181
  }
175
182
  }
176
- function resolveWaitSlot(args, config) {
183
+ function resolveTargetSlot(args, config) {
177
184
  if (args.slot !== undefined) {
178
185
  const slot = Number(args.slot);
179
186
  const scheme = resolvePortScheme(config);
@@ -210,13 +217,27 @@ function printWorktreeInfo(config, slot, worktreeForLog, fallback) {
210
217
  mainWorktree: ctx.mainWorktree,
211
218
  }));
212
219
  }
213
- function runInfo(config) {
220
+ function runInfo(args, config) {
221
+ if (args.slot !== undefined) {
222
+ const slot = resolveTargetSlot(args, config);
223
+ const ctx = detectWorktree();
224
+ const entry = readSlots(ctx.mainWorktree, config.registryDir).slots[String(slot)];
225
+ if (!entry) {
226
+ console.error(`Error: No slot ${slot} in registry.`);
227
+ process.exit(1);
228
+ }
229
+ printWorktreeInfo(config, slot, entry.worktree, { branch: entry.branch, owner: entry.owner });
230
+ return;
231
+ }
214
232
  const resolved = resolveCurrentSlot(config.basePort, config.registryDir);
215
233
  printWorktreeInfo(config, resolved.slot, ".", { branch: resolved.branch, owner: resolved.owner });
216
234
  }
217
235
  async function runWait(args, config) {
236
+ const slot = resolveTargetSlot(args, config);
237
+ await waitForSlot(slot, config);
238
+ }
239
+ async function waitForSlot(slot, config) {
218
240
  const ctx = detectWorktree();
219
- const slot = resolveWaitSlot(args, config);
220
241
  const initial = readSlots(ctx.mainWorktree, config.registryDir).slots[String(slot)];
221
242
  if (!initial) {
222
243
  console.error(`Error: No slot ${slot} in registry.`);
@@ -249,6 +270,13 @@ async function handleRemove(args, ctx, run, config) {
249
270
  const removeHere = Boolean(args["remove-here"]);
250
271
  const registry = readSlots(ctx.mainWorktree, config.registryDir);
251
272
  const target = resolveRemoveTarget(args, ctx, registry, removeHere);
273
+ // Refuse to remove while the detached finalize is still writing to slots.json / wt-setup.log:
274
+ // racing the two corrupts the registry and leaves the worktree directory orphaned.
275
+ if (registry.slots[target.slotPort]?.status === "pending") {
276
+ console.error(`Error: Setup is still in progress for slot ${target.slotPort}. ` +
277
+ `Run 'setup-worktree --wait --slot ${target.slotPort}' to wait for it to finish (or fail), then retry --remove.`);
278
+ process.exit(1);
279
+ }
252
280
  if (!args["no-remote-check"]) {
253
281
  verifyBranchAbsentFromRemote(target.branch, run);
254
282
  }
@@ -275,7 +303,8 @@ async function handleRemove(args, ctx, run, config) {
275
303
  process.chdir(ctx.mainWorktree);
276
304
  }
277
305
  removeWorktree(target.worktreePath, run);
278
- console.log(`Removed worktree for branch "${target.branch}" (slot ${target.slotPort}${ownerSuffix}).`);
306
+ console.log(`Removed worktree for branch "${target.branch}" (slot ${target.slotPort}${ownerSuffix}). ` +
307
+ `Branch "${target.branch}" kept.`);
279
308
  if (removeHere) {
280
309
  console.log(`Now run: cd ${ctx.mainWorktree}`);
281
310
  }
package/dist/worktree.js CHANGED
@@ -40,6 +40,7 @@ export function createBranch(requestedBranch, ctx, run) {
40
40
  ++suffix;
41
41
  }
42
42
  finalBranch = `${requestedBranch}-${suffix}`;
43
+ console.warn(`Warning: Branch "${requestedBranch}" already exists; using "${finalBranch}" instead.`);
43
44
  }
44
45
  const worktreePath = computeWorktreePath(ctx.mainWorktree, finalBranch);
45
46
  execFileSync("git", ["worktree", "add", "-b", finalBranch, worktreePath], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paleo/worktree-env",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Worktree-based concurrent local environment kernel.",
5
5
  "keywords": [
6
6
  "worktree",