@ricsam/r5d-worker 0.0.75 → 0.0.76
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/cjs/main.cjs +9 -0
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/project-worktrees.cjs +63 -9
- package/dist/mjs/main.mjs +10 -0
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/project-worktrees.mjs +61 -9
- package/dist/types/project-worktrees.d.ts +11 -0
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -2290,6 +2290,15 @@ async function startWorker(options) {
|
|
|
2290
2290
|
`);
|
|
2291
2291
|
process.stdout.write(`[r5d-worker] server: ${baseUrl}
|
|
2292
2292
|
`);
|
|
2293
|
+
const snapshotCleanup = (0, import_project_worktrees.cleanupStaleProjectWorktreeSnapshots)();
|
|
2294
|
+
if (snapshotCleanup.removed.length > 0) {
|
|
2295
|
+
process.stdout.write(`[r5d-worker] removed ${snapshotCleanup.removed.length} stale project snapshot(s)
|
|
2296
|
+
`);
|
|
2297
|
+
}
|
|
2298
|
+
for (const failure of snapshotCleanup.failed) {
|
|
2299
|
+
process.stderr.write(`[r5d-worker] failed to remove stale project snapshot ${failure.path}: ${failure.error}
|
|
2300
|
+
`);
|
|
2301
|
+
}
|
|
2293
2302
|
runGit(["--version"]);
|
|
2294
2303
|
await verifyR5dctlAuth(baseUrl, token);
|
|
2295
2304
|
import_node_fs.default.mkdirSync(projectsRoot, { recursive: true });
|
package/dist/cjs/package.json
CHANGED
|
@@ -28,6 +28,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var project_worktrees_exports = {};
|
|
30
30
|
__export(project_worktrees_exports, {
|
|
31
|
+
PROJECT_WORKTREE_SNAPSHOT_PREFIX: () => PROJECT_WORKTREE_SNAPSHOT_PREFIX,
|
|
32
|
+
cleanupStaleProjectWorktreeSnapshots: () => cleanupStaleProjectWorktreeSnapshots,
|
|
31
33
|
createLinkedProjectBranch: () => createLinkedProjectBranch,
|
|
32
34
|
deleteLinkedProjectBranch: () => deleteLinkedProjectBranch,
|
|
33
35
|
deleteProjectMirrorBranch: () => deleteProjectMirrorBranch,
|
|
@@ -47,6 +49,10 @@ var import_node_os = __toESM(require("node:os"), 1);
|
|
|
47
49
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
48
50
|
var import_managed_paths = require("./managed-paths.cjs");
|
|
49
51
|
var import_working_tree_mirror = require("./working-tree-mirror.cjs");
|
|
52
|
+
const PROJECT_WORKTREE_SNAPSHOT_PREFIX = "r5d-project-worktrees-";
|
|
53
|
+
const PROJECT_WORKTREE_SNAPSHOT_NAME = new RegExp(
|
|
54
|
+
`^${PROJECT_WORKTREE_SNAPSHOT_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([1-9]\\d*)-([A-Za-z0-9]{6})$`
|
|
55
|
+
);
|
|
50
56
|
function projectWorktreeConfigurationFingerprint(input) {
|
|
51
57
|
return (0, import_node_crypto.createHash)("sha256").update(
|
|
52
58
|
JSON.stringify({
|
|
@@ -167,17 +173,63 @@ function hasCompatibleLinkedProjectWorktreeLayout(input) {
|
|
|
167
173
|
return kind === null || kind === "file" && commonGitDirectory(checkoutPath) === primaryCommonDir;
|
|
168
174
|
});
|
|
169
175
|
}
|
|
176
|
+
function createProjectWorktreeSnapshotRoot() {
|
|
177
|
+
return import_node_fs.default.mkdtempSync(import_node_path.default.join(import_node_os.default.tmpdir(), `${PROJECT_WORKTREE_SNAPSHOT_PREFIX}${process.pid}-`));
|
|
178
|
+
}
|
|
170
179
|
function snapshotBranchTrees(projectRoot, branches) {
|
|
171
|
-
const root =
|
|
180
|
+
const root = createProjectWorktreeSnapshotRoot();
|
|
172
181
|
const paths = /* @__PURE__ */ new Map();
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
182
|
+
try {
|
|
183
|
+
for (const { branchName } of branches) {
|
|
184
|
+
const checkoutPath = branchPath(projectRoot, branchName);
|
|
185
|
+
if (!import_node_fs.default.existsSync(checkoutPath)) continue;
|
|
186
|
+
const snapshotPath = import_node_path.default.join(root, ...branchName.split("/"));
|
|
187
|
+
(0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: checkoutPath, targetRoot: snapshotPath, sourceMode: "all", deletionMode: "all" });
|
|
188
|
+
paths.set(branchName, snapshotPath);
|
|
189
|
+
}
|
|
190
|
+
return { root, paths };
|
|
191
|
+
} catch (error) {
|
|
192
|
+
import_node_fs.default.rmSync(root, { recursive: true, force: true });
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function isProcessAlive(processId) {
|
|
197
|
+
try {
|
|
198
|
+
process.kill(processId, 0);
|
|
199
|
+
return true;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
return error.code !== "ESRCH";
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function cleanupStaleProjectWorktreeSnapshots(input = {}) {
|
|
205
|
+
const temporaryRoot = import_node_path.default.resolve(input.temporaryRoot ?? import_node_os.default.tmpdir());
|
|
206
|
+
const processAlive = input.processAlive ?? isProcessAlive;
|
|
207
|
+
const removed = [];
|
|
208
|
+
const failed = [];
|
|
209
|
+
let entries;
|
|
210
|
+
try {
|
|
211
|
+
entries = import_node_fs.default.readdirSync(temporaryRoot, { withFileTypes: true });
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (error.code === "ENOENT") return { removed, failed };
|
|
214
|
+
return { removed, failed: [{ path: temporaryRoot, error: error instanceof Error ? error.message : String(error) }] };
|
|
215
|
+
}
|
|
216
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
217
|
+
const match = PROJECT_WORKTREE_SNAPSHOT_NAME.exec(entry.name);
|
|
218
|
+
if (!match) continue;
|
|
219
|
+
const ownerProcessId = Number(match[1]);
|
|
220
|
+
if (!Number.isSafeInteger(ownerProcessId) || processAlive(ownerProcessId)) continue;
|
|
221
|
+
const candidate = import_node_path.default.join(temporaryRoot, entry.name);
|
|
222
|
+
try {
|
|
223
|
+
const stat = import_node_fs.default.lstatSync(candidate);
|
|
224
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
|
|
225
|
+
import_node_fs.default.rmSync(candidate, { recursive: true, force: true });
|
|
226
|
+
removed.push(candidate);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (error.code === "ENOENT") continue;
|
|
229
|
+
failed.push({ path: candidate, error: error instanceof Error ? error.message : String(error) });
|
|
230
|
+
}
|
|
179
231
|
}
|
|
180
|
-
return {
|
|
232
|
+
return { removed, failed };
|
|
181
233
|
}
|
|
182
234
|
function configureRepository(input) {
|
|
183
235
|
if (tryGit(input.primaryPath, ["remote", "get-url", "origin"])) {
|
|
@@ -373,7 +425,7 @@ function createLinkedProjectBranch(input) {
|
|
|
373
425
|
});
|
|
374
426
|
if (overlappingWorktree) throw new Error(`Project branch folder overlaps linked worktree ${overlappingWorktree}`);
|
|
375
427
|
const sourceHead = git(sourcePath, ["rev-parse", "HEAD"], "resolve source branch head");
|
|
376
|
-
const snapshotRoot =
|
|
428
|
+
const snapshotRoot = createProjectWorktreeSnapshotRoot();
|
|
377
429
|
try {
|
|
378
430
|
(0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: sourcePath, targetRoot: snapshotRoot, sourceMode: "git", deletionMode: "all" });
|
|
379
431
|
git(sourcePath, ["branch", input.branchName, sourceHead], `create project branch ${input.branchName}`);
|
|
@@ -491,6 +543,8 @@ const projectWorktreesTestHarness = {
|
|
|
491
543
|
};
|
|
492
544
|
// Annotate the CommonJS export names for ESM import in node:
|
|
493
545
|
0 && (module.exports = {
|
|
546
|
+
PROJECT_WORKTREE_SNAPSHOT_PREFIX,
|
|
547
|
+
cleanupStaleProjectWorktreeSnapshots,
|
|
494
548
|
createLinkedProjectBranch,
|
|
495
549
|
deleteLinkedProjectBranch,
|
|
496
550
|
deleteProjectMirrorBranch,
|
package/dist/mjs/main.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "./supervisor.mjs";
|
|
27
27
|
import { managedProjectRoot, validateManagedBranchName } from "./managed-paths.mjs";
|
|
28
28
|
import {
|
|
29
|
+
cleanupStaleProjectWorktreeSnapshots,
|
|
29
30
|
createLinkedProjectBranch,
|
|
30
31
|
deleteLinkedProjectBranch,
|
|
31
32
|
deleteProjectMirrorBranch,
|
|
@@ -2266,6 +2267,15 @@ async function startWorker(options) {
|
|
|
2266
2267
|
`);
|
|
2267
2268
|
process.stdout.write(`[r5d-worker] server: ${baseUrl}
|
|
2268
2269
|
`);
|
|
2270
|
+
const snapshotCleanup = cleanupStaleProjectWorktreeSnapshots();
|
|
2271
|
+
if (snapshotCleanup.removed.length > 0) {
|
|
2272
|
+
process.stdout.write(`[r5d-worker] removed ${snapshotCleanup.removed.length} stale project snapshot(s)
|
|
2273
|
+
`);
|
|
2274
|
+
}
|
|
2275
|
+
for (const failure of snapshotCleanup.failed) {
|
|
2276
|
+
process.stderr.write(`[r5d-worker] failed to remove stale project snapshot ${failure.path}: ${failure.error}
|
|
2277
|
+
`);
|
|
2278
|
+
}
|
|
2269
2279
|
runGit(["--version"]);
|
|
2270
2280
|
await verifyR5dctlAuth(baseUrl, token);
|
|
2271
2281
|
fs.mkdirSync(projectsRoot, { recursive: true });
|
package/dist/mjs/package.json
CHANGED
|
@@ -4,6 +4,10 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { validateManagedBranchName } from "./managed-paths.mjs";
|
|
6
6
|
import { mirrorWorkingTree } from "./working-tree-mirror.mjs";
|
|
7
|
+
const PROJECT_WORKTREE_SNAPSHOT_PREFIX = "r5d-project-worktrees-";
|
|
8
|
+
const PROJECT_WORKTREE_SNAPSHOT_NAME = new RegExp(
|
|
9
|
+
`^${PROJECT_WORKTREE_SNAPSHOT_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([1-9]\\d*)-([A-Za-z0-9]{6})$`
|
|
10
|
+
);
|
|
7
11
|
function projectWorktreeConfigurationFingerprint(input) {
|
|
8
12
|
return createHash("sha256").update(
|
|
9
13
|
JSON.stringify({
|
|
@@ -124,17 +128,63 @@ function hasCompatibleLinkedProjectWorktreeLayout(input) {
|
|
|
124
128
|
return kind === null || kind === "file" && commonGitDirectory(checkoutPath) === primaryCommonDir;
|
|
125
129
|
});
|
|
126
130
|
}
|
|
131
|
+
function createProjectWorktreeSnapshotRoot() {
|
|
132
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), `${PROJECT_WORKTREE_SNAPSHOT_PREFIX}${process.pid}-`));
|
|
133
|
+
}
|
|
127
134
|
function snapshotBranchTrees(projectRoot, branches) {
|
|
128
|
-
const root =
|
|
135
|
+
const root = createProjectWorktreeSnapshotRoot();
|
|
129
136
|
const paths = /* @__PURE__ */ new Map();
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
137
|
+
try {
|
|
138
|
+
for (const { branchName } of branches) {
|
|
139
|
+
const checkoutPath = branchPath(projectRoot, branchName);
|
|
140
|
+
if (!fs.existsSync(checkoutPath)) continue;
|
|
141
|
+
const snapshotPath = path.join(root, ...branchName.split("/"));
|
|
142
|
+
mirrorWorkingTree({ sourceRoot: checkoutPath, targetRoot: snapshotPath, sourceMode: "all", deletionMode: "all" });
|
|
143
|
+
paths.set(branchName, snapshotPath);
|
|
144
|
+
}
|
|
145
|
+
return { root, paths };
|
|
146
|
+
} catch (error) {
|
|
147
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function isProcessAlive(processId) {
|
|
152
|
+
try {
|
|
153
|
+
process.kill(processId, 0);
|
|
154
|
+
return true;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return error.code !== "ESRCH";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function cleanupStaleProjectWorktreeSnapshots(input = {}) {
|
|
160
|
+
const temporaryRoot = path.resolve(input.temporaryRoot ?? os.tmpdir());
|
|
161
|
+
const processAlive = input.processAlive ?? isProcessAlive;
|
|
162
|
+
const removed = [];
|
|
163
|
+
const failed = [];
|
|
164
|
+
let entries;
|
|
165
|
+
try {
|
|
166
|
+
entries = fs.readdirSync(temporaryRoot, { withFileTypes: true });
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (error.code === "ENOENT") return { removed, failed };
|
|
169
|
+
return { removed, failed: [{ path: temporaryRoot, error: error instanceof Error ? error.message : String(error) }] };
|
|
170
|
+
}
|
|
171
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
172
|
+
const match = PROJECT_WORKTREE_SNAPSHOT_NAME.exec(entry.name);
|
|
173
|
+
if (!match) continue;
|
|
174
|
+
const ownerProcessId = Number(match[1]);
|
|
175
|
+
if (!Number.isSafeInteger(ownerProcessId) || processAlive(ownerProcessId)) continue;
|
|
176
|
+
const candidate = path.join(temporaryRoot, entry.name);
|
|
177
|
+
try {
|
|
178
|
+
const stat = fs.lstatSync(candidate);
|
|
179
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) continue;
|
|
180
|
+
fs.rmSync(candidate, { recursive: true, force: true });
|
|
181
|
+
removed.push(candidate);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (error.code === "ENOENT") continue;
|
|
184
|
+
failed.push({ path: candidate, error: error instanceof Error ? error.message : String(error) });
|
|
185
|
+
}
|
|
136
186
|
}
|
|
137
|
-
return {
|
|
187
|
+
return { removed, failed };
|
|
138
188
|
}
|
|
139
189
|
function configureRepository(input) {
|
|
140
190
|
if (tryGit(input.primaryPath, ["remote", "get-url", "origin"])) {
|
|
@@ -330,7 +380,7 @@ function createLinkedProjectBranch(input) {
|
|
|
330
380
|
});
|
|
331
381
|
if (overlappingWorktree) throw new Error(`Project branch folder overlaps linked worktree ${overlappingWorktree}`);
|
|
332
382
|
const sourceHead = git(sourcePath, ["rev-parse", "HEAD"], "resolve source branch head");
|
|
333
|
-
const snapshotRoot =
|
|
383
|
+
const snapshotRoot = createProjectWorktreeSnapshotRoot();
|
|
334
384
|
try {
|
|
335
385
|
mirrorWorkingTree({ sourceRoot: sourcePath, targetRoot: snapshotRoot, sourceMode: "git", deletionMode: "all" });
|
|
336
386
|
git(sourcePath, ["branch", input.branchName, sourceHead], `create project branch ${input.branchName}`);
|
|
@@ -447,6 +497,8 @@ const projectWorktreesTestHarness = {
|
|
|
447
497
|
commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG, ...authArgs(auth), ...args]
|
|
448
498
|
};
|
|
449
499
|
export {
|
|
500
|
+
PROJECT_WORKTREE_SNAPSHOT_PREFIX,
|
|
501
|
+
cleanupStaleProjectWorktreeSnapshots,
|
|
450
502
|
createLinkedProjectBranch,
|
|
451
503
|
deleteLinkedProjectBranch,
|
|
452
504
|
deleteProjectMirrorBranch,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const PROJECT_WORKTREE_SNAPSHOT_PREFIX = "r5d-project-worktrees-";
|
|
1
2
|
export type GitHttpAuth = {
|
|
2
3
|
extraHeaderUrl: string;
|
|
3
4
|
header: string;
|
|
@@ -32,6 +33,16 @@ export declare function hasLinkedProjectWorktreeLayout(input: {
|
|
|
32
33
|
primaryBranchName: string;
|
|
33
34
|
branches: readonly Pick<ProjectWorktreeBranch, "branchName">[];
|
|
34
35
|
}): boolean;
|
|
36
|
+
export declare function cleanupStaleProjectWorktreeSnapshots(input?: {
|
|
37
|
+
temporaryRoot?: string;
|
|
38
|
+
processAlive?: (processId: number) => boolean;
|
|
39
|
+
}): {
|
|
40
|
+
removed: string[];
|
|
41
|
+
failed: Array<{
|
|
42
|
+
path: string;
|
|
43
|
+
error: string;
|
|
44
|
+
}>;
|
|
45
|
+
};
|
|
35
46
|
export declare function ensureProjectWorktrees(input: {
|
|
36
47
|
projectRoot: string;
|
|
37
48
|
primaryBranchName: string;
|