@paleo/worktree-env 0.6.1 → 0.6.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 +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/setup-worktree.d.ts +20 -8
- package/dist/setup-worktree.js +5 -4
- package/dist/worktree.d.ts +14 -3
- package/dist/worktree.js +37 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,15 +2,15 @@ import { parseArgs } from "node:util";
|
|
|
2
2
|
import { ConfigError } from "./errors.js";
|
|
3
3
|
const SETUP_OPTIONS = {
|
|
4
4
|
help: { type: "boolean", short: "h", description: "Show this help message" },
|
|
5
|
-
|
|
5
|
+
create: {
|
|
6
6
|
type: "string",
|
|
7
7
|
arg: "branch",
|
|
8
|
-
description: "Create a
|
|
8
|
+
description: "Create a new branch + worktree, then set up the local environment. If the branch already exists, appends a numeric suffix (-2, -3, ...)",
|
|
9
9
|
},
|
|
10
|
-
|
|
10
|
+
use: {
|
|
11
11
|
type: "string",
|
|
12
12
|
arg: "branch",
|
|
13
|
-
description: "Create a
|
|
13
|
+
description: "Create a worktree for an existing branch, then set up the local environment",
|
|
14
14
|
},
|
|
15
15
|
here: {
|
|
16
16
|
type: "boolean",
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { runSetupWorktree } from "./setup-worktree.js";
|
|
2
|
+
export { defaultWorktreeDirName } from "./worktree.js";
|
|
3
|
+
export type { WorktreeDirNameFn } from "./worktree.js";
|
|
2
4
|
export type { SetupWorktreeConfig, SetupContext, SummaryContext, PatchContext, ConfigFileEntry, PurgeContext, } from "./setup-worktree.js";
|
|
3
5
|
export { runDevServer } from "./dev-server.js";
|
|
4
6
|
export type { DevServerConfig, DevServerSummaryContext, ServerDescriptor, ServerContext, SpawnServer, CallbackServer, } from "./dev-server.js";
|
package/dist/index.js
CHANGED
package/dist/setup-worktree.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WorktreeDirNameFn } from "./worktree.js";
|
|
1
2
|
/** Configuration accepted by {@link runSetupWorktree}. */
|
|
2
3
|
export interface SetupWorktreeConfig {
|
|
3
4
|
/**
|
|
@@ -6,6 +7,13 @@ export interface SetupWorktreeConfig {
|
|
|
6
7
|
* — typically `fileURLToPath(import.meta.url)` from your `setup-worktree.mjs`.
|
|
7
8
|
*/
|
|
8
9
|
scriptPath: string;
|
|
10
|
+
/**
|
|
11
|
+
* Absolute path to your dev-server script (the file that calls `runDevServer`). On `--remove`,
|
|
12
|
+
* the kernel shells out to `node <devServerScript> --stop` with `cwd: <target worktree>`.
|
|
13
|
+
* Typically `fileURLToPath(new URL('./dev-server.mjs', import.meta.url))` from your
|
|
14
|
+
* `setup-worktree.mjs`.
|
|
15
|
+
*/
|
|
16
|
+
devServerScript: string;
|
|
9
17
|
/** Anchor port for the slot range. Slots are derived from this value. */
|
|
10
18
|
basePort: number;
|
|
11
19
|
/** Distance between consecutive slots. Defaults to `10`. */
|
|
@@ -38,13 +46,6 @@ export interface SetupWorktreeConfig {
|
|
|
38
46
|
* installed deps, etc.).
|
|
39
47
|
*/
|
|
40
48
|
finalizeWorktree: (ctx: SetupContext) => Promise<void> | void;
|
|
41
|
-
/**
|
|
42
|
-
* Absolute path to your dev-server script (the file that calls `runDevServer`). On `--remove`,
|
|
43
|
-
* the kernel shells out to `node <devServerScript> --stop` with `cwd: <target worktree>`.
|
|
44
|
-
* Typically `fileURLToPath(new URL('./dev-server.mjs', import.meta.url))` from your
|
|
45
|
-
* `setup-worktree.mjs`.
|
|
46
|
-
*/
|
|
47
|
-
devServerScript: string;
|
|
48
49
|
/**
|
|
49
50
|
* Destructive infrastructure teardown on `--remove` (e.g. `docker compose down -v` to wipe
|
|
50
51
|
* volumes). Runs after the dev-server stop. Best-effort; errors should be swallowed.
|
|
@@ -52,6 +53,13 @@ export interface SetupWorktreeConfig {
|
|
|
52
53
|
purgeInfrastructure?: (ctx: PurgeContext) => Promise<void> | void;
|
|
53
54
|
/** Builds the post-setup summary printed to stdout. */
|
|
54
55
|
printSummary: (ctx: SummaryContext) => string;
|
|
56
|
+
/**
|
|
57
|
+
* Optional override for the worktree directory basename. Receives `{ branch, repoName }` and
|
|
58
|
+
* returns the basename (e.g. `myrepo-feat-ABC-123`). Defaults to {@link defaultWorktreeDirName},
|
|
59
|
+
* which strips a recognizable ticket suffix and caps the slug at 22 chars. The kernel handles
|
|
60
|
+
* deduplication (`-2`, `-3`…) when the resulting directory already exists.
|
|
61
|
+
*/
|
|
62
|
+
worktreeDirName?: WorktreeDirNameFn;
|
|
55
63
|
}
|
|
56
64
|
/** Context passed to {@link SetupWorktreeConfig.finalizeWorktree}. */
|
|
57
65
|
export interface SetupContext {
|
|
@@ -64,7 +72,11 @@ export interface SetupContext {
|
|
|
64
72
|
force: boolean;
|
|
65
73
|
verbose: boolean;
|
|
66
74
|
}
|
|
67
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Context passed to {@link SetupWorktreeConfig.printSummary}.
|
|
77
|
+
*
|
|
78
|
+
* Called after worktree creation; the dev-server is not running yet.
|
|
79
|
+
*/
|
|
68
80
|
export interface SummaryContext {
|
|
69
81
|
slot: number;
|
|
70
82
|
branch: string;
|
package/dist/setup-worktree.js
CHANGED
|
@@ -78,7 +78,7 @@ async function runSetup(args, ctx, run, config) {
|
|
|
78
78
|
registryDir: config.registryDir,
|
|
79
79
|
scheme,
|
|
80
80
|
});
|
|
81
|
-
const setupCtx = ensureWorktree(args, ctx, run);
|
|
81
|
+
const setupCtx = ensureWorktree(args, ctx, run, config.worktreeDirName);
|
|
82
82
|
const branch = getCurrentBranch(setupCtx.currentWorktree);
|
|
83
83
|
const { port: slot, owner } = resolveAndRegisterSlot({
|
|
84
84
|
slot: args.slot,
|
|
@@ -123,6 +123,7 @@ async function runSetup(args, ctx, run, config) {
|
|
|
123
123
|
}));
|
|
124
124
|
teeLog(`WORKTREE_CREATED path=${setupCtx.currentWorktree} branch=${branch} slot=${slot}`);
|
|
125
125
|
teeLog(`Setup continuing in background. Tail: ${logPath}`);
|
|
126
|
+
teeLog(`Block until ready: setup-worktree --wait --slot ${slot}`);
|
|
126
127
|
const child = spawn(process.execPath, [config.scriptPath, "--__finalize", String(slot)], {
|
|
127
128
|
detached: true,
|
|
128
129
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -345,11 +346,11 @@ function handleSetOwnerMode(args, ctx, config) {
|
|
|
345
346
|
}
|
|
346
347
|
console.log(`Owner for slot ${slotPort}: ${newOwner ?? "(none)"}`);
|
|
347
348
|
}
|
|
348
|
-
function ensureWorktree(args, ctx, run) {
|
|
349
|
+
function ensureWorktree(args, ctx, run, dirNameFn) {
|
|
349
350
|
if (args.use)
|
|
350
|
-
return useExistingBranch(args.use, ctx, run);
|
|
351
|
+
return useExistingBranch(args.use, ctx, run, dirNameFn);
|
|
351
352
|
if (args.create)
|
|
352
|
-
return createBranch(args.create, ctx, run);
|
|
353
|
+
return createBranch(args.create, ctx, run, dirNameFn);
|
|
353
354
|
return ctx;
|
|
354
355
|
}
|
|
355
356
|
function linkSharedDirectories(ctx, dirs, log) {
|
package/dist/worktree.d.ts
CHANGED
|
@@ -12,9 +12,20 @@ export declare function enforceWorktreeMode(args: {
|
|
|
12
12
|
create?: string;
|
|
13
13
|
here?: boolean;
|
|
14
14
|
}, ctx: WorktreeContext): void;
|
|
15
|
-
export declare function useExistingBranch(branch: string, ctx: WorktreeContext, run: RunCtx): WorktreeContext;
|
|
16
|
-
export declare function createBranch(requestedBranch: string, ctx: WorktreeContext, run: RunCtx): WorktreeContext;
|
|
15
|
+
export declare function useExistingBranch(branch: string, ctx: WorktreeContext, run: RunCtx, dirNameFn?: WorktreeDirNameFn): WorktreeContext;
|
|
16
|
+
export declare function createBranch(requestedBranch: string, ctx: WorktreeContext, run: RunCtx, dirNameFn?: WorktreeDirNameFn): WorktreeContext;
|
|
17
17
|
export declare function verifyBranchAbsentFromRemote(branch: string, run: RunCtx): void;
|
|
18
18
|
export declare function getCurrentBranch(worktreePath: string): string;
|
|
19
19
|
export declare function removeWorktree(worktreePath: string, run: RunCtx): void;
|
|
20
|
-
|
|
20
|
+
/** Pure function that produces the basename of a worktree directory from a branch. */
|
|
21
|
+
export type WorktreeDirNameFn = (opts: {
|
|
22
|
+
branch: string;
|
|
23
|
+
repoName: string;
|
|
24
|
+
}) => string;
|
|
25
|
+
/**
|
|
26
|
+
* Default {@link WorktreeDirNameFn}. Strips a recognizable ticket suffix from the last branch
|
|
27
|
+
* segment (`feat/ABC-123-extra` → `feat-ABC-123`), caps the result at 22 chars, and strips
|
|
28
|
+
* trailing dashes. Falls back to the full sanitized branch when no ticket pattern is found.
|
|
29
|
+
*/
|
|
30
|
+
export declare const defaultWorktreeDirName: WorktreeDirNameFn;
|
|
31
|
+
export declare function computeWorktreePath(mainWorktree: string, branch: string, dirNameFn?: WorktreeDirNameFn): string;
|
package/dist/worktree.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
4
|
export function detectWorktree() {
|
|
4
5
|
const currentWorktree = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
@@ -23,16 +24,16 @@ export function enforceWorktreeMode(args, ctx) {
|
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
|
-
export function useExistingBranch(branch, ctx, run) {
|
|
27
|
+
export function useExistingBranch(branch, ctx, run, dirNameFn = defaultWorktreeDirName) {
|
|
27
28
|
if (!branchExists(branch)) {
|
|
28
29
|
console.error(`Error: Branch "${branch}" does not exist locally or on the remote.`);
|
|
29
30
|
process.exit(1);
|
|
30
31
|
}
|
|
31
|
-
const worktreePath = computeWorktreePath(ctx.mainWorktree, branch);
|
|
32
|
+
const worktreePath = dedupeWorktreePath(computeWorktreePath(ctx.mainWorktree, branch, dirNameFn));
|
|
32
33
|
execFileSync("git", ["worktree", "add", worktreePath, branch], { stdio: stdioFor(run) });
|
|
33
34
|
return { ...ctx, currentWorktree: worktreePath, isMainWorktree: false };
|
|
34
35
|
}
|
|
35
|
-
export function createBranch(requestedBranch, ctx, run) {
|
|
36
|
+
export function createBranch(requestedBranch, ctx, run, dirNameFn = defaultWorktreeDirName) {
|
|
36
37
|
let finalBranch = requestedBranch;
|
|
37
38
|
if (branchExists(finalBranch)) {
|
|
38
39
|
let suffix = 2;
|
|
@@ -42,7 +43,7 @@ export function createBranch(requestedBranch, ctx, run) {
|
|
|
42
43
|
finalBranch = `${requestedBranch}-${suffix}`;
|
|
43
44
|
console.warn(`Warning: Branch "${requestedBranch}" already exists; using "${finalBranch}" instead.`);
|
|
44
45
|
}
|
|
45
|
-
const worktreePath = computeWorktreePath(ctx.mainWorktree, finalBranch);
|
|
46
|
+
const worktreePath = dedupeWorktreePath(computeWorktreePath(ctx.mainWorktree, finalBranch, dirNameFn));
|
|
46
47
|
execFileSync("git", ["worktree", "add", "-b", finalBranch, worktreePath], {
|
|
47
48
|
stdio: stdioFor(run),
|
|
48
49
|
});
|
|
@@ -67,10 +68,39 @@ export function getCurrentBranch(worktreePath) {
|
|
|
67
68
|
export function removeWorktree(worktreePath, run) {
|
|
68
69
|
execFileSync("git", ["worktree", "remove", "--force", worktreePath], { stdio: stdioFor(run) });
|
|
69
70
|
}
|
|
70
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Default {@link WorktreeDirNameFn}. Strips a recognizable ticket suffix from the last branch
|
|
73
|
+
* segment (`feat/ABC-123-extra` → `feat-ABC-123`), caps the result at 22 chars, and strips
|
|
74
|
+
* trailing dashes. Falls back to the full sanitized branch when no ticket pattern is found.
|
|
75
|
+
*/
|
|
76
|
+
export const defaultWorktreeDirName = ({ branch, repoName }) => {
|
|
77
|
+
return `${repoName}-${shortenBranchSegment(branch)}`;
|
|
78
|
+
};
|
|
79
|
+
function shortenBranchSegment(branch) {
|
|
80
|
+
const parts = branch.split("/");
|
|
81
|
+
const last = parts[parts.length - 1] ?? "";
|
|
82
|
+
const match = last.match(/^([A-Za-z]+-\d+|\d+)/);
|
|
83
|
+
if (match) {
|
|
84
|
+
parts[parts.length - 1] = match[1];
|
|
85
|
+
}
|
|
86
|
+
let result = parts.join("-");
|
|
87
|
+
if (result.length > 22) {
|
|
88
|
+
result = result.slice(0, 22);
|
|
89
|
+
}
|
|
90
|
+
return result.replace(/-+$/, "");
|
|
91
|
+
}
|
|
92
|
+
export function computeWorktreePath(mainWorktree, branch, dirNameFn = defaultWorktreeDirName) {
|
|
71
93
|
const repoName = basename(mainWorktree);
|
|
72
|
-
|
|
73
|
-
|
|
94
|
+
return join(dirname(mainWorktree), dirNameFn({ branch, repoName }));
|
|
95
|
+
}
|
|
96
|
+
function dedupeWorktreePath(candidate) {
|
|
97
|
+
if (!existsSync(candidate))
|
|
98
|
+
return candidate;
|
|
99
|
+
let suffix = 2;
|
|
100
|
+
while (existsSync(`${candidate}-${suffix}`)) {
|
|
101
|
+
++suffix;
|
|
102
|
+
}
|
|
103
|
+
return `${candidate}-${suffix}`;
|
|
74
104
|
}
|
|
75
105
|
function branchExists(branch) {
|
|
76
106
|
try {
|