@h-rig/repos-plugin 0.0.6-alpha.157 → 0.0.6-alpha.159
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/src/index.d.ts +1 -0
- package/dist/src/index.js +507 -81
- package/dist/src/layout.js +1 -1
- package/dist/src/mirror/bootstrap.js +2 -2
- package/dist/src/mirror/refresh.js +2 -2
- package/dist/src/mirror/state.js +2 -2
- package/dist/src/plugin.d.ts +2 -2
- package/dist/src/plugin.js +516 -71
- package/dist/src/registry.js +2 -2
- package/dist/src/repo-operations.d.ts +3 -0
- package/dist/src/repo-operations.js +871 -0
- package/dist/src/service.d.ts +1 -1
- package/dist/src/service.js +4 -4
- package/package.json +3 -4
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
-
|
|
18
2
|
// packages/repos-plugin/src/registry.ts
|
|
19
3
|
function createRepoRegistry(entries) {
|
|
20
4
|
const map = new Map;
|
|
@@ -29,6 +13,7 @@ function createRepoRegistry(entries) {
|
|
|
29
13
|
list: () => ordered
|
|
30
14
|
};
|
|
31
15
|
}
|
|
16
|
+
var MANAGED_REPOS = new Map;
|
|
32
17
|
function setManagedRepos(entries) {
|
|
33
18
|
const next = new Map;
|
|
34
19
|
for (const e of entries) {
|
|
@@ -75,19 +60,14 @@ function repoRegistrationToManagedEntry(reg) {
|
|
|
75
60
|
alias: reg.defaultPath ?? reg.id,
|
|
76
61
|
defaultBranch: reg.defaultBranch,
|
|
77
62
|
defaultRemoteUrl: reg.url,
|
|
78
|
-
remoteEnvVar: reg.remoteEnvVar,
|
|
79
|
-
checkoutEnvVar: reg.checkoutEnvVar
|
|
63
|
+
...reg.remoteEnvVar !== undefined ? { remoteEnvVar: reg.remoteEnvVar } : {},
|
|
64
|
+
...reg.checkoutEnvVar !== undefined ? { checkoutEnvVar: reg.checkoutEnvVar } : {}
|
|
80
65
|
};
|
|
81
66
|
}
|
|
82
|
-
var MANAGED_REPOS;
|
|
83
|
-
var init_registry = __esm(() => {
|
|
84
|
-
MANAGED_REPOS = new Map;
|
|
85
|
-
});
|
|
86
|
-
|
|
87
67
|
// packages/repos-plugin/src/layout.ts
|
|
88
68
|
import { existsSync } from "fs";
|
|
89
69
|
import { basename, dirname, join, resolve } from "path";
|
|
90
|
-
import { resolveMonorepoRoot } from "@rig/
|
|
70
|
+
import { resolveMonorepoRoot } from "@rig/core/layout";
|
|
91
71
|
function resolveRepoStateDir(projectRoot) {
|
|
92
72
|
const normalizedProjectRoot = resolve(projectRoot);
|
|
93
73
|
const projectParent = dirname(normalizedProjectRoot);
|
|
@@ -138,12 +118,9 @@ function resolveMonorepoRepoLayout(projectRoot) {
|
|
|
138
118
|
const primary = entries[0];
|
|
139
119
|
return resolveManagedRepoLayout(projectRoot, primary.id);
|
|
140
120
|
}
|
|
141
|
-
var init_layout = __esm(() => {
|
|
142
|
-
init_registry();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
121
|
// packages/repos-plugin/src/mirror/state.ts
|
|
146
|
-
import { readAuthorityProjectStateJson, writeAuthorityProjectStateJson } from "@rig/
|
|
122
|
+
import { readAuthorityProjectStateJson, writeAuthorityProjectStateJson } from "@rig/core/json-files";
|
|
123
|
+
var STATE_VERSION = 1;
|
|
147
124
|
function defaultMirrorState(projectRoot, repoId) {
|
|
148
125
|
const layout = resolveManagedRepoLayout(projectRoot, repoId);
|
|
149
126
|
return {
|
|
@@ -169,11 +146,6 @@ function writeManagedRepoMirrorState(projectRoot, repoId, patch) {
|
|
|
169
146
|
writeAuthorityProjectStateJson(projectRoot, layout.mirrorStateRelativePath, next);
|
|
170
147
|
return next;
|
|
171
148
|
}
|
|
172
|
-
var STATE_VERSION = 1;
|
|
173
|
-
var init_state = __esm(() => {
|
|
174
|
-
init_layout();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
149
|
// packages/repos-plugin/src/mirror/bootstrap.ts
|
|
178
150
|
import { existsSync as existsSync2, mkdirSync, realpathSync } from "fs";
|
|
179
151
|
import { resolve as resolve2 } from "path";
|
|
@@ -275,12 +247,6 @@ function ensureManagedRepoMirror(projectRoot, repoId) {
|
|
|
275
247
|
});
|
|
276
248
|
return layout;
|
|
277
249
|
}
|
|
278
|
-
var init_bootstrap = __esm(() => {
|
|
279
|
-
init_registry();
|
|
280
|
-
init_layout();
|
|
281
|
-
init_state();
|
|
282
|
-
});
|
|
283
|
-
|
|
284
250
|
// packages/repos-plugin/src/mirror/refresh.ts
|
|
285
251
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, realpathSync as realpathSync2, rmSync } from "fs";
|
|
286
252
|
import { resolve as resolve3 } from "path";
|
|
@@ -407,58 +373,516 @@ function syncManagedRepo(projectRoot, repoId) {
|
|
|
407
373
|
}
|
|
408
374
|
return { layout, headCommit };
|
|
409
375
|
}
|
|
410
|
-
var init_refresh = __esm(() => {
|
|
411
|
-
init_bootstrap();
|
|
412
|
-
init_state();
|
|
413
|
-
});
|
|
414
|
-
|
|
415
376
|
// packages/repos-plugin/src/service.ts
|
|
416
|
-
var
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
377
|
+
var managedRepoService = {
|
|
378
|
+
setManagedRepos,
|
|
379
|
+
listManagedRepoEntries,
|
|
380
|
+
resolveManagedRepoIdByAlias,
|
|
381
|
+
repoRegistrationToManagedEntry,
|
|
382
|
+
createRepoRegistry,
|
|
383
|
+
resolveManagedRepoLayoutByAlias,
|
|
384
|
+
resolveMonorepoRepoLayout,
|
|
385
|
+
syncManagedRepo
|
|
386
|
+
};
|
|
387
|
+
var svc = managedRepoService;
|
|
388
|
+
// packages/repos-plugin/src/repo-operations.ts
|
|
389
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync, readdirSync, rmSync as rmSync2, writeFileSync } from "fs";
|
|
390
|
+
import { basename as basename2, dirname as dirname2, resolve as resolve4 } from "path";
|
|
391
|
+
import { CliError, TASK_DATA_SERVICE_CAPABILITY } from "@rig/contracts";
|
|
392
|
+
import { defineCapability } from "@rig/core/capability";
|
|
393
|
+
import { requireInstalledCapability } from "@rig/core/capability-loaders";
|
|
394
|
+
import { loadRuntimeContext } from "@rig/core/runtime-context";
|
|
395
|
+
import { resolveHarnessPaths } from "@rig/core/harness-paths";
|
|
396
|
+
import { nowIso as nowIso3, readJsonFile, runCapture } from "@rig/core/exec";
|
|
397
|
+
var TaskDataCap = defineCapability(TASK_DATA_SERVICE_CAPABILITY);
|
|
398
|
+
var taskData = () => requireInstalledCapability(TaskDataCap, "task-data capability unavailable: load @rig/task-sources-plugin (default bundle) before resolving task repos.");
|
|
399
|
+
function managedRepoEntries() {
|
|
400
|
+
return managedRepoService.listManagedRepoEntries();
|
|
401
|
+
}
|
|
402
|
+
function primaryManagedRepoId() {
|
|
403
|
+
const entries = managedRepoEntries();
|
|
404
|
+
return entries.length > 0 ? entries[0].id : null;
|
|
405
|
+
}
|
|
406
|
+
function primaryManagedRepoAlias() {
|
|
407
|
+
const entries = managedRepoEntries();
|
|
408
|
+
return entries.length > 0 ? entries[0].alias : null;
|
|
409
|
+
}
|
|
410
|
+
function ensureMonorepoReady(projectRoot) {
|
|
411
|
+
const id = primaryManagedRepoId();
|
|
412
|
+
if (!id) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
const synced = managedRepoService.syncManagedRepo(projectRoot, id);
|
|
416
|
+
const sha = synced.headCommit.slice(0, 7);
|
|
417
|
+
console.log(`Monorepo ready: ${synced.layout.alias}@${sha}`);
|
|
418
|
+
return synced;
|
|
419
|
+
}
|
|
420
|
+
function repoEnsure(projectRoot, taskId) {
|
|
421
|
+
const monorepo = ensureMonorepoReady(projectRoot);
|
|
422
|
+
const resolvedTask = taskId || taskData().currentTaskId(projectRoot);
|
|
423
|
+
if (!resolvedTask) {
|
|
424
|
+
if (monorepo) {
|
|
425
|
+
const alias = primaryManagedRepoAlias();
|
|
426
|
+
if (alias) {
|
|
427
|
+
persistBaselinePins(projectRoot, { [alias]: monorepo.headCommit });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
console.log("No active task. Refreshed baseline repo pins.");
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const pins = resolvedPins(projectRoot, resolvedTask);
|
|
434
|
+
applyPins(projectRoot, pins);
|
|
435
|
+
verifyPins(projectRoot, pins);
|
|
436
|
+
}
|
|
437
|
+
function repoPins(projectRoot, taskId) {
|
|
438
|
+
const resolvedTask = taskId || taskData().currentTaskId(projectRoot);
|
|
439
|
+
if (!resolvedTask) {
|
|
440
|
+
return {};
|
|
441
|
+
}
|
|
442
|
+
return resolvedPins(projectRoot, resolvedTask);
|
|
443
|
+
}
|
|
444
|
+
function repoVerify(projectRoot, taskId) {
|
|
445
|
+
const resolvedTask = taskId || taskData().currentTaskId(projectRoot);
|
|
446
|
+
if (!resolvedTask) {
|
|
447
|
+
console.log("No active task. Nothing to verify.");
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
const pins = resolvedPins(projectRoot, resolvedTask);
|
|
451
|
+
return verifyPins(projectRoot, pins);
|
|
452
|
+
}
|
|
453
|
+
function repoDiscover(projectRoot, taskId) {
|
|
454
|
+
const resolvedTask = taskId || taskData().currentTaskId(projectRoot);
|
|
455
|
+
if (!resolvedTask) {
|
|
456
|
+
return {};
|
|
457
|
+
}
|
|
458
|
+
const explicit = explicitPins(projectRoot, resolvedTask);
|
|
459
|
+
if (Object.keys(explicit).length > 0) {
|
|
460
|
+
return explicit;
|
|
461
|
+
}
|
|
462
|
+
return discoverPins(projectRoot, resolvedTask);
|
|
463
|
+
}
|
|
464
|
+
function repoBaseline(projectRoot, refresh = false) {
|
|
465
|
+
const paths = resolveRepoDiscoveryPaths(projectRoot);
|
|
466
|
+
if (!refresh && existsSync4(paths.baseRepoPinsPath)) {
|
|
467
|
+
const baseline = readJsonFile(paths.baseRepoPinsPath, { recorded_at: nowIso3(), repos: {} });
|
|
468
|
+
return baseline.repos || {};
|
|
469
|
+
}
|
|
470
|
+
const id = primaryManagedRepoId();
|
|
471
|
+
if (!id) {
|
|
472
|
+
return persistBaselinePins(projectRoot, {});
|
|
473
|
+
}
|
|
474
|
+
const synced = managedRepoService.syncManagedRepo(projectRoot, id);
|
|
475
|
+
const alias = primaryManagedRepoAlias() ?? id;
|
|
476
|
+
return persistBaselinePins(projectRoot, { [alias]: synced.headCommit });
|
|
477
|
+
}
|
|
478
|
+
function resetBaseline(projectRoot, keepTaskStatus) {
|
|
479
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
480
|
+
const label = primaryManagedRepoAlias() ?? "monorepo";
|
|
481
|
+
resetRepoToOriginMain(projectRoot, paths.monorepoRoot, label);
|
|
482
|
+
deleteBranches(projectRoot, paths.monorepoRoot, label, "rig/*");
|
|
483
|
+
deleteBranches(projectRoot, projectRoot, "project-rig", "codex/*");
|
|
484
|
+
repoBaseline(projectRoot, true);
|
|
485
|
+
console.log("OK: refreshed baseline repo pins");
|
|
486
|
+
if (!keepTaskStatus) {
|
|
487
|
+
reopenClosedTasks(projectRoot);
|
|
488
|
+
} else {
|
|
489
|
+
console.log("INFO: task status unchanged (--keep-task-status)");
|
|
490
|
+
}
|
|
491
|
+
clearDirectory(paths.artifactsDir, ".gitkeep");
|
|
492
|
+
clearDirectory(paths.logsDir, ".gitkeep");
|
|
493
|
+
for (const stateFile of ["hook_trips.log", "task-repo-commits.json", "current-task-id.txt"]) {
|
|
494
|
+
rmSync2(resolve4(paths.stateDir, stateFile), { force: true });
|
|
495
|
+
}
|
|
496
|
+
console.log("OK: cleared harness artifacts/logs/runtime state");
|
|
497
|
+
console.log("");
|
|
498
|
+
console.log("Baseline reset complete.");
|
|
499
|
+
}
|
|
500
|
+
function persistBaselinePins(projectRoot, repos) {
|
|
501
|
+
const paths = resolveRepoDiscoveryPaths(projectRoot);
|
|
502
|
+
mkdirSync3(resolve4(paths.baseRepoPinsPath, ".."), { recursive: true });
|
|
503
|
+
writeFileSync(paths.baseRepoPinsPath, `${JSON.stringify({ recorded_at: nowIso3(), repos }, null, 2)}
|
|
504
|
+
`, "utf-8");
|
|
505
|
+
return repos;
|
|
506
|
+
}
|
|
507
|
+
function resetRepoToOriginMain(projectRoot, repoPath, label) {
|
|
508
|
+
if (!existsSync4(resolve4(repoPath, ".git"))) {
|
|
509
|
+
console.log(`WARN: ${label} repo missing at ${repoPath}, skipping reset.`);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
runGitCapture(["git", "-C", repoPath, "fetch", "origin", "main"], projectRoot);
|
|
513
|
+
const hasMain = runGitCapture(["git", "-C", repoPath, "show-ref", "--verify", "--quiet", "refs/heads/main"], projectRoot).exitCode === 0;
|
|
514
|
+
if (hasMain) {
|
|
515
|
+
runGitCapture(["git", "-C", repoPath, "checkout", "main"], projectRoot);
|
|
516
|
+
} else {
|
|
517
|
+
runGitCapture(["git", "-C", repoPath, "checkout", "-B", "main", "origin/main"], projectRoot);
|
|
518
|
+
}
|
|
519
|
+
const resetResult = runGitCapture(["git", "-C", repoPath, "reset", "--hard", "origin/main"], projectRoot);
|
|
520
|
+
if (resetResult.exitCode !== 0) {
|
|
521
|
+
throw new Error(`Failed to reset ${label} to origin/main:
|
|
522
|
+
${resetResult.stderr || resetResult.stdout}`);
|
|
523
|
+
}
|
|
524
|
+
runGitCapture(["git", "-C", repoPath, "clean", "-fd"], projectRoot);
|
|
525
|
+
const head = runGitCapture(["git", "-C", repoPath, "rev-parse", "--short", "HEAD"], projectRoot).stdout.trim();
|
|
526
|
+
console.log(`OK: ${label} reset to origin/main (${head})`);
|
|
527
|
+
}
|
|
528
|
+
function deleteBranches(projectRoot, repoPath, label, pattern) {
|
|
529
|
+
if (!existsSync4(resolve4(repoPath, ".git"))) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const listed = runGitCapture(["git", "-C", repoPath, "for-each-ref", `refs/heads/${pattern}`, "--format=%(refname:short)"], projectRoot).stdout.split(/\r?\n/).filter(Boolean);
|
|
533
|
+
let deleted = 0;
|
|
534
|
+
for (const branch of listed) {
|
|
535
|
+
runGitCapture(["git", "-C", repoPath, "branch", "-D", branch], projectRoot);
|
|
536
|
+
deleted += 1;
|
|
537
|
+
}
|
|
538
|
+
console.log(`OK: ${label} deleted ${deleted} branch(es) matching ${pattern}`);
|
|
539
|
+
}
|
|
540
|
+
function reopenClosedTasks(projectRoot) {
|
|
541
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
542
|
+
const issuesPath = resolve4(paths.monorepoRoot, ".beads/issues.jsonl");
|
|
543
|
+
if (!existsSync4(issuesPath)) {
|
|
544
|
+
console.log("WARN: .beads/issues.jsonl not found, skipping task reopen.");
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const content = readFileSync(issuesPath, "utf-8");
|
|
548
|
+
const closed = [];
|
|
549
|
+
for (const line of content.split(/\r?\n/)) {
|
|
550
|
+
const trimmed = line.trim();
|
|
551
|
+
if (!trimmed) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
const issue = JSON.parse(trimmed);
|
|
556
|
+
const normalizedStatus = taskData().normalizeTaskLifecycleStatus(issue.status);
|
|
557
|
+
if (issue.issue_type === "task" && issue.id && (normalizedStatus === "completed" || normalizedStatus === "cancelled" || normalizedStatus === "blocked")) {
|
|
558
|
+
closed.push(issue.id);
|
|
559
|
+
}
|
|
560
|
+
} catch {}
|
|
561
|
+
}
|
|
562
|
+
if (closed.length === 0) {
|
|
563
|
+
console.log("OK: no terminal tasks to reopen");
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
for (const id of closed) {
|
|
567
|
+
const update = runCapture(["br", "--no-db", "update", id, "--status", "open"], paths.monorepoRoot);
|
|
568
|
+
if (update.exitCode === 0) {
|
|
569
|
+
console.log(`OK: reopened task ${id}`);
|
|
570
|
+
} else {
|
|
571
|
+
console.log(`WARN: failed to reopen task ${id}`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
function clearDirectory(dir, keepName) {
|
|
576
|
+
if (!existsSync4(dir)) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
for (const entry of readdirSync(dir)) {
|
|
580
|
+
if (entry === keepName) {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
rmSync2(resolve4(dir, entry), { recursive: true, force: true });
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function resolvedPins(projectRoot, taskId) {
|
|
587
|
+
const explicit = explicitPins(projectRoot, taskId);
|
|
588
|
+
if (Object.keys(explicit).length > 0) {
|
|
589
|
+
return explicit;
|
|
590
|
+
}
|
|
591
|
+
return discoverPins(projectRoot, taskId);
|
|
592
|
+
}
|
|
593
|
+
function explicitPins(projectRoot, taskId) {
|
|
594
|
+
const repoPins2 = readRepoDiscoveryTaskConfig(projectRoot)[taskId]?.repo_pins || {};
|
|
595
|
+
const normalized = {};
|
|
596
|
+
const validAliases = new Set(managedRepoEntries().map((e) => e.alias));
|
|
597
|
+
for (const [key, value] of Object.entries(repoPins2)) {
|
|
598
|
+
if (!value) {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (validAliases.size > 0 && !validAliases.has(key)) {
|
|
602
|
+
throw new Error(`Unsupported repo pin key for ${taskId}: ${key}. Known aliases: ${[...validAliases].join(", ") || "(none registered)"}`);
|
|
603
|
+
}
|
|
604
|
+
const existing = normalized[key];
|
|
605
|
+
if (existing && existing !== value) {
|
|
606
|
+
throw new Error(`Conflicting explicit repo pins for ${key}: ${existing} vs ${value}`);
|
|
607
|
+
}
|
|
608
|
+
normalized[key] = value;
|
|
609
|
+
}
|
|
610
|
+
return normalized;
|
|
611
|
+
}
|
|
612
|
+
function taskDependencies(projectRoot, taskId) {
|
|
613
|
+
return taskData().taskDependencyIds(projectRoot, taskId);
|
|
614
|
+
}
|
|
615
|
+
function discoverPins(projectRoot, taskId) {
|
|
616
|
+
const deps = taskDependencies(projectRoot, taskId);
|
|
617
|
+
if (deps.length === 0) {
|
|
618
|
+
return repoBaseline(projectRoot, true);
|
|
619
|
+
}
|
|
620
|
+
const paths = resolveRepoDiscoveryPaths(projectRoot);
|
|
621
|
+
const state = readJsonFile(paths.taskRepoCommitsPath, {});
|
|
622
|
+
const pinKeys = managedRepoEntries().map((e) => e.alias);
|
|
623
|
+
if (pinKeys.length === 0) {
|
|
624
|
+
return {};
|
|
625
|
+
}
|
|
626
|
+
const discovered = {};
|
|
627
|
+
for (const key of pinKeys) {
|
|
628
|
+
const commits = new Set;
|
|
629
|
+
for (const dep of deps) {
|
|
630
|
+
const fromState = state[dep]?.repos?.[key];
|
|
631
|
+
if (fromState) {
|
|
632
|
+
commits.add(fromState);
|
|
633
|
+
}
|
|
634
|
+
const fromArtifact = readPinFromArtifact(projectRoot, dep, key);
|
|
635
|
+
if (fromArtifact) {
|
|
636
|
+
commits.add(fromArtifact);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (commits.size > 1) {
|
|
640
|
+
const values = [...commits].join(`
|
|
641
|
+
- `);
|
|
642
|
+
throw new Error(`Conflicting discovered pins for ${key} from dependency graph of ${taskId}:
|
|
643
|
+
- ${values}`);
|
|
644
|
+
}
|
|
645
|
+
if (commits.size === 1) {
|
|
646
|
+
discovered[key] = [...commits][0];
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return discovered;
|
|
650
|
+
}
|
|
651
|
+
function readRepoDiscoveryTaskConfig(projectRoot) {
|
|
652
|
+
try {
|
|
653
|
+
return taskData().readSourceTaskConfig(projectRoot);
|
|
654
|
+
} catch {
|
|
655
|
+
return taskData().readTaskConfig(projectRoot);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function resolveRepoDiscoveryPaths(projectRoot) {
|
|
659
|
+
const monorepoRoot = managedRepoService.resolveMonorepoRepoLayout(projectRoot).checkoutRoot;
|
|
660
|
+
const explicitHostProjectRoot = (process.env.RIG_HOST_PROJECT_ROOT || "").trim();
|
|
661
|
+
const normalizedProjectRoot = resolve4(projectRoot);
|
|
662
|
+
const hostProjectRoot = explicitHostProjectRoot && shouldUseHostProjectStateRoot(normalizedProjectRoot) ? explicitHostProjectRoot : normalizedProjectRoot;
|
|
663
|
+
const stateDir = resolve4(hostProjectRoot, ".rig", "state");
|
|
664
|
+
return {
|
|
665
|
+
monorepoRoot,
|
|
666
|
+
taskRepoCommitsPath: resolve4(stateDir, "task-repo-commits.json"),
|
|
667
|
+
baseRepoPinsPath: resolve4(stateDir, "base-repo-pins.json")
|
|
435
668
|
};
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
669
|
+
}
|
|
670
|
+
function shouldUseHostProjectStateRoot(projectRoot) {
|
|
671
|
+
const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
672
|
+
if (runtimeWorkspace && resolve4(runtimeWorkspace) === projectRoot) {
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
return basename2(dirname2(projectRoot)) === ".worktrees";
|
|
676
|
+
}
|
|
677
|
+
function readPinFromArtifact(projectRoot, depTask, repoKey) {
|
|
678
|
+
const snapshot = resolve4(taskData().artifactDirForId(projectRoot, depTask), "git-state.txt");
|
|
679
|
+
if (!existsSync4(snapshot)) {
|
|
680
|
+
return "";
|
|
681
|
+
}
|
|
682
|
+
const content = readFileSync(snapshot, "utf-8");
|
|
683
|
+
const chunk = content.split(/\r?\n/);
|
|
684
|
+
let inSection = false;
|
|
685
|
+
for (const line of chunk) {
|
|
686
|
+
if (line.startsWith("## ")) {
|
|
687
|
+
inSection = line.startsWith(`## ${repoKey}`);
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (!inSection) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (line.startsWith("head:")) {
|
|
694
|
+
return line.replace(/^head:\s*/, "").trim();
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return "";
|
|
698
|
+
}
|
|
699
|
+
function repoPath(projectRoot, key) {
|
|
700
|
+
const managed = managedRepoService.resolveManagedRepoLayoutByAlias(projectRoot, key);
|
|
701
|
+
if (managed) {
|
|
702
|
+
return managed.checkoutRoot;
|
|
703
|
+
}
|
|
704
|
+
return key.startsWith("/") ? key : resolve4(projectRoot, key);
|
|
705
|
+
}
|
|
706
|
+
function applyPins(projectRoot, pins) {
|
|
707
|
+
for (const [key, commit] of Object.entries(pins)) {
|
|
708
|
+
const path = repoPath(projectRoot, key);
|
|
709
|
+
if (!existsSync4(resolve4(path, ".git"))) {
|
|
710
|
+
throw new Error(`Repo for pin not available: ${key} (${path})`);
|
|
711
|
+
}
|
|
712
|
+
let hasCommit = runGitCapture(["git", "-C", path, "cat-file", "-e", `${commit}^{commit}`], projectRoot).exitCode === 0;
|
|
713
|
+
if (!hasCommit) {
|
|
714
|
+
runGitCapture(["git", "-C", path, "fetch", "--all", "--tags", "--prune"], projectRoot);
|
|
715
|
+
hasCommit = runGitCapture(["git", "-C", path, "cat-file", "-e", `${commit}^{commit}`], projectRoot).exitCode === 0;
|
|
716
|
+
}
|
|
717
|
+
if (!hasCommit) {
|
|
718
|
+
throw new Error(`Commit not found for ${key}: ${commit}`);
|
|
719
|
+
}
|
|
720
|
+
const current = runGitCapture(["git", "-C", path, "rev-parse", "HEAD"], projectRoot).stdout.trim();
|
|
721
|
+
if (current === commit) {
|
|
722
|
+
console.log(`Repo pin: ${key} already at ${commit}`);
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
const branch = runGitCapture(["git", "-C", path, "rev-parse", "--abbrev-ref", "HEAD"], projectRoot).stdout.trim();
|
|
726
|
+
const checkout = branch.startsWith("rig/") ? runGitCapture(["git", "-C", path, "reset", "--hard", commit], projectRoot) : runGitCapture(["git", "-C", path, "checkout", "--detach", commit], projectRoot);
|
|
727
|
+
if (checkout.exitCode !== 0) {
|
|
728
|
+
throw new Error(`Failed to apply repo pin ${key} -> ${commit}:
|
|
729
|
+
${checkout.stderr || checkout.stdout}`);
|
|
730
|
+
}
|
|
731
|
+
console.log(`Repo pin: ${key} -> ${commit}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function verifyPins(projectRoot, pins) {
|
|
735
|
+
let ok = true;
|
|
736
|
+
for (const [key, expected] of Object.entries(pins)) {
|
|
737
|
+
const path = repoPath(projectRoot, key);
|
|
738
|
+
if (!existsSync4(resolve4(path, ".git"))) {
|
|
739
|
+
console.error(`ERROR: repo missing during pin verification: ${key}`);
|
|
740
|
+
ok = false;
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
const current = runGitCapture(["git", "-C", path, "rev-parse", "HEAD"], projectRoot).stdout.trim();
|
|
744
|
+
if (current === expected) {
|
|
745
|
+
console.log(`Repo verify: ${key} at ${current}`);
|
|
746
|
+
} else {
|
|
747
|
+
console.error(`ERROR: repo pin mismatch for ${key}: expected ${expected}, got ${current}`);
|
|
748
|
+
ok = false;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return ok;
|
|
752
|
+
}
|
|
753
|
+
function runGitCapture(command, projectRoot) {
|
|
754
|
+
return runCapture(command, projectRoot, resolveRuntimeGitEnv());
|
|
755
|
+
}
|
|
756
|
+
function resolveRuntimeGitEnv() {
|
|
757
|
+
if (process.env.GIT_SSH_COMMAND?.trim()) {
|
|
758
|
+
return {
|
|
759
|
+
HOME: process.env.HOME ?? "",
|
|
760
|
+
GIT_SSH_COMMAND: process.env.GIT_SSH_COMMAND
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
const runtimeRoot = process.env.RIG_RUNTIME_HOME?.trim() || (process.env.RIG_RUNTIME_CONTEXT_FILE?.trim() ? resolve4(process.env.RIG_RUNTIME_CONTEXT_FILE, "..") : inferRuntimeRootFromWorkspace(process.cwd()));
|
|
764
|
+
const runtimeHome = runtimeRoot ? resolve4(runtimeRoot, "home") : process.env.HOME?.trim() || "";
|
|
765
|
+
if (!runtimeHome) {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const knownHostsPath = resolve4(runtimeHome, ".ssh", "known_hosts");
|
|
769
|
+
if (!existsSync4(knownHostsPath)) {
|
|
770
|
+
return { HOME: runtimeHome };
|
|
771
|
+
}
|
|
772
|
+
const agentSshKey = resolve4(runtimeHome, ".ssh", "rig-agent-key");
|
|
773
|
+
const sshParts = [
|
|
774
|
+
"ssh",
|
|
775
|
+
`-o UserKnownHostsFile="${knownHostsPath}"`,
|
|
776
|
+
"-o StrictHostKeyChecking=yes",
|
|
777
|
+
"-F /dev/null"
|
|
778
|
+
];
|
|
779
|
+
if (existsSync4(agentSshKey)) {
|
|
780
|
+
sshParts.splice(1, 0, `-i "${agentSshKey}"`, "-o IdentitiesOnly=yes");
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
HOME: runtimeHome,
|
|
784
|
+
GIT_SSH_COMMAND: sshParts.join(" ")
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function inferRuntimeRootFromWorkspace(cwd) {
|
|
788
|
+
const contextPath = findRuntimeContextFile(cwd);
|
|
789
|
+
if (!contextPath || !existsSync4(contextPath)) {
|
|
790
|
+
return "";
|
|
791
|
+
}
|
|
792
|
+
try {
|
|
793
|
+
loadRuntimeContext(contextPath);
|
|
794
|
+
return resolve4(contextPath, "..");
|
|
795
|
+
} catch {
|
|
796
|
+
return "";
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function findRuntimeContextFile(startPath) {
|
|
800
|
+
let current = resolve4(startPath);
|
|
801
|
+
while (true) {
|
|
802
|
+
const candidate = resolve4(current, "runtime-context.json");
|
|
803
|
+
if (existsSync4(candidate)) {
|
|
804
|
+
return candidate;
|
|
805
|
+
}
|
|
806
|
+
const parent = resolve4(current, "..");
|
|
807
|
+
if (parent === current) {
|
|
808
|
+
return "";
|
|
809
|
+
}
|
|
810
|
+
current = parent;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
function runGit3(projectRoot, args, failureSummary) {
|
|
814
|
+
const result = runCapture(["git", ...args], projectRoot);
|
|
815
|
+
if (result.exitCode !== 0) {
|
|
816
|
+
const details = result.stderr.trim() || result.stdout.trim() || "unknown git failure";
|
|
817
|
+
throw new CliError(`${failureSummary}: ${details}`, 2);
|
|
818
|
+
}
|
|
819
|
+
return result.stdout.trim();
|
|
820
|
+
}
|
|
821
|
+
async function ensureProjectMainFreshBeforeRun(options) {
|
|
822
|
+
if (options.disabled) {
|
|
823
|
+
return { status: "disabled" };
|
|
824
|
+
}
|
|
825
|
+
const defaultSync = (projectRoot) => {
|
|
826
|
+
for (const entry of managedRepoEntries()) {
|
|
827
|
+
managedRepoService.syncManagedRepo(projectRoot, entry.id);
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
await (options.syncMonorepo ?? defaultSync)(options.projectRoot);
|
|
831
|
+
runGit3(options.projectRoot, ["fetch", "origin", "main"], "Failed to fetch origin/main");
|
|
832
|
+
const branch = runGit3(options.projectRoot, ["rev-parse", "--abbrev-ref", "HEAD"], "Failed to read current branch");
|
|
833
|
+
if (branch !== "main") {
|
|
834
|
+
return { status: "skipped_not_main", branch };
|
|
835
|
+
}
|
|
836
|
+
const countsText = runGit3(options.projectRoot, ["rev-list", "--left-right", "--count", "main...origin/main"], "Failed to compare local main to origin/main");
|
|
837
|
+
const [localAheadRaw, remoteAheadRaw] = countsText.split(/\s+/);
|
|
838
|
+
const localAhead = Number.parseInt(localAheadRaw || "0", 10);
|
|
839
|
+
const remoteAhead = Number.parseInt(remoteAheadRaw || "0", 10);
|
|
840
|
+
if (!Number.isFinite(localAhead) || !Number.isFinite(remoteAhead)) {
|
|
841
|
+
throw new CliError(`Failed to compare local main to origin/main: unexpected rev-list output "${countsText}"`, 2);
|
|
842
|
+
}
|
|
843
|
+
if (remoteAhead === 0) {
|
|
844
|
+
if (localAhead > 0) {
|
|
845
|
+
return { status: "local_ahead", localAhead };
|
|
846
|
+
}
|
|
847
|
+
return { status: "up_to_date" };
|
|
848
|
+
}
|
|
849
|
+
if (localAhead > 0) {
|
|
850
|
+
throw new CliError("Local main has diverged from origin/main. Resolve the branch state manually or rerun with --skip-project-sync.", 2);
|
|
851
|
+
}
|
|
852
|
+
runGit3(options.projectRoot, ["merge", "--ff-only", "origin/main"], "Failed to fast-forward local main to origin/main");
|
|
853
|
+
await options.runBootstrap();
|
|
854
|
+
return { status: "updated", remoteAhead };
|
|
855
|
+
}
|
|
856
|
+
var repoOperationsService = {
|
|
857
|
+
repoEnsure,
|
|
858
|
+
repoPins,
|
|
859
|
+
repoVerify,
|
|
860
|
+
repoDiscover,
|
|
861
|
+
repoBaseline,
|
|
862
|
+
resetBaseline,
|
|
863
|
+
ensureProjectMainFreshBeforeRun
|
|
864
|
+
};
|
|
865
|
+
var repoOps = repoOperationsService;
|
|
447
866
|
// packages/repos-plugin/src/plugin.ts
|
|
448
867
|
import { definePlugin } from "@rig/core/config";
|
|
449
|
-
import {
|
|
868
|
+
import { defineCapability as defineCapability2 } from "@rig/core/capability";
|
|
869
|
+
import { MANAGED_REPO_SERVICE_CAPABILITY, REPO_OPERATIONS_CAPABILITY } from "@rig/contracts";
|
|
450
870
|
var REPOS_PLUGIN_NAME = "@rig/repos-plugin";
|
|
871
|
+
var ManagedRepoCap = defineCapability2(MANAGED_REPO_SERVICE_CAPABILITY);
|
|
872
|
+
var RepoOperationsCap = defineCapability2(REPO_OPERATIONS_CAPABILITY);
|
|
451
873
|
var reposPlugin = definePlugin({
|
|
452
874
|
name: REPOS_PLUGIN_NAME,
|
|
453
875
|
version: "0.0.0-alpha.1",
|
|
454
876
|
contributes: {
|
|
455
877
|
capabilities: [
|
|
456
|
-
{
|
|
457
|
-
id: MANAGED_REPO_SERVICE_CAPABILITY_ID,
|
|
878
|
+
ManagedRepoCap.provide(svc, {
|
|
458
879
|
title: "Managed repositories",
|
|
459
|
-
description: "Registry, on-disk layout, and mirror/sync for plugin-contributed managed repos."
|
|
460
|
-
|
|
461
|
-
|
|
880
|
+
description: "Registry, on-disk layout, and mirror/sync for plugin-contributed managed repos."
|
|
881
|
+
}),
|
|
882
|
+
RepoOperationsCap.provide(repoOps, {
|
|
883
|
+
title: "Repository operations",
|
|
884
|
+
description: "Task repo pinning, baseline reset, and project-main pre-run sync."
|
|
885
|
+
})
|
|
462
886
|
]
|
|
463
887
|
}
|
|
464
888
|
});
|
|
@@ -477,6 +901,8 @@ export {
|
|
|
477
901
|
resolveManagedRepoIdByAlias,
|
|
478
902
|
reposPlugin,
|
|
479
903
|
repoRegistrationToManagedEntry,
|
|
904
|
+
repoOps,
|
|
905
|
+
repoOperationsService,
|
|
480
906
|
refreshManagedRepoMirror,
|
|
481
907
|
readManagedRepoMirrorState,
|
|
482
908
|
managedRepoService,
|
package/dist/src/layout.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// packages/repos-plugin/src/layout.ts
|
|
3
3
|
import { existsSync } from "fs";
|
|
4
4
|
import { basename, dirname, join, resolve } from "path";
|
|
5
|
-
import { resolveMonorepoRoot } from "@rig/
|
|
5
|
+
import { resolveMonorepoRoot } from "@rig/core/layout";
|
|
6
6
|
|
|
7
7
|
// packages/repos-plugin/src/registry.ts
|
|
8
8
|
var MANAGED_REPOS = new Map;
|
|
@@ -19,7 +19,7 @@ function listManagedRepoEntries() {
|
|
|
19
19
|
// packages/repos-plugin/src/layout.ts
|
|
20
20
|
import { existsSync } from "fs";
|
|
21
21
|
import { basename, dirname, join, resolve } from "path";
|
|
22
|
-
import { resolveMonorepoRoot } from "@rig/
|
|
22
|
+
import { resolveMonorepoRoot } from "@rig/core/layout";
|
|
23
23
|
function resolveRepoStateDir(projectRoot) {
|
|
24
24
|
const normalizedProjectRoot = resolve(projectRoot);
|
|
25
25
|
const projectParent = dirname(normalizedProjectRoot);
|
|
@@ -60,7 +60,7 @@ function resolveManagedRepoLayout(projectRoot, repoId) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// packages/repos-plugin/src/mirror/state.ts
|
|
63
|
-
import { readAuthorityProjectStateJson, writeAuthorityProjectStateJson } from "@rig/
|
|
63
|
+
import { readAuthorityProjectStateJson, writeAuthorityProjectStateJson } from "@rig/core/json-files";
|
|
64
64
|
var STATE_VERSION = 1;
|
|
65
65
|
function defaultMirrorState(projectRoot, repoId) {
|
|
66
66
|
const layout = resolveManagedRepoLayout(projectRoot, repoId);
|