@bluewombat/isolation-git 0.1.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 +62 -0
- package/dist/branch-name.d.ts +2 -0
- package/dist/branch-name.d.ts.map +1 -0
- package/dist/branch-name.js +42 -0
- package/dist/branch-name.js.map +1 -0
- package/dist/child/run-child.d.ts +42 -0
- package/dist/child/run-child.d.ts.map +1 -0
- package/dist/child/run-child.js +121 -0
- package/dist/child/run-child.js.map +1 -0
- package/dist/copy-working-files.d.ts +40 -0
- package/dist/copy-working-files.d.ts.map +1 -0
- package/dist/copy-working-files.js +144 -0
- package/dist/copy-working-files.js.map +1 -0
- package/dist/fold.d.ts +6 -0
- package/dist/fold.d.ts.map +1 -0
- package/dist/fold.js +19 -0
- package/dist/fold.js.map +1 -0
- package/dist/git-merge.d.ts +37 -0
- package/dist/git-merge.d.ts.map +1 -0
- package/dist/git-merge.js +415 -0
- package/dist/git-merge.js.map +1 -0
- package/dist/git-worktree.d.ts +37 -0
- package/dist/git-worktree.d.ts.map +1 -0
- package/dist/git-worktree.js +68 -0
- package/dist/git-worktree.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/isolation.d.ts +13 -0
- package/dist/isolation.d.ts.map +1 -0
- package/dist/isolation.js +43 -0
- package/dist/isolation.js.map +1 -0
- package/dist/reference.d.ts +15 -0
- package/dist/reference.d.ts.map +1 -0
- package/dist/reference.js +196 -0
- package/dist/reference.js.map +1 -0
- package/dist/run-env.d.ts +30 -0
- package/dist/run-env.d.ts.map +1 -0
- package/dist/run-env.js +49 -0
- package/dist/run-env.js.map +1 -0
- package/dist/strategy.d.ts +19 -0
- package/dist/strategy.d.ts.map +1 -0
- package/dist/strategy.js +44 -0
- package/dist/strategy.js.map +1 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Isolation: git
|
|
2
|
+
|
|
3
|
+
Git worktree attach and three-way merge fold. Host (or a Transformer CLI) loads
|
|
4
|
+
this package when `workLine.isolation` is `@bluewombat/isolation-git`.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { branchNameOf, strategy } from "@bluewombat/isolation-git";
|
|
8
|
+
// strategy.isolation → Isolator
|
|
9
|
+
// strategy.fold → Integrator
|
|
10
|
+
// strategy.refOf(id) → issue/<item>, what the Authority hands the Publisher:
|
|
11
|
+
// issue/9 for github:owner/repo#9, issue/9-s1 for its Subtask s1,
|
|
12
|
+
// issue/<slug of the id> for an id in any other shape, cut at 80 with a
|
|
13
|
+
// 7-hex digest of the whole id when it was cut
|
|
14
|
+
// branchNameOf(id) → the same, for anyone holding an id
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The Child is a worktree of the Parent, then wiped and filled with the Parent's
|
|
18
|
+
working files so dirty and untracked content matches — `.cursor/`, `CLAUDE.md`,
|
|
19
|
+
`node_modules/`, tracked or not, all of it.
|
|
20
|
+
|
|
21
|
+
## What a Child does not get
|
|
22
|
+
|
|
23
|
+
`.env` and `.env.*`, at any depth, unless the Project says otherwise: a secret
|
|
24
|
+
in the clear has no business in a directory an agent reads and a pull request
|
|
25
|
+
may carry. A Project names its own list in the config, and that list
|
|
26
|
+
**replaces** the default:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
"workLine": {
|
|
30
|
+
"isolation": "@bluewombat/isolation-git",
|
|
31
|
+
"isolationOptions": { "exclude": [".env", ".env.*", "**/*.pem", "secrets/**"] }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A pattern with no `/` is matched against the last path segment, at any depth
|
|
36
|
+
(`.env` also leaves out `packages/api/.env`); one with a `/` against the whole
|
|
37
|
+
path from the root (`build/out`). `*` is any run of characters within one
|
|
38
|
+
segment, `**` anything, and a leading `**/` means "at any depth". An unknown
|
|
39
|
+
key, or an `exclude` that is not a list of non-empty strings, stops `mason
|
|
40
|
+
run` before it isolates anything. Nothing here reads `.gitignore`: what git
|
|
41
|
+
ignores is very often exactly what a Builder needs (its dependencies, the
|
|
42
|
+
Project's own agent configuration).
|
|
43
|
+
|
|
44
|
+
Each root entry is copied by `cp` — `-c` on macOS, `--reflink=auto` elsewhere
|
|
45
|
+
— so on a file system that clones (APFS, btrfs, XFS) the Child shares its
|
|
46
|
+
bytes with the Parent instead of doubling them; where `cp` cannot, the entry
|
|
47
|
+
is copied by hand. Excluded paths are removed from the Child afterwards.
|
|
48
|
+
|
|
49
|
+
## The reference, and who the system is
|
|
50
|
+
|
|
51
|
+
`strategy.reference.parse` reads what a manager names as the reference work
|
|
52
|
+
line: `remote` and `branch`, required; `credentialEnv` and `author`, optional.
|
|
53
|
+
Anything else is refused. From the optional two it builds the git environment
|
|
54
|
+
of the run — `GIT_AUTHOR_*` / `GIT_COMMITTER_*`, and a credential helper that
|
|
55
|
+
reads the token from the named variable at the moment git asks, after
|
|
56
|
+
clearing the machine's own helpers so a keychain never answers first. Nothing
|
|
57
|
+
is written to disk. Every git this package spawns carries that environment,
|
|
58
|
+
and the parsed reference exposes it as `env` so Host can hand it to the slots
|
|
59
|
+
that touch the work line (Publisher, Refresher) — and to nothing else: a
|
|
60
|
+
Builder never inherits a token.
|
|
61
|
+
|
|
62
|
+
Without `author`, commits carry the identity of whoever runs the process.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branch-name.d.ts","sourceRoot":"","sources":["../src/branch-name.ts"],"names":[],"mappings":"AA4BA,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAiB/C"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* A ref name for one Isolation, from its id.
|
|
4
|
+
*
|
|
5
|
+
* An unnamed Child leaves nothing behind: once its directory is gone, no one
|
|
6
|
+
* can see what the work was. The name is derived rather than asked for, so the
|
|
7
|
+
* contract keeps one id and gains no second identity to keep in step.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* What the branch is named after: the tracked item it works on, not the tool
|
|
11
|
+
* that made it. The tool is one git user among others on the repository; the
|
|
12
|
+
* branch outlives its run and is read by people who never heard of it.
|
|
13
|
+
*/
|
|
14
|
+
const REF_PREFIX = "issue";
|
|
15
|
+
/**
|
|
16
|
+
* `<scheme>:<path>#<number>[:<rest>]` is how a tracker names an item — a
|
|
17
|
+
* GitHub issue, a GitLab one — and inside one Project the work line belongs to
|
|
18
|
+
* one repository, so the number alone tells the branches apart. An id in any
|
|
19
|
+
* other shape keeps its whole slug.
|
|
20
|
+
*/
|
|
21
|
+
const TRACKED_ITEM = /^[a-z][a-z0-9+.-]*:(?:[^#]*\/)?[^#/]*#(\d+)(?::(.+))?$/i;
|
|
22
|
+
/** Where a slug is cut; past it, a digest of the whole id keeps two long ids apart. */
|
|
23
|
+
const MAX_SLUG = 80;
|
|
24
|
+
const DIGEST_LENGTH = 7;
|
|
25
|
+
export function branchNameOf(id) {
|
|
26
|
+
const item = TRACKED_ITEM.exec(id);
|
|
27
|
+
const short = item?.[1] === undefined ? id : item[2] === undefined ? item[1] : `${item[1]}:${item[2]}`;
|
|
28
|
+
const slug = short
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
31
|
+
.replace(/^-+|-+$/g, "");
|
|
32
|
+
if (slug.length <= MAX_SLUG) {
|
|
33
|
+
return `${REF_PREFIX}/${slug.length > 0 ? slug : "isolation"}`;
|
|
34
|
+
}
|
|
35
|
+
// Two ids that differ only past the cut would otherwise share a name, and
|
|
36
|
+
// `-B` would reset one Isolation's branch onto the other's. The digest is
|
|
37
|
+
// of the id as given, so the name stays a function of the id alone.
|
|
38
|
+
const digest = createHash("sha256").update(id).digest("hex").slice(0, DIGEST_LENGTH);
|
|
39
|
+
const head = slug.slice(0, MAX_SLUG - DIGEST_LENGTH - 1).replace(/-+$/, "");
|
|
40
|
+
return `${REF_PREFIX}/${head}-${digest}`;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=branch-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branch-name.js","sourceRoot":"","sources":["../src/branch-name.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;GAMG;AACH;;;;GAIG;AACH,MAAM,UAAU,GAAG,OAAO,CAAC;AAE3B;;;;;GAKG;AACH,MAAM,YAAY,GAAG,yDAAyD,CAAC;AAE/E,uFAAuF;AACvF,MAAM,QAAQ,GAAG,EAAE,CAAC;AACpB,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnC,MAAM,KAAK,GACT,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3F,MAAM,IAAI,GAAG,KAAK;SACf,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC5B,OAAO,GAAG,UAAU,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACjE,CAAC;IACD,0EAA0E;IAC1E,0EAA0E;IAC1E,oEAAoE;IACpE,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;IACrF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC5E,OAAO,GAAG,UAAU,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
type ClockName = "isolation" | "integration";
|
|
2
|
+
export type SpawnRequest = {
|
|
3
|
+
argv: string[];
|
|
4
|
+
/** Undefined lets the child inherit the caller's directory. */
|
|
5
|
+
cwd?: string | undefined;
|
|
6
|
+
/** Wall-clock budget for this child (ms). */
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
/** Which clock owns the nearer deadline when timeoutMs hits. */
|
|
9
|
+
timeoutClock: ClockName;
|
|
10
|
+
/** When true, treat kill as interrupted (stop signal). */
|
|
11
|
+
shouldInterrupt: () => boolean;
|
|
12
|
+
/** Undefined lets the child inherit the caller's environment. */
|
|
13
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
14
|
+
/** Layered on top of the inherited environment. Ignored when `env` is set. */
|
|
15
|
+
envOverrides?: NodeJS.ProcessEnv | undefined;
|
|
16
|
+
};
|
|
17
|
+
export type SpawnOutcome = {
|
|
18
|
+
kind: "exited";
|
|
19
|
+
exitCode: number | null;
|
|
20
|
+
signal: NodeJS.Signals | null;
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "timed_out";
|
|
25
|
+
clock: ClockName;
|
|
26
|
+
stdout: string;
|
|
27
|
+
stderr: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "interrupted";
|
|
30
|
+
stdout: string;
|
|
31
|
+
stderr: string;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "spawn_error";
|
|
34
|
+
detail: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Run one child with a wall-clock timeout. Does not inherit the caller's stdin.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runChild(request: SpawnRequest): Promise<SpawnOutcome>;
|
|
40
|
+
export declare function combinedOutput(stdout: string, stderr: string): string;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=run-child.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-child.d.ts","sourceRoot":"","sources":["../../src/child/run-child.ts"],"names":[],"mappings":"AAIA,KAAK,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;AAE7C,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,YAAY,EAAE,SAAS,CAAC;IACxB,0DAA0D;IAC1D,eAAe,EAAE,MAAM,OAAO,CAAC;IAC/B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;IACpC,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC;CAC9C,CAAC;AAgBF,MAAM,MAAM,YAAY,GACpB;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAiBN;;GAEG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA+FrE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrE"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
const OUTPUT_TRUNCATE = 8_192;
|
|
3
|
+
/**
|
|
4
|
+
* The one place ambient environment enters a child. `env` replaces it outright;
|
|
5
|
+
* `envOverrides` layers on top of what the child would inherit anyway.
|
|
6
|
+
*/
|
|
7
|
+
function childEnv(request) {
|
|
8
|
+
if (request.env !== undefined) {
|
|
9
|
+
return request.env;
|
|
10
|
+
}
|
|
11
|
+
if (request.envOverrides !== undefined) {
|
|
12
|
+
return { ...process.env, ...request.envOverrides };
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
function truncate(text) {
|
|
17
|
+
if (text.length <= OUTPUT_TRUNCATE) {
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
return `${text.slice(0, OUTPUT_TRUNCATE)}…[truncated]`;
|
|
21
|
+
}
|
|
22
|
+
function killProcessTree(child) {
|
|
23
|
+
try {
|
|
24
|
+
child.kill("SIGKILL");
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// already gone
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run one child with a wall-clock timeout. Does not inherit the caller's stdin.
|
|
32
|
+
*/
|
|
33
|
+
export function runChild(request) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
const [file, ...args] = request.argv;
|
|
36
|
+
if (file === undefined) {
|
|
37
|
+
resolve({ kind: "spawn_error", detail: "Empty command." });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let settled = false;
|
|
41
|
+
let timedOut = false;
|
|
42
|
+
let interrupted = false;
|
|
43
|
+
let stdout = "";
|
|
44
|
+
let stderr = "";
|
|
45
|
+
let timer;
|
|
46
|
+
let interruptPoll;
|
|
47
|
+
const finish = (outcome) => {
|
|
48
|
+
if (settled) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
settled = true;
|
|
52
|
+
if (timer !== undefined) {
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
}
|
|
55
|
+
if (interruptPoll !== undefined) {
|
|
56
|
+
clearInterval(interruptPoll);
|
|
57
|
+
}
|
|
58
|
+
resolve(outcome);
|
|
59
|
+
};
|
|
60
|
+
let child;
|
|
61
|
+
try {
|
|
62
|
+
child = spawn(file, args, {
|
|
63
|
+
cwd: request.cwd,
|
|
64
|
+
env: childEnv(request),
|
|
65
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
70
|
+
finish({ kind: "spawn_error", detail });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!child.stdout || !child.stderr) {
|
|
74
|
+
finish({ kind: "spawn_error", detail: "Child stdout/stderr pipes unavailable." });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
child.stdout.setEncoding("utf8");
|
|
78
|
+
child.stderr.setEncoding("utf8");
|
|
79
|
+
child.stdout.on("data", (chunk) => {
|
|
80
|
+
stdout += chunk;
|
|
81
|
+
});
|
|
82
|
+
child.stderr.on("data", (chunk) => {
|
|
83
|
+
stderr += chunk;
|
|
84
|
+
});
|
|
85
|
+
child.on("error", (error) => {
|
|
86
|
+
finish({ kind: "spawn_error", detail: error.message });
|
|
87
|
+
});
|
|
88
|
+
timer = setTimeout(() => {
|
|
89
|
+
timedOut = true;
|
|
90
|
+
killProcessTree(child);
|
|
91
|
+
}, Math.max(1, request.timeoutMs));
|
|
92
|
+
interruptPoll = setInterval(() => {
|
|
93
|
+
if (request.shouldInterrupt()) {
|
|
94
|
+
interrupted = true;
|
|
95
|
+
killProcessTree(child);
|
|
96
|
+
}
|
|
97
|
+
}, 20);
|
|
98
|
+
if (request.shouldInterrupt()) {
|
|
99
|
+
interrupted = true;
|
|
100
|
+
killProcessTree(child);
|
|
101
|
+
}
|
|
102
|
+
child.on("close", (exitCode, signal) => {
|
|
103
|
+
const out = truncate(stdout);
|
|
104
|
+
const err = truncate(stderr);
|
|
105
|
+
if (interrupted || request.shouldInterrupt()) {
|
|
106
|
+
finish({ kind: "interrupted", stdout: out, stderr: err });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (timedOut) {
|
|
110
|
+
finish({ kind: "timed_out", clock: request.timeoutClock, stdout: out, stderr: err });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
finish({ kind: "exited", exitCode, signal, stdout: out, stderr: err });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export function combinedOutput(stdout, stderr) {
|
|
118
|
+
const parts = [stdout, stderr].filter((p) => p.length > 0);
|
|
119
|
+
return truncate(parts.join("\n"));
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=run-child.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-child.js","sourceRoot":"","sources":["../../src/child/run-child.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type IsolationStop, IsolationStoppedError } from "@bluewombat/isolator";
|
|
2
|
+
/**
|
|
3
|
+
* What never leaves the Parent for a Child, unless a Project says otherwise:
|
|
4
|
+
* a secret in the clear has no business in a workspace an agent reads and a
|
|
5
|
+
* pull request may carry.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_EXCLUDES: readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* Copy working files from `from` into an existing `to` directory.
|
|
10
|
+
* Skips `.git` only at the root of `from` when `skipGitAtRoot` is set, and
|
|
11
|
+
* every path `exclude` names (see `isExcluded`).
|
|
12
|
+
*
|
|
13
|
+
* Each root entry is cloned by `cp` where the file system can — APFS, btrfs,
|
|
14
|
+
* XFS: same bytes on disk, no second copy — and copied by hand where it
|
|
15
|
+
* cannot. Excluded paths are then removed from the Child; a clone shares
|
|
16
|
+
* blocks that were already on the disk, so nothing was written in the clear
|
|
17
|
+
* that was not there before.
|
|
18
|
+
*/
|
|
19
|
+
export declare function copyWorkingFiles(input: {
|
|
20
|
+
from: string;
|
|
21
|
+
to: string;
|
|
22
|
+
skipGitAtRoot: boolean;
|
|
23
|
+
exclude?: readonly string[];
|
|
24
|
+
shouldStop: () => IsolationStop | undefined;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
export declare function wipeWorkingFiles(directory: string, shouldStop: () => IsolationStop | undefined): Promise<void>;
|
|
27
|
+
export { IsolationStoppedError };
|
|
28
|
+
/**
|
|
29
|
+
* Whether `path` (relative to the copied root, `/`-separated) is one a
|
|
30
|
+
* Project asked to leave behind.
|
|
31
|
+
*
|
|
32
|
+
* A pattern with no `/` is matched against the last segment, at any depth:
|
|
33
|
+
* `.env` leaves out `.env` and `packages/api/.env`. A pattern with a `/` is
|
|
34
|
+
* matched against the whole relative path from the root. `*` stands for any
|
|
35
|
+
* run of characters within one segment, `**` for anything, `/` included; a
|
|
36
|
+
* pattern opening with two stars and a slash means "at any depth", like a
|
|
37
|
+
* bare name does.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isExcluded(path: string, patterns: readonly string[]): boolean;
|
|
40
|
+
//# sourceMappingURL=copy-working-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-working-files.d.ts","sourceRoot":"","sources":["../src/copy-working-files.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,aAAa,EAAE,qBAAqB,EAAkB,MAAM,sBAAsB,CAAC;AAMjG;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAAuB,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,aAAa,GAAG,SAAS,CAAC;CAC7C,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBhB;AAED,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,aAAa,GAAG,SAAS,GAC1C,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,OAAO,EAAE,qBAAqB,EAAE,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAQ7E"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { copyFile, lstat, mkdir, readdir, readlink, rm, symlink } from "node:fs/promises";
|
|
2
|
+
import { platform } from "node:os";
|
|
3
|
+
import { basename, join, relative, sep } from "node:path";
|
|
4
|
+
import { IsolationStoppedError, throwIfStopped } from "@bluewombat/isolator";
|
|
5
|
+
import { runChild } from "./child/run-child.js";
|
|
6
|
+
/** How long one `cp` of one root entry may take before the walk takes over. */
|
|
7
|
+
const CLONE_TIMEOUT_MS = 120_000;
|
|
8
|
+
/**
|
|
9
|
+
* What never leaves the Parent for a Child, unless a Project says otherwise:
|
|
10
|
+
* a secret in the clear has no business in a workspace an agent reads and a
|
|
11
|
+
* pull request may carry.
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_EXCLUDES = [".env", ".env.*"];
|
|
14
|
+
/**
|
|
15
|
+
* Copy working files from `from` into an existing `to` directory.
|
|
16
|
+
* Skips `.git` only at the root of `from` when `skipGitAtRoot` is set, and
|
|
17
|
+
* every path `exclude` names (see `isExcluded`).
|
|
18
|
+
*
|
|
19
|
+
* Each root entry is cloned by `cp` where the file system can — APFS, btrfs,
|
|
20
|
+
* XFS: same bytes on disk, no second copy — and copied by hand where it
|
|
21
|
+
* cannot. Excluded paths are then removed from the Child; a clone shares
|
|
22
|
+
* blocks that were already on the disk, so nothing was written in the clear
|
|
23
|
+
* that was not there before.
|
|
24
|
+
*/
|
|
25
|
+
export async function copyWorkingFiles(input) {
|
|
26
|
+
const exclude = input.exclude ?? DEFAULT_EXCLUDES;
|
|
27
|
+
await throwIfStopped(input.shouldStop);
|
|
28
|
+
const entries = await readdir(input.from, { withFileTypes: true });
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
await throwIfStopped(input.shouldStop);
|
|
31
|
+
if (input.skipGitAtRoot && entry.name === ".git") {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (isExcluded(entry.name, exclude)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const src = join(input.from, entry.name);
|
|
38
|
+
const dest = join(input.to, entry.name);
|
|
39
|
+
const cloned = await clone(src, dest, input.shouldStop);
|
|
40
|
+
if (!cloned) {
|
|
41
|
+
await rm(dest, { recursive: true, force: true });
|
|
42
|
+
await copyEntry(src, dest, entry, input.shouldStop);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
await pruneExcluded(input.to, input.to, exclude, input.shouldStop);
|
|
46
|
+
}
|
|
47
|
+
export async function wipeWorkingFiles(directory, shouldStop) {
|
|
48
|
+
await throwIfStopped(shouldStop);
|
|
49
|
+
const entries = await readdir(directory);
|
|
50
|
+
for (const name of entries) {
|
|
51
|
+
await throwIfStopped(shouldStop);
|
|
52
|
+
if (name === ".git") {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
await rm(join(directory, name), { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export { IsolationStoppedError };
|
|
59
|
+
/**
|
|
60
|
+
* Whether `path` (relative to the copied root, `/`-separated) is one a
|
|
61
|
+
* Project asked to leave behind.
|
|
62
|
+
*
|
|
63
|
+
* A pattern with no `/` is matched against the last segment, at any depth:
|
|
64
|
+
* `.env` leaves out `.env` and `packages/api/.env`. A pattern with a `/` is
|
|
65
|
+
* matched against the whole relative path from the root. `*` stands for any
|
|
66
|
+
* run of characters within one segment, `**` for anything, `/` included; a
|
|
67
|
+
* pattern opening with two stars and a slash means "at any depth", like a
|
|
68
|
+
* bare name does.
|
|
69
|
+
*/
|
|
70
|
+
export function isExcluded(path, patterns) {
|
|
71
|
+
const normalized = path.split(sep).join("/");
|
|
72
|
+
return patterns.some((pattern) => {
|
|
73
|
+
// A leading `**/` says "at any depth", which is what a bare name already means.
|
|
74
|
+
const bare = pattern.startsWith("**/") ? pattern.slice(3) : pattern;
|
|
75
|
+
const subject = bare.includes("/") ? normalized : basename(normalized);
|
|
76
|
+
return toRegExp(bare).test(subject);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function toRegExp(pattern) {
|
|
80
|
+
const source = pattern
|
|
81
|
+
.split("**")
|
|
82
|
+
.map((part) => part
|
|
83
|
+
.split("*")
|
|
84
|
+
.map((literal) => literal.replace(/[.+?^${}()|[\]\\]/g, "\\$&"))
|
|
85
|
+
.join("[^/]*"))
|
|
86
|
+
.join(".*");
|
|
87
|
+
return new RegExp(`^${source}$`);
|
|
88
|
+
}
|
|
89
|
+
async function clone(src, dest, shouldStop) {
|
|
90
|
+
const flag = platform() === "darwin" ? "-c" : "--reflink=auto";
|
|
91
|
+
const outcome = await runChild({
|
|
92
|
+
argv: ["cp", "-R", "-P", flag, src, dest],
|
|
93
|
+
timeoutMs: CLONE_TIMEOUT_MS,
|
|
94
|
+
timeoutClock: "isolation",
|
|
95
|
+
shouldInterrupt: () => shouldStop() === "interrupt",
|
|
96
|
+
});
|
|
97
|
+
if (outcome.kind === "interrupted") {
|
|
98
|
+
throw new IsolationStoppedError("interrupt");
|
|
99
|
+
}
|
|
100
|
+
return outcome.kind === "exited" && outcome.exitCode === 0;
|
|
101
|
+
}
|
|
102
|
+
async function copyEntry(src, dest, entry, shouldStop) {
|
|
103
|
+
await throwIfStopped(shouldStop);
|
|
104
|
+
if (entry.isDirectory()) {
|
|
105
|
+
await mkdir(dest);
|
|
106
|
+
const entries = await readdir(src, { withFileTypes: true });
|
|
107
|
+
for (const child of entries) {
|
|
108
|
+
await copyEntry(join(src, child.name), join(dest, child.name), child, shouldStop);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (entry.isSymbolicLink()) {
|
|
113
|
+
const target = await readlink(src);
|
|
114
|
+
await symlink(target, dest);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (entry.isFile()) {
|
|
118
|
+
await copyFile(src, dest);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`Cannot isolate "${src}": unsupported file type.`);
|
|
122
|
+
}
|
|
123
|
+
/** Remove from `directory` every path below `root` that `patterns` name. Never follows a link. */
|
|
124
|
+
async function pruneExcluded(root, directory, patterns, shouldStop) {
|
|
125
|
+
if (patterns.length === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
await throwIfStopped(shouldStop);
|
|
129
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
const path = join(directory, entry.name);
|
|
132
|
+
if (directory === root && entry.name === ".git") {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (isExcluded(relative(root, path), patterns)) {
|
|
136
|
+
await rm(path, { recursive: true, force: true });
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (entry.isDirectory() && !(await lstat(path)).isSymbolicLink()) {
|
|
140
|
+
await pruneExcluded(root, path, patterns, shouldStop);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=copy-working-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-working-files.js","sourceRoot":"","sources":["../src/copy-working-files.ts"],"names":[],"mappings":"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"}
|
package/dist/fold.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fold.d.ts","sourceRoot":"","sources":["../src/fold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG1D;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,WAarB,CAAC"}
|
package/dist/fold.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { gitMerge } from "./git-merge.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fold by three-way git merge. Never forced.
|
|
4
|
+
*/
|
|
5
|
+
export const gitFold = {
|
|
6
|
+
async fold(input) {
|
|
7
|
+
return await gitMerge({
|
|
8
|
+
parent: input.parent,
|
|
9
|
+
child: input.child,
|
|
10
|
+
...(input.id === undefined ? {} : { id: input.id }),
|
|
11
|
+
...(input.subject === undefined ? {} : { subject: input.subject }),
|
|
12
|
+
...(input.mergeSubject === undefined ? {} : { mergeSubject: input.mergeSubject }),
|
|
13
|
+
deadlineMs: input.deadlineMs,
|
|
14
|
+
now: input.now,
|
|
15
|
+
shouldInterrupt: input.shouldInterrupt,
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=fold.js.map
|
package/dist/fold.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fold.js","sourceRoot":"","sources":["../src/fold.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAgB;IAClC,KAAK,CAAC,IAAI,CAAC,KAAK;QACd,OAAO,MAAM,QAAQ,CAAC;YACpB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YACnD,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YAClE,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;YACjF,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,eAAe,EAAE,KAAK,CAAC,eAAe;SACvC,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type FoldStop } from "@bluewombat/integrator";
|
|
2
|
+
export type GitMergeResult = {
|
|
3
|
+
ok: true;
|
|
4
|
+
} | {
|
|
5
|
+
ok: false;
|
|
6
|
+
stop: FoldStop;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
conflict: true;
|
|
10
|
+
report: string;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
detail: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Three-way git merge of Child working files into Parent. Never forced.
|
|
17
|
+
* Does not fall back to copy.
|
|
18
|
+
*
|
|
19
|
+
* History is left only where something happened: a side with nothing loose
|
|
20
|
+
* gets no snapshot commit, a Parent that never moved is fast-forwarded, and a
|
|
21
|
+
* merge commit exists only when both sides moved. What is written is named
|
|
22
|
+
* after the work (`subject`), not after this mechanism.
|
|
23
|
+
*/
|
|
24
|
+
export declare function gitMerge(input: {
|
|
25
|
+
parent: string;
|
|
26
|
+
child: string;
|
|
27
|
+
/** Names this Integration in what it writes, so history says what was folded. */
|
|
28
|
+
id?: string;
|
|
29
|
+
/** What the folded work is; the commit that carries it says this. */
|
|
30
|
+
subject?: string;
|
|
31
|
+
/** What a merge commit says, when both sides moved. */
|
|
32
|
+
mergeSubject?: string;
|
|
33
|
+
deadlineMs: number;
|
|
34
|
+
now: () => number;
|
|
35
|
+
shouldInterrupt: () => boolean;
|
|
36
|
+
}): Promise<GitMergeResult>;
|
|
37
|
+
//# sourceMappingURL=git-merge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-merge.d.ts","sourceRoot":"","sources":["../src/git-merge.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,QAAQ,EAA2C,MAAM,wBAAwB,CAAC;AAOhG,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAwFlC;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,OAAO,CAAC;CAChC,GAAG,OAAO,CAAC,cAAc,CAAC,CAmO1B"}
|