@open-agent-toolkit/cli 0.2.50 → 0.2.51
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/assets/bundle-metadata.json +1 -1
- package/assets/docs/reference/cli-reference.md +20 -5
- package/assets/docs/reference/file-locations.md +8 -2
- package/assets/docs/reference/oat-directory-structure.md +14 -5
- package/assets/docs/workflows/projects/artifacts.md +9 -4
- package/assets/docs/workflows/projects/lifecycle.md +17 -5
- package/assets/docs/workflows/projects/picking-up-projects.md +26 -9
- package/assets/docs/workflows/projects/pr-flow.md +5 -3
- package/assets/docs/workflows/projects/reviewing-oat-prs.md +12 -8
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +2 -2
- package/assets/skills/oat-project-complete/SKILL.md +193 -26
- package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +306 -0
- package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +125 -0
- package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +106 -0
- package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +110 -0
- package/assets/skills/oat-project-complete/scripts/resolve-synced-archive-entry.mjs +164 -0
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +2 -2
- package/dist/commands/project/archive/archive-utils.d.ts +8 -1
- package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
- package/dist/commands/project/archive/archive-utils.js +147 -50
- package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
- package/dist/commands/project/archive/push-runner.js +15 -2
- package/dist/commands/project/links/compute.d.ts +1 -0
- package/dist/commands/project/links/compute.d.ts.map +1 -1
- package/dist/commands/project/links/compute.js +5 -4
- package/dist/commands/project/links/index.d.ts +9 -0
- package/dist/commands/project/links/index.d.ts.map +1 -1
- package/dist/commands/project/links/index.js +71 -2
- package/dist/commands/project/list.d.ts +10 -2
- package/dist/commands/project/list.d.ts.map +1 -1
- package/dist/commands/project/list.js +205 -29
- package/dist/commands/project/open/index.d.ts +2 -0
- package/dist/commands/project/open/index.d.ts.map +1 -1
- package/dist/commands/project/open/index.js +16 -1
- package/dist/commands/project/prune/index.d.ts +10 -4
- package/dist/commands/project/prune/index.d.ts.map +1 -1
- package/dist/commands/project/prune/index.js +85 -15
- package/dist/commands/project/pull/index.d.ts +4 -0
- package/dist/commands/project/pull/index.d.ts.map +1 -1
- package/dist/commands/project/pull/index.js +82 -3
- package/dist/commands/project/sync/git.d.ts +9 -0
- package/dist/commands/project/sync/git.d.ts.map +1 -1
- package/dist/commands/project/sync/git.js +18 -0
- package/dist/commands/project/sync/ref-sync.d.ts +16 -1
- package/dist/commands/project/sync/ref-sync.d.ts.map +1 -1
- package/dist/commands/project/sync/ref-sync.js +288 -17
- package/dist/commands/project/sync/resolve-target.d.ts +10 -0
- package/dist/commands/project/sync/resolve-target.d.ts.map +1 -1
- package/dist/commands/project/sync/resolve-target.js +56 -1
- package/dist/commands/shared/project-scope.d.ts +2 -0
- package/dist/commands/shared/project-scope.d.ts.map +1 -1
- package/dist/commands/shared/project-scope.js +5 -0
- package/dist/commands/state/generate.d.ts +2 -0
- package/dist/commands/state/generate.d.ts.map +1 -1
- package/dist/commands/state/generate.js +93 -28
- package/package.json +2 -2
|
@@ -5,7 +5,8 @@ import { basename, dirname, isAbsolute, join, posix as path, relative, resolve,
|
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { defaultGitRunner } from '../../project/sync/git.js';
|
|
7
7
|
import { readSyncedRecord, writeSyncedRecord, } from '../../project/sync/record.js';
|
|
8
|
-
import { buildSyncTarget, commitRecordChange, preflightSyncedCheckout, removeSyncedCheckout, } from '../../project/sync/ref-sync.js';
|
|
8
|
+
import { buildSyncTarget, commitRecordChange, preflightSyncedCheckout, removeSyncedCheckout, retireSyncedRef, } from '../../project/sync/ref-sync.js';
|
|
9
|
+
import { probeSyncedTerminalRefs } from '../../project/sync/resolve-target.js';
|
|
9
10
|
import { canonicalizePath, resolveProjectScope, resolveScopeRoot, syncedRecordPath, } from '../../shared/project-scope.js';
|
|
10
11
|
import { CliError } from '../../../errors/cli-error.js';
|
|
11
12
|
import { copyDirectory, copySingleFile, dirExists, ensureDir, fileExists, } from '../../../fs/io.js';
|
|
@@ -356,6 +357,66 @@ async function verifyArchiveSnapshotMetadata(archivePath, expected) {
|
|
|
356
357
|
throw new CliError(`Existing archive \`${archivePath}\` does not match persisted snapshot \`${expected.snapshotName}\`; refusing to overwrite it.`);
|
|
357
358
|
}
|
|
358
359
|
}
|
|
360
|
+
async function resolveRecordlessSyncedArchiveIdentity(options, target, git, dependencies) {
|
|
361
|
+
const tentativeTarget = await resolveArchiveProjectTarget(options, dependencies);
|
|
362
|
+
assertDurableArchiveProjectTarget(tentativeTarget);
|
|
363
|
+
const archiveRoot = dirname(tentativeTarget.archivePath);
|
|
364
|
+
const candidates = [];
|
|
365
|
+
let entries;
|
|
366
|
+
try {
|
|
367
|
+
entries = await readdir(archiveRoot, { withFileTypes: true });
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
entries = [];
|
|
371
|
+
}
|
|
372
|
+
for (const entry of entries) {
|
|
373
|
+
if (!entry.isDirectory())
|
|
374
|
+
continue;
|
|
375
|
+
const archivePath = join(archiveRoot, entry.name);
|
|
376
|
+
let metadata;
|
|
377
|
+
try {
|
|
378
|
+
metadata = JSON.parse(await readFile(join(archivePath, ARCHIVE_SNAPSHOT_METADATA_FILENAME), 'utf8'));
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (isRecord(metadata) &&
|
|
384
|
+
metadata.projectName === options.projectName &&
|
|
385
|
+
metadata.scope === 'synced' &&
|
|
386
|
+
typeof metadata.snapshotName === 'string' &&
|
|
387
|
+
typeof metadata.sourceRefSha === 'string' &&
|
|
388
|
+
/^[0-9a-f]{40}$/.test(metadata.sourceRefSha)) {
|
|
389
|
+
candidates.push({
|
|
390
|
+
archivePath,
|
|
391
|
+
metadata: {
|
|
392
|
+
projectName: options.projectName,
|
|
393
|
+
snapshotName: metadata.snapshotName,
|
|
394
|
+
scope: 'synced',
|
|
395
|
+
sourceRefSha: metadata.sourceRefSha,
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (candidates.length === 0) {
|
|
401
|
+
throw new CliError(`Synced project ${options.projectName} has no active record and no persisted synced archive identity; refusing to create a replacement active record or a new snapshot.`, 2);
|
|
402
|
+
}
|
|
403
|
+
const probe = dependencies.probeSyncedTerminalRefs ?? probeSyncedTerminalRefs;
|
|
404
|
+
const matches = [];
|
|
405
|
+
for (const candidate of candidates) {
|
|
406
|
+
const terminal = await probe(target, candidate.metadata.sourceRefSha, git);
|
|
407
|
+
if (terminal.completedSha === candidate.metadata.sourceRefSha &&
|
|
408
|
+
(terminal.activeSha === null ||
|
|
409
|
+
terminal.activeSha === candidate.metadata.sourceRefSha)) {
|
|
410
|
+
matches.push(candidate);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (matches.length !== 1) {
|
|
414
|
+
throw new CliError(matches.length === 0
|
|
415
|
+
? `Synced project ${options.projectName} has no active record, but no persisted archive identity matches its authoritative completed ref; refusing terminal retry before export or S3 mutation.`
|
|
416
|
+
: `Synced project ${options.projectName} has no active record and multiple persisted archives match its authoritative completed ref; refusing an ambiguous terminal retry.`, 2);
|
|
417
|
+
}
|
|
418
|
+
return matches[0];
|
|
419
|
+
}
|
|
359
420
|
async function exportProjectSummary(archivePath, snapshotName, summaryExportPath, repoRoot, dependencies) {
|
|
360
421
|
const summarySource = join(archivePath, 'summary.md');
|
|
361
422
|
const exists = dependencies.fileExists ?? fileExists;
|
|
@@ -850,29 +911,42 @@ export async function archiveProjectOnCompletion(options, dependencies = {}) {
|
|
|
850
911
|
const syncTarget = isSynced
|
|
851
912
|
? buildSyncTarget(options.repoRoot, options.projectsRoot, options.projectName)
|
|
852
913
|
: null;
|
|
853
|
-
const git = dependencies.gitRunner ?? defaultGitRunner;
|
|
854
914
|
let activeRecord = record;
|
|
915
|
+
const git = dependencies.gitRunner ?? defaultGitRunner;
|
|
916
|
+
const recordlessIdentity = syncTarget && !activeRecord
|
|
917
|
+
? await resolveRecordlessSyncedArchiveIdentity(options, syncTarget, git, dependencies)
|
|
918
|
+
: null;
|
|
855
919
|
let sourceRefSha;
|
|
856
920
|
if (syncTarget) {
|
|
857
|
-
if (
|
|
858
|
-
|
|
921
|
+
if (recordlessIdentity) {
|
|
922
|
+
sourceRefSha = recordlessIdentity.metadata.sourceRefSha;
|
|
859
923
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
924
|
+
else {
|
|
925
|
+
const preflight = await (dependencies.preflightSyncedCheckout ?? preflightSyncedCheckout)(syncTarget, git);
|
|
926
|
+
const isBoundTerminalRetry = Boolean(preflight.sha &&
|
|
927
|
+
activeRecord?.archiveSourceRefSha === preflight.sha &&
|
|
928
|
+
preflight.status === 'unpushed');
|
|
929
|
+
if (preflight.status !== 'clean' &&
|
|
930
|
+
!(preflight.status === 'absent' &&
|
|
931
|
+
(activeRecord?.status === 'complete' ||
|
|
932
|
+
Boolean(activeRecord?.archiveSourceRefSha))) &&
|
|
933
|
+
!isBoundTerminalRetry) {
|
|
934
|
+
const recoveryCommand = preflight.status === 'absent'
|
|
935
|
+
? `oat project pull ${options.projectName}`
|
|
936
|
+
: `oat project push ${options.projectName}`;
|
|
937
|
+
throw new CliError(`Synced project ${options.projectName} is ${preflight.status}; run ${recoveryCommand} before archiving.`, 1);
|
|
938
|
+
}
|
|
939
|
+
sourceRefSha =
|
|
940
|
+
preflight.sha ??
|
|
941
|
+
activeRecord?.archiveSourceRefSha ??
|
|
942
|
+
(await git.run(['rev-parse', syncTarget.ref], {
|
|
943
|
+
cwd: options.repoRoot,
|
|
944
|
+
})).stdout;
|
|
867
945
|
}
|
|
868
|
-
sourceRefSha =
|
|
869
|
-
preflight.sha ??
|
|
870
|
-
(await git.run(['rev-parse', syncTarget.ref], { cwd: options.repoRoot }))
|
|
871
|
-
.stdout;
|
|
872
946
|
if (!/^[0-9a-f]{40}$/.test(sourceRefSha)) {
|
|
873
947
|
throw new CliError(`Unable to bind archive retry identity for ${options.projectName} to ${syncTarget.ref}.`, 2);
|
|
874
948
|
}
|
|
875
|
-
if (activeRecord
|
|
949
|
+
if (activeRecord?.archiveSourceRefSha &&
|
|
876
950
|
activeRecord.archiveSourceRefSha !== sourceRefSha) {
|
|
877
951
|
throw new CliError(`Archive retry for ${options.projectName} is bound to source ref ${activeRecord.archiveSourceRefSha}, but ${syncTarget.ref} is now ${sourceRefSha}. Restore the retained ref to the recorded commit to finish this archive transaction, or preserve the existing snapshot and start a separately reviewed recovery; refusing to accept stale archive content.`, 1);
|
|
878
952
|
}
|
|
@@ -881,9 +955,11 @@ export async function archiveProjectOnCompletion(options, dependencies = {}) {
|
|
|
881
955
|
repoRoot: options.repoRoot,
|
|
882
956
|
projectsRoot: options.projectsRoot,
|
|
883
957
|
projectName: options.projectName,
|
|
884
|
-
...(activeRecord?.archiveSnapshot
|
|
958
|
+
...((activeRecord?.archiveSnapshot ??
|
|
959
|
+
recordlessIdentity?.metadata.snapshotName)
|
|
885
960
|
? {
|
|
886
|
-
archiveSnapshot: activeRecord
|
|
961
|
+
archiveSnapshot: activeRecord?.archiveSnapshot ??
|
|
962
|
+
recordlessIdentity?.metadata.snapshotName,
|
|
887
963
|
archiveScope: projectScope,
|
|
888
964
|
}
|
|
889
965
|
: {}),
|
|
@@ -902,7 +978,9 @@ export async function archiveProjectOnCompletion(options, dependencies = {}) {
|
|
|
902
978
|
}
|
|
903
979
|
const archivePath = archiveTarget.archivePath;
|
|
904
980
|
const exportIdentity = syncTarget
|
|
905
|
-
? (activeRecord?.archiveSnapshot ??
|
|
981
|
+
? (activeRecord?.archiveSnapshot ??
|
|
982
|
+
recordlessIdentity?.metadata.snapshotName ??
|
|
983
|
+
snapshotName)
|
|
906
984
|
: snapshotName;
|
|
907
985
|
const snapshotId = syncTarget ? exportIdentity : basename(archivePath);
|
|
908
986
|
const shouldPersistArchiveSnapshot = Boolean(syncTarget &&
|
|
@@ -1000,6 +1078,9 @@ export async function archiveProjectOnCompletion(options, dependencies = {}) {
|
|
|
1000
1078
|
env: dependencies.env,
|
|
1001
1079
|
});
|
|
1002
1080
|
warnings.push(...access.warnings);
|
|
1081
|
+
if (syncTarget && !access.ok) {
|
|
1082
|
+
throw new CliError(`Synced archive durability for ${options.projectName} requires the configured S3 upload to succeed before terminal cleanup. ${access.warnings.join(' ') || 'Archive S3 access could not be verified.'}`, 1);
|
|
1083
|
+
}
|
|
1003
1084
|
if (access.ok) {
|
|
1004
1085
|
const remoteRepoRoot = await resolvePrimaryRepoRoot(options.repoRoot, dependencies);
|
|
1005
1086
|
s3Path = buildProjectArchiveS3Uri(options.s3Uri, remoteRepoRoot, exportIdentity);
|
|
@@ -1018,6 +1099,9 @@ export async function archiveProjectOnCompletion(options, dependencies = {}) {
|
|
|
1018
1099
|
}
|
|
1019
1100
|
catch (error) {
|
|
1020
1101
|
const message = error instanceof Error ? error.message : String(error);
|
|
1102
|
+
if (syncTarget) {
|
|
1103
|
+
throw new CliError(`Synced archive durability for ${options.projectName} requires the configured S3 upload to succeed before terminal cleanup: ${message}`, 1);
|
|
1104
|
+
}
|
|
1021
1105
|
warnings.push(`Archive S3 sync to \`${s3Path}\` failed: ${message}`);
|
|
1022
1106
|
s3Path = null;
|
|
1023
1107
|
}
|
|
@@ -1025,38 +1109,53 @@ export async function archiveProjectOnCompletion(options, dependencies = {}) {
|
|
|
1025
1109
|
}
|
|
1026
1110
|
let lifecycleCommit = null;
|
|
1027
1111
|
let recapExportPaths = [];
|
|
1112
|
+
let terminalReceipt = null;
|
|
1113
|
+
let recordRetired = false;
|
|
1028
1114
|
if (syncTarget) {
|
|
1029
|
-
if (!
|
|
1030
|
-
throw new CliError(`Synced project ${options.projectName}
|
|
1115
|
+
if (!sourceRefSha) {
|
|
1116
|
+
throw new CliError(`Synced project ${options.projectName} has no verified source SHA for terminal sealing.`, 2);
|
|
1117
|
+
}
|
|
1118
|
+
terminalReceipt = await (dependencies.retireSyncedRef ?? retireSyncedRef)(syncTarget, sourceRefSha, git);
|
|
1119
|
+
if (terminalReceipt.state !== 'completed-only' &&
|
|
1120
|
+
terminalReceipt.state !== 'matching-aliases') {
|
|
1121
|
+
throw new CliError(`Synced project ${options.projectName} did not reach a verified terminal ref state.`, 2);
|
|
1122
|
+
}
|
|
1123
|
+
const removed = await (dependencies.removeSyncedCheckout ?? removeSyncedCheckout)(syncTarget, git, { force: true });
|
|
1124
|
+
if (removed.status !== 'removed' && removed.status !== 'absent') {
|
|
1125
|
+
throw new CliError(`Synced checkout became ${removed.status} during archive; run oat project push ${options.projectName} and retry.`, 1);
|
|
1031
1126
|
}
|
|
1032
1127
|
if (projectRecapExport) {
|
|
1033
1128
|
recapExportPaths = (await listArchiveExportFiles(projectRecapExport.exportRoot)).filter((filePath) => basename(filePath) !== 'manifest.json' &&
|
|
1034
1129
|
basename(filePath) !== 'build-record.json');
|
|
1035
1130
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
};
|
|
1041
|
-
await writeRecord(recordPath, activeRecord);
|
|
1131
|
+
await (dependencies.removeSyncedRecord
|
|
1132
|
+
? dependencies.removeSyncedRecord(recordPath)
|
|
1133
|
+
: removePath(recordPath, { recursive: true, force: true }));
|
|
1134
|
+
recordRetired = true;
|
|
1042
1135
|
if (options.commit !== false) {
|
|
1043
1136
|
const pathspecs = [
|
|
1044
1137
|
recordPath,
|
|
1045
1138
|
...(summaryExportFile ? [summaryExportFile] : []),
|
|
1046
1139
|
...recapExportPaths,
|
|
1047
1140
|
];
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1141
|
+
const lifecycleStatus = await git.run([
|
|
1142
|
+
'status',
|
|
1143
|
+
'--porcelain=v1',
|
|
1144
|
+
'--untracked-files=all',
|
|
1145
|
+
'--',
|
|
1146
|
+
...pathspecs.map((pathspec) => relative(canonicalizePath(resolve(options.repoRoot)), canonicalizePath(resolve(pathspec)))),
|
|
1147
|
+
], { cwd: options.repoRoot });
|
|
1148
|
+
const committed = lifecycleStatus.stdout === ''
|
|
1149
|
+
? null
|
|
1150
|
+
: await (dependencies.commitRecordChange ?? commitRecordChange)(options.repoRoot, pathspecs, `chore(oat): complete synced project ${options.projectName}`, git, {
|
|
1151
|
+
summaryExportPath: options.summaryExportPath,
|
|
1152
|
+
projectRoots: syncTarget,
|
|
1153
|
+
recapExportRoot: projectRecapExport?.exportRoot,
|
|
1154
|
+
});
|
|
1053
1155
|
lifecycleCommit =
|
|
1054
1156
|
committed?.sha ??
|
|
1055
|
-
(await recoverSyncedLifecycleCommit(options.repoRoot, pathspecs,
|
|
1056
|
-
|
|
1057
|
-
const removed = await (dependencies.removeSyncedCheckout ?? removeSyncedCheckout)(syncTarget, git);
|
|
1058
|
-
if (removed.status !== 'removed' && removed.status !== 'absent') {
|
|
1059
|
-
throw new CliError(`Synced checkout became ${removed.status} during archive; run oat project push ${options.projectName} and retry.`, 1);
|
|
1157
|
+
(await recoverSyncedLifecycleCommit(options.repoRoot, pathspecs, options.projectName, `chore(oat): complete synced project ${options.projectName}`, git));
|
|
1158
|
+
await dependencies.afterLifecycleCommit?.();
|
|
1060
1159
|
}
|
|
1061
1160
|
}
|
|
1062
1161
|
return {
|
|
@@ -1068,12 +1167,14 @@ export async function archiveProjectOnCompletion(options, dependencies = {}) {
|
|
|
1068
1167
|
lifecycleCommit,
|
|
1069
1168
|
recapExportPaths,
|
|
1070
1169
|
snapshotId,
|
|
1170
|
+
terminalReceipt,
|
|
1171
|
+
recordRetired,
|
|
1071
1172
|
};
|
|
1072
1173
|
}
|
|
1073
|
-
async function recoverSyncedLifecycleCommit(repoRoot, pathspecs,
|
|
1174
|
+
async function recoverSyncedLifecycleCommit(repoRoot, pathspecs, projectName, expectedSubject, git) {
|
|
1074
1175
|
const [recordPathspec] = pathspecs;
|
|
1075
1176
|
if (!recordPathspec) {
|
|
1076
|
-
throw new CliError(`Unable to recover the prior lifecycle commit for ${
|
|
1177
|
+
throw new CliError(`Unable to recover the prior lifecycle commit for ${projectName}: no lifecycle paths were supplied.`, 2);
|
|
1077
1178
|
}
|
|
1078
1179
|
const recordPath = relative(resolve(repoRoot), resolve(recordPathspec))
|
|
1079
1180
|
.split(sep)
|
|
@@ -1086,7 +1187,7 @@ async function recoverSyncedLifecycleCommit(repoRoot, pathspecs, record, expecte
|
|
|
1086
1187
|
cwd: repoRoot,
|
|
1087
1188
|
})).stdout;
|
|
1088
1189
|
if (!/^[0-9a-f]{40}$/.test(candidate)) {
|
|
1089
|
-
throw new CliError(`Unable to recover the prior lifecycle commit for ${
|
|
1190
|
+
throw new CliError(`Unable to recover the prior lifecycle commit for ${projectName}.`, 2);
|
|
1090
1191
|
}
|
|
1091
1192
|
const subject = (await git.run(['show', '-s', '--format=%s', candidate], { cwd: repoRoot })).stdout;
|
|
1092
1193
|
const ancestor = await git.run(['merge-base', '--is-ancestor', candidate, 'HEAD'], { cwd: repoRoot, allowFailure: true });
|
|
@@ -1095,20 +1196,16 @@ async function recoverSyncedLifecycleCommit(repoRoot, pathspecs, record, expecte
|
|
|
1095
1196
|
.filter(Boolean)
|
|
1096
1197
|
.sort();
|
|
1097
1198
|
const contentsMatch = await git.run(['diff', '--quiet', candidate, '--', ...normalizedPathspecs], { cwd: repoRoot, allowFailure: true });
|
|
1098
|
-
const committedRecord = await git.run(['show', `${candidate}:${recordPath}`], {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}
|
|
1103
|
-
catch {
|
|
1104
|
-
parsedRecord = null;
|
|
1105
|
-
}
|
|
1199
|
+
const committedRecord = await git.run(['show', `${candidate}:${recordPath}`], {
|
|
1200
|
+
cwd: repoRoot,
|
|
1201
|
+
allowFailure: true,
|
|
1202
|
+
});
|
|
1106
1203
|
if (subject !== expectedSubject ||
|
|
1107
1204
|
ancestor.code !== 0 ||
|
|
1108
1205
|
contentsMatch.code !== 0 ||
|
|
1109
1206
|
JSON.stringify(changedPaths) !== JSON.stringify(normalizedPathspecs) ||
|
|
1110
|
-
|
|
1111
|
-
throw new CliError(`Unable to recover lifecycle commit ${candidate} for ${
|
|
1207
|
+
committedRecord.code === 0) {
|
|
1208
|
+
throw new CliError(`Unable to recover lifecycle commit ${candidate} for ${projectName}: subject, path set, record deletion, contents, or branch relationship did not match the completed archive transaction.`, 2);
|
|
1112
1209
|
}
|
|
1113
1210
|
return candidate;
|
|
1114
1211
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push-runner.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/archive/push-runner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAOjE,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAEL,6BAA6B,EAI7B,2BAA2B,EAC3B,sBAAsB,EACtB,oCAAoC,EAEpC,KAAK,iCAAiC,EACtC,KAAK,gCAAgC,EAEtC,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,qCAAqC;IACpD,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;IACtD,2BAA2B,EAAE,OAAO,2BAA2B,CAAC;IAChE,gBAAgB,EAAE,OAAO,gBAAgB,CAAC;IAC1C,oCAAoC,EAAE,OAAO,oCAAoC,CAAC;IAClF,0BAA0B,EAAE,CAC1B,OAAO,EAAE,iCAAiC,KACvC,OAAO,CAAC,gCAAgC,CAAC,CAAC;IAC/C,6BAA6B,EAAE,OAAO,6BAA6B,CAAC;IACpE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,SAAS,EAAE,MAAM,MAAM,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"push-runner.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/archive/push-runner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAOjE,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAEL,6BAA6B,EAI7B,2BAA2B,EAC3B,sBAAsB,EACtB,oCAAoC,EAEpC,KAAK,iCAAiC,EACtC,KAAK,gCAAgC,EAEtC,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,qCAAqC;IACpD,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;IACtD,2BAA2B,EAAE,OAAO,2BAA2B,CAAC;IAChE,gBAAgB,EAAE,OAAO,gBAAgB,CAAC;IAC1C,oCAAoC,EAAE,OAAO,oCAAoC,CAAC;IAClF,0BAA0B,EAAE,CAC1B,OAAO,EAAE,iCAAiC,KACvC,OAAO,CAAC,gCAAgC,CAAC,CAAC;IAC/C,6BAA6B,EAAE,OAAO,6BAA6B,CAAC;IACpE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,SAAS,EAAE,MAAM,MAAM,CAAC;CACzB;AA0BD,wBAAgB,4CAA4C,IAAI,qCAAqC,CAgBpG;AA2JD,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,qCAAqC,EACnD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAmHf"}
|
|
@@ -82,6 +82,10 @@ async function buildDryRunReport(dependencies, repoRoot, config, target, archive
|
|
|
82
82
|
snapshotId: projectScope === 'synced'
|
|
83
83
|
? snapshotName
|
|
84
84
|
: basename(archiveTarget.archivePath),
|
|
85
|
+
completedRef: null,
|
|
86
|
+
verifiedSourceSha: null,
|
|
87
|
+
activeAliasDisposition: null,
|
|
88
|
+
recordRetired: false,
|
|
85
89
|
};
|
|
86
90
|
}
|
|
87
91
|
function emitArchivePushText(report, summaryExportPath, logger) {
|
|
@@ -148,10 +152,11 @@ export async function runArchivePushCommand(dependencies, projectPathArg, option
|
|
|
148
152
|
timestamp: dependencies.timestamp,
|
|
149
153
|
});
|
|
150
154
|
assertDurableArchiveProjectTarget(archiveTarget);
|
|
151
|
-
|
|
155
|
+
const dryRun = options.dryRun === true || context.dryRun;
|
|
156
|
+
const recordlessSyncedApply = projectScope === 'synced' && syncedRecord === null && !dryRun;
|
|
157
|
+
if (options.projectRecapRun?.trim() && !recordlessSyncedApply) {
|
|
152
158
|
await dependencies.verifySelectedProjectRecapForArchive(target.projectPath, options.projectRecapRun.trim());
|
|
153
159
|
}
|
|
154
|
-
const dryRun = options.dryRun === true || context.dryRun;
|
|
155
160
|
if (dryRun) {
|
|
156
161
|
const report = await buildDryRunReport(dependencies, repoRoot, config, target, archiveTarget, snapshotName, projectScope);
|
|
157
162
|
emitArchivePushReport(report, config.archive?.summaryExportPath, context);
|
|
@@ -174,6 +179,14 @@ export async function runArchivePushCommand(dependencies, projectPathArg, option
|
|
|
174
179
|
lifecycleCommit: result.lifecycleCommit,
|
|
175
180
|
recapExportPaths: result.recapExportPaths,
|
|
176
181
|
snapshotId: result.snapshotId,
|
|
182
|
+
completedRef: result.terminalReceipt?.completedRef ?? null,
|
|
183
|
+
verifiedSourceSha: result.terminalReceipt?.verifiedSha ?? null,
|
|
184
|
+
activeAliasDisposition: result.terminalReceipt
|
|
185
|
+
? result.terminalReceipt.activeAliasRetained
|
|
186
|
+
? 'retained'
|
|
187
|
+
: 'removed'
|
|
188
|
+
: null,
|
|
189
|
+
recordRetired: result.recordRetired,
|
|
177
190
|
};
|
|
178
191
|
emitArchivePushReport(report, config.archive?.summaryExportPath, context);
|
|
179
192
|
process.exitCode = 0;
|
|
@@ -4,6 +4,7 @@ import { type LinksInput } from './render.js';
|
|
|
4
4
|
export interface ComputeLinksOptions {
|
|
5
5
|
durableSummaryPath?: string;
|
|
6
6
|
now: Date;
|
|
7
|
+
ref?: string;
|
|
7
8
|
}
|
|
8
9
|
export declare function computeLinksInput(target: SyncTarget, git: GitRunner, options: ComputeLinksOptions): Promise<LinksInput>;
|
|
9
10
|
//# sourceMappingURL=compute.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compute.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/links/compute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,mBAAmB;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"compute.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/links/compute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,UAAU,CAAC;AAElB,MAAM,WAAW,mBAAmB;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,IAAI,CAAC;IACV,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CA8BrB"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { LINKABLE_ARTIFACTS, } from './render.js';
|
|
2
2
|
export async function computeLinksInput(target, git, options) {
|
|
3
|
-
|
|
3
|
+
const selectedRef = options.ref ?? target.ref;
|
|
4
|
+
await git.run(['fetch', target.remote, `+${selectedRef}:${selectedRef}`], {
|
|
4
5
|
cwd: target.repoRoot,
|
|
5
6
|
});
|
|
6
7
|
const [sha, tree, origin] = await Promise.all([
|
|
7
|
-
git.run(['rev-parse',
|
|
8
|
-
git.run(['ls-tree', '--name-only',
|
|
8
|
+
git.run(['rev-parse', selectedRef], { cwd: target.repoRoot }),
|
|
9
|
+
git.run(['ls-tree', '--name-only', selectedRef], { cwd: target.repoRoot }),
|
|
9
10
|
git.run(['remote', 'get-url', target.remote], { cwd: target.repoRoot }),
|
|
10
11
|
]);
|
|
11
12
|
const treeNames = new Set(tree.stdout
|
|
@@ -16,7 +17,7 @@ export async function computeLinksInput(target, git, options) {
|
|
|
16
17
|
return {
|
|
17
18
|
slug: target.slug,
|
|
18
19
|
sha: sha.stdout,
|
|
19
|
-
ref:
|
|
20
|
+
ref: selectedRef,
|
|
20
21
|
originUrl: origin.stdout,
|
|
21
22
|
present,
|
|
22
23
|
...(options.durableSummaryPath
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
2
2
|
import { type GitRunner } from '../../project/sync/git.js';
|
|
3
|
+
import { type SyncTarget } from '../../project/sync/ref-sync.js';
|
|
3
4
|
import { resolveSyncedTarget } from '../../project/sync/resolve-target.js';
|
|
5
|
+
import { resolveProjectsRoot } from '../../shared/oat-paths.js';
|
|
4
6
|
import { Command } from 'commander';
|
|
5
7
|
import { computeLinksInput } from './compute.js';
|
|
6
8
|
interface ProjectLinksDependencies {
|
|
7
9
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
8
10
|
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
11
|
+
resolveProjectsRoot: typeof resolveProjectsRoot;
|
|
9
12
|
resolveSyncedTarget: typeof resolveSyncedTarget;
|
|
13
|
+
resolveLinksTarget: typeof resolveLinksTarget;
|
|
10
14
|
computeLinksInput: typeof computeLinksInput;
|
|
11
15
|
gitRunner: GitRunner;
|
|
12
16
|
processEnv: NodeJS.ProcessEnv;
|
|
13
17
|
now: () => Date;
|
|
14
18
|
}
|
|
19
|
+
interface ResolvedLinksTarget {
|
|
20
|
+
target: SyncTarget;
|
|
21
|
+
ref: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function resolveLinksTarget(repoRoot: string, projectsRoot: string, pathOrSlug: string | undefined, env: NodeJS.ProcessEnv, git: GitRunner, resolveTarget: typeof resolveSyncedTarget): Promise<ResolvedLinksTarget>;
|
|
15
24
|
export declare function createProjectLinksCommand(overrides?: Partial<ProjectLinksDependencies>): Command;
|
|
16
25
|
export {};
|
|
17
26
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/links/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/links/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAEL,mBAAmB,EACpB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AASjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAQ9C,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;IAC9C,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAmCD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AAwBD,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,EAAE,SAAS,EACd,aAAa,EAAE,OAAO,mBAAmB,GACxC,OAAO,CAAC,mBAAmB,CAAC,CAgE9B;AAoDD,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAsBT"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
2
2
|
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
3
3
|
import { defaultGitRunner } from '../../project/sync/git.js';
|
|
4
|
-
import {
|
|
4
|
+
import { buildSyncTarget, classifyRemoteRefLookup, } from '../../project/sync/ref-sync.js';
|
|
5
|
+
import { probeSyncedTerminalRefs, resolveSyncedTarget, } from '../../project/sync/resolve-target.js';
|
|
6
|
+
import { resolveProjectsRoot } from '../../shared/oat-paths.js';
|
|
7
|
+
import { completedSyncedRefName, resolveProjectScope, resolveScopeRoot, } from '../../shared/project-scope.js';
|
|
5
8
|
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
6
9
|
import { CliError } from '../../../errors/cli-error.js';
|
|
7
10
|
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
@@ -11,7 +14,9 @@ import { renderLinksBlock } from './render.js';
|
|
|
11
14
|
const DEFAULT_DEPENDENCIES = {
|
|
12
15
|
buildCommandContext,
|
|
13
16
|
resolveProjectRoot,
|
|
17
|
+
resolveProjectsRoot,
|
|
14
18
|
resolveSyncedTarget,
|
|
19
|
+
resolveLinksTarget,
|
|
15
20
|
computeLinksInput,
|
|
16
21
|
gitRunner: defaultGitRunner,
|
|
17
22
|
processEnv: process.env,
|
|
@@ -30,13 +35,77 @@ function normalizeDurableSummaryPath(repoRoot, durableSummaryPath) {
|
|
|
30
35
|
}
|
|
31
36
|
return repositoryRelative.split(sep).join('/');
|
|
32
37
|
}
|
|
38
|
+
function linksSlug(repoRoot, projectsRoot, pathOrSlug) {
|
|
39
|
+
if (!pathOrSlug.includes('/') && !pathOrSlug.includes('\\')) {
|
|
40
|
+
return pathOrSlug;
|
|
41
|
+
}
|
|
42
|
+
const absolute = isAbsolute(pathOrSlug)
|
|
43
|
+
? resolve(pathOrSlug)
|
|
44
|
+
: resolve(repoRoot, pathOrSlug);
|
|
45
|
+
const sharedRoot = resolveScopeRoot(repoRoot, projectsRoot, 'shared');
|
|
46
|
+
if (resolveProjectScope(absolute, sharedRoot, repoRoot) !== 'synced') {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const child = relative(resolveScopeRoot(repoRoot, projectsRoot, 'synced'), absolute);
|
|
50
|
+
return child && !child.includes(sep) ? child : null;
|
|
51
|
+
}
|
|
52
|
+
export async function resolveLinksTarget(repoRoot, projectsRoot, pathOrSlug, env, git, resolveTarget) {
|
|
53
|
+
let resolved = null;
|
|
54
|
+
let resolutionError;
|
|
55
|
+
try {
|
|
56
|
+
resolved = await resolveTarget({ repoRoot, env }, pathOrSlug, { gitRunner: git }, { allowMissingCheckout: true });
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
resolutionError = error;
|
|
60
|
+
if (!(error instanceof CliError) ||
|
|
61
|
+
error.exitCode !== 1 ||
|
|
62
|
+
!error.message.startsWith('No synced project named ')) {
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const slug = pathOrSlug
|
|
67
|
+
? linksSlug(repoRoot, projectsRoot, pathOrSlug)
|
|
68
|
+
: resolved?.slug;
|
|
69
|
+
if (!slug) {
|
|
70
|
+
if (resolutionError)
|
|
71
|
+
throw resolutionError;
|
|
72
|
+
throw new CliError('No synced project is available for link rendering.', 1);
|
|
73
|
+
}
|
|
74
|
+
const target = resolved ?? buildSyncTarget(repoRoot, projectsRoot, slug);
|
|
75
|
+
const completedRef = completedSyncedRefName(slug);
|
|
76
|
+
const lookup = await git.run(['ls-remote', '--exit-code', target.remote, completedRef], { cwd: repoRoot, allowFailure: true });
|
|
77
|
+
if (classifyRemoteRefLookup(lookup, target.remote, completedRef) === 'absent') {
|
|
78
|
+
if (resolutionError)
|
|
79
|
+
throw resolutionError;
|
|
80
|
+
return { target, ref: target.ref };
|
|
81
|
+
}
|
|
82
|
+
const rows = lookup.stdout.split('\n').filter(Boolean);
|
|
83
|
+
const [sha, ref] = rows[0]?.trim().split(/\s+/) ?? [];
|
|
84
|
+
if (rows.length !== 1 ||
|
|
85
|
+
ref !== completedRef ||
|
|
86
|
+
!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(sha ?? '')) {
|
|
87
|
+
throw new CliError(`Unable to verify terminal links for ${slug}: origin returned a malformed completed-ref advertisement.`, 2);
|
|
88
|
+
}
|
|
89
|
+
const probe = await probeSyncedTerminalRefs(target, sha, git);
|
|
90
|
+
if (probe.state === 'wrong-sha') {
|
|
91
|
+
throw new CliError(`Cannot refresh links for ${slug}: active ${probe.activeRef} is ${probe.activeSha ?? 'absent'} while completed ${probe.completedRef} is ${probe.completedSha ?? 'absent'}. Repair the terminal ref mismatch first.`, 1);
|
|
92
|
+
}
|
|
93
|
+
if (probe.state === 'completed-only' || probe.state === 'both') {
|
|
94
|
+
return { target, ref: completedRef };
|
|
95
|
+
}
|
|
96
|
+
if (resolutionError)
|
|
97
|
+
throw resolutionError;
|
|
98
|
+
return { target, ref: target.ref };
|
|
99
|
+
}
|
|
33
100
|
async function runLinks(context, pathOrSlug, options, dependencies) {
|
|
34
101
|
try {
|
|
35
102
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
36
|
-
const
|
|
103
|
+
const projectsRoot = await dependencies.resolveProjectsRoot(repoRoot, dependencies.processEnv);
|
|
104
|
+
const { target, ref } = await dependencies.resolveLinksTarget(repoRoot, projectsRoot, pathOrSlug, dependencies.processEnv, dependencies.gitRunner, dependencies.resolveSyncedTarget);
|
|
37
105
|
const input = await dependencies.computeLinksInput(target, dependencies.gitRunner, {
|
|
38
106
|
durableSummaryPath: normalizeDurableSummaryPath(repoRoot, options.durableSummary),
|
|
39
107
|
now: dependencies.now(),
|
|
108
|
+
ref,
|
|
40
109
|
});
|
|
41
110
|
const markdown = renderLinksBlock(input);
|
|
42
111
|
if (context.json || options.format === 'json') {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type CommandContext, type GlobalOptions } from '../../app/command-context.js';
|
|
2
2
|
import { type GitRunner } from '../project/sync/git.js';
|
|
3
|
-
import { listSyncedRecords } from '../project/sync/record.js';
|
|
4
|
-
import {
|
|
3
|
+
import { listSyncedRecords, type SyncedProjectRecord } from '../project/sync/record.js';
|
|
4
|
+
import { buildSyncTarget } from '../project/sync/ref-sync.js';
|
|
5
|
+
import { probeSyncedTerminalRefs, type SyncedTerminalRefProbe } from '../project/sync/resolve-target.js';
|
|
6
|
+
import { type ProjectListRow, type ProjectSummary } from '@open-agent-toolkit/control-plane';
|
|
5
7
|
import { Command } from 'commander';
|
|
6
8
|
interface ProjectListDependencies {
|
|
7
9
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
@@ -9,6 +11,8 @@ interface ProjectListDependencies {
|
|
|
9
11
|
resolveProjectsRoot: (repoRoot: string, env: NodeJS.ProcessEnv) => Promise<string>;
|
|
10
12
|
listProjects: (projectsRoot: string) => Promise<ProjectSummary[]>;
|
|
11
13
|
listSyncedRecords: typeof listSyncedRecords;
|
|
14
|
+
probeSyncedTerminalRefs: typeof probeSyncedTerminalRefs;
|
|
15
|
+
probeAuthoritativeCompletedRef: typeof probeAuthoritativeCompletedRef;
|
|
12
16
|
directoryExists: (path: string) => Promise<boolean>;
|
|
13
17
|
readProjectMetadata: (projectPath: string) => Promise<ProjectListMetadata>;
|
|
14
18
|
processEnv: NodeJS.ProcessEnv;
|
|
@@ -19,6 +23,10 @@ interface ProjectListMetadata {
|
|
|
19
23
|
phase: string;
|
|
20
24
|
phaseStatus: string;
|
|
21
25
|
}
|
|
26
|
+
export declare function classifyLegacySyncedRecord(record: SyncedProjectRecord, path: string, probe?: SyncedTerminalRefProbe): Extract<ProjectListRow, {
|
|
27
|
+
kind: 'recorded-absent' | 'recorded-terminal' | 'terminal-invalid';
|
|
28
|
+
}>;
|
|
29
|
+
export declare function probeAuthoritativeCompletedRef(target: ReturnType<typeof buildSyncTarget>, git: GitRunner): Promise<SyncedTerminalRefProbe | null>;
|
|
22
30
|
export declare function createProjectListCommand(overrides?: Partial<ProjectListDependencies>): Command;
|
|
23
31
|
export {};
|
|
24
32
|
//# sourceMappingURL=list.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/project/list.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/project/list.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,iBAAiB,EACjB,KAAK,mBAAmB,EACzB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,eAAe,EAEhB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,uBAAuB,EACvB,KAAK,sBAAsB,EAC5B,MAAM,uCAAuC,CAAC;AAY/C,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAE5C,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAClE,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,8BAA8B,EAAE,OAAO,8BAA8B,CAAC;IACtE,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,mBAAmB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC3E,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AA6KD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,sBAAsB,GAC7B,OAAO,CACR,cAAc,EACd;IAAE,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,kBAAkB,CAAA;CAAE,CACvE,CA0FA;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EAC1C,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAwBxC;AA8TD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAwBT"}
|