@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
|
@@ -2,10 +2,12 @@ import { stat } from 'node:fs/promises';
|
|
|
2
2
|
import { basename, extname, isAbsolute, join, relative, resolve, sep, } from 'node:path';
|
|
3
3
|
import { buildCommandContext, } from '../../app/command-context.js';
|
|
4
4
|
import { defaultGitRunner } from '../project/sync/git.js';
|
|
5
|
-
import { listSyncedRecords } from '../project/sync/record.js';
|
|
5
|
+
import { listSyncedRecords, } from '../project/sync/record.js';
|
|
6
|
+
import { buildSyncTarget, classifyRemoteRefLookup, } from '../project/sync/ref-sync.js';
|
|
7
|
+
import { probeSyncedTerminalRefs, } from '../project/sync/resolve-target.js';
|
|
6
8
|
import { parseFrontmatterField } from '../shared/frontmatter.js';
|
|
7
9
|
import { resolveProjectsRoot } from '../shared/oat-paths.js';
|
|
8
|
-
import { PROJECT_SCOPES, resolveScopeRoot, } from '../shared/project-scope.js';
|
|
10
|
+
import { completedSyncedRefName, PROJECT_SCOPES, resolveScopeRoot, } from '../shared/project-scope.js';
|
|
9
11
|
import { readGlobalOptions } from '../shared/shared.utils.js';
|
|
10
12
|
import { CliError } from '../../errors/cli-error.js';
|
|
11
13
|
import { resolveProjectRoot } from '../../fs/paths.js';
|
|
@@ -17,6 +19,8 @@ const DEFAULT_DEPENDENCIES = {
|
|
|
17
19
|
resolveProjectsRoot,
|
|
18
20
|
listProjects,
|
|
19
21
|
listSyncedRecords,
|
|
22
|
+
probeSyncedTerminalRefs,
|
|
23
|
+
probeAuthoritativeCompletedRef,
|
|
20
24
|
directoryExists,
|
|
21
25
|
readProjectMetadata,
|
|
22
26
|
processEnv: process.env,
|
|
@@ -83,7 +87,13 @@ function formatProjectTable(projects) {
|
|
|
83
87
|
: '—'
|
|
84
88
|
: project.kind === 'recorded-invalid'
|
|
85
89
|
? 'restore record from Git'
|
|
86
|
-
:
|
|
90
|
+
: project.kind === 'recorded-terminal'
|
|
91
|
+
? project.archiveSnapshot
|
|
92
|
+
? 'retry completion cleanup'
|
|
93
|
+
: 'retry archive completion'
|
|
94
|
+
: project.kind === 'terminal-invalid'
|
|
95
|
+
? 'repair terminal ref mismatch'
|
|
96
|
+
: `oat project pull ${project.name}`,
|
|
87
97
|
}));
|
|
88
98
|
const widths = {
|
|
89
99
|
name: Math.max('NAME'.length, ...rows.map((row) => row.name.length)),
|
|
@@ -119,6 +129,127 @@ function formatProjectTable(projects) {
|
|
|
119
129
|
].join(' '));
|
|
120
130
|
return [header, divider, ...lines];
|
|
121
131
|
}
|
|
132
|
+
const ARCHIVE_RETRY_REASON = 'archive snapshot is incomplete; retry oat-project-complete before retiring terminal state';
|
|
133
|
+
const RETIREMENT_RETRY_REASON = 'archive is durable but legacy terminal cleanup remains; retry oat-project-complete';
|
|
134
|
+
const AUTHORITATIVE_COMPLETION_REASON = 'completed ref is authoritative; remove the stale local checkout or active record with oat-project-complete';
|
|
135
|
+
function terminalMismatchReason(activeSha, completedSha, expectedSha) {
|
|
136
|
+
return `terminal ref SHA mismatch: active ${activeSha ?? 'absent'}, completed ${completedSha ?? 'absent'}${expectedSha ? `, expected archived source ${expectedSha}` : ''}; repair the ref mismatch before retrying completion or prune`;
|
|
137
|
+
}
|
|
138
|
+
export function classifyLegacySyncedRecord(record, path, probe) {
|
|
139
|
+
if (probe?.state === 'wrong-sha') {
|
|
140
|
+
return {
|
|
141
|
+
kind: 'terminal-invalid',
|
|
142
|
+
name: record.slug,
|
|
143
|
+
path,
|
|
144
|
+
scope: 'synced',
|
|
145
|
+
checkout: 'invalid',
|
|
146
|
+
terminalState: 'ref-sha-mismatch',
|
|
147
|
+
activeRef: probe.activeRef,
|
|
148
|
+
completedRef: probe.completedRef,
|
|
149
|
+
activeSha: probe.activeSha,
|
|
150
|
+
completedSha: probe.completedSha,
|
|
151
|
+
expectedSha: probe.expectedSha,
|
|
152
|
+
phase: null,
|
|
153
|
+
phaseStatus: null,
|
|
154
|
+
workflowMode: null,
|
|
155
|
+
lifecycle: 'complete',
|
|
156
|
+
progress: null,
|
|
157
|
+
recommendation: {
|
|
158
|
+
skill: 'none',
|
|
159
|
+
reason: terminalMismatchReason(probe.activeSha, probe.completedSha, probe.expectedSha),
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (probe?.state === 'completed-only' || probe?.state === 'both') {
|
|
164
|
+
return {
|
|
165
|
+
kind: 'recorded-terminal',
|
|
166
|
+
name: record.slug,
|
|
167
|
+
path,
|
|
168
|
+
scope: 'synced',
|
|
169
|
+
checkout: 'absent',
|
|
170
|
+
terminalState: 'authoritative-completion',
|
|
171
|
+
archiveSnapshot: record.archiveSnapshot ?? null,
|
|
172
|
+
phase: null,
|
|
173
|
+
phaseStatus: null,
|
|
174
|
+
workflowMode: null,
|
|
175
|
+
lifecycle: 'complete',
|
|
176
|
+
progress: null,
|
|
177
|
+
recommendation: {
|
|
178
|
+
skill: 'none',
|
|
179
|
+
reason: AUTHORITATIVE_COMPLETION_REASON,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (record.status === 'complete') {
|
|
184
|
+
return {
|
|
185
|
+
kind: 'recorded-terminal',
|
|
186
|
+
name: record.slug,
|
|
187
|
+
path,
|
|
188
|
+
scope: 'synced',
|
|
189
|
+
checkout: 'absent',
|
|
190
|
+
terminalState: 'legacy-completion',
|
|
191
|
+
archiveSnapshot: record.archiveSnapshot ?? null,
|
|
192
|
+
phase: null,
|
|
193
|
+
phaseStatus: null,
|
|
194
|
+
workflowMode: null,
|
|
195
|
+
lifecycle: 'complete',
|
|
196
|
+
progress: null,
|
|
197
|
+
recommendation: {
|
|
198
|
+
skill: 'none',
|
|
199
|
+
reason: record.archiveSnapshot
|
|
200
|
+
? RETIREMENT_RETRY_REASON
|
|
201
|
+
: ARCHIVE_RETRY_REASON,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
kind: 'recorded-absent',
|
|
207
|
+
name: record.slug,
|
|
208
|
+
path,
|
|
209
|
+
scope: 'synced',
|
|
210
|
+
checkout: 'absent',
|
|
211
|
+
phase: null,
|
|
212
|
+
phaseStatus: null,
|
|
213
|
+
workflowMode: null,
|
|
214
|
+
lifecycle: null,
|
|
215
|
+
progress: null,
|
|
216
|
+
recommendation: {
|
|
217
|
+
skill: 'oat project pull',
|
|
218
|
+
reason: 'checkout absent',
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
export async function probeAuthoritativeCompletedRef(target, git) {
|
|
223
|
+
const completedRef = completedSyncedRefName(target.slug);
|
|
224
|
+
const lookup = await git.run(['ls-remote', '--exit-code', target.remote, completedRef], { cwd: target.repoRoot, allowFailure: true });
|
|
225
|
+
if (classifyRemoteRefLookup(lookup, target.remote, completedRef) === 'absent') {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const rows = lookup.stdout.split('\n').filter(Boolean);
|
|
229
|
+
const [sha, ref] = rows[0]?.trim().split(/\s+/) ?? [];
|
|
230
|
+
if (rows.length !== 1 ||
|
|
231
|
+
ref !== completedRef ||
|
|
232
|
+
!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(sha ?? '')) {
|
|
233
|
+
throw new CliError(`Unable to reconcile completed authority for ${target.slug}: origin returned a malformed completed-ref advertisement.`, 2);
|
|
234
|
+
}
|
|
235
|
+
return probeSyncedTerminalRefs(target, sha, git);
|
|
236
|
+
}
|
|
237
|
+
function classifyMaterializedTerminalRow(row, probe) {
|
|
238
|
+
const record = {
|
|
239
|
+
schemaVersion: 1,
|
|
240
|
+
slug: row.name,
|
|
241
|
+
scope: 'synced',
|
|
242
|
+
ref: probe.activeRef,
|
|
243
|
+
remote: 'origin',
|
|
244
|
+
status: 'active',
|
|
245
|
+
createdAt: new Date(0).toISOString(),
|
|
246
|
+
completedAt: null,
|
|
247
|
+
};
|
|
248
|
+
const classified = classifyLegacySyncedRecord(record, row.path, probe);
|
|
249
|
+
return classified.kind === 'recorded-terminal'
|
|
250
|
+
? { ...classified, checkout: 'present' }
|
|
251
|
+
: classified;
|
|
252
|
+
}
|
|
122
253
|
function displayPath(repoRoot, absolutePath) {
|
|
123
254
|
return relative(repoRoot, absolutePath).split(sep).join('/');
|
|
124
255
|
}
|
|
@@ -152,9 +283,6 @@ async function collectProjectRows(repoRoot, projectsRoot, options, context, depe
|
|
|
152
283
|
})));
|
|
153
284
|
}
|
|
154
285
|
if (root.scope === 'synced') {
|
|
155
|
-
const materialized = new Set(rows
|
|
156
|
-
.filter((row) => row.scope === 'synced' && row.kind === 'materialized')
|
|
157
|
-
.map((row) => row.name));
|
|
158
286
|
const records = await dependencies.listSyncedRecords(root.path, {
|
|
159
287
|
onInvalid: (path, error) => {
|
|
160
288
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -191,24 +319,24 @@ async function collectProjectRows(repoRoot, projectsRoot, options, context, depe
|
|
|
191
319
|
context.logger.warn(`Skipping invalid synced project record ${displayPath(repoRoot, path)}: ${message}`);
|
|
192
320
|
},
|
|
193
321
|
});
|
|
322
|
+
for (const row of rows.filter((candidate) => candidate.scope === 'synced' && candidate.kind === 'materialized')) {
|
|
323
|
+
const target = buildSyncTarget(repoRoot, projectsRoot, row.name);
|
|
324
|
+
const probe = await dependencies.probeAuthoritativeCompletedRef(target, dependencies.gitRunner);
|
|
325
|
+
if (!probe)
|
|
326
|
+
continue;
|
|
327
|
+
const index = rows.indexOf(row);
|
|
328
|
+
rows[index] = classifyMaterializedTerminalRow(row, probe);
|
|
329
|
+
}
|
|
330
|
+
const represented = new Set(rows.filter((row) => row.scope === 'synced').map((row) => row.name));
|
|
194
331
|
for (const record of records) {
|
|
195
|
-
if (!
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
phaseStatus: null,
|
|
204
|
-
workflowMode: null,
|
|
205
|
-
lifecycle: null,
|
|
206
|
-
progress: null,
|
|
207
|
-
recommendation: {
|
|
208
|
-
skill: 'oat project pull',
|
|
209
|
-
reason: 'checkout absent',
|
|
210
|
-
},
|
|
211
|
-
});
|
|
332
|
+
if (!represented.has(record.slug)) {
|
|
333
|
+
const target = buildSyncTarget(repoRoot, projectsRoot, record.slug);
|
|
334
|
+
const authoritative = await dependencies.probeAuthoritativeCompletedRef(target, dependencies.gitRunner);
|
|
335
|
+
const probe = authoritative ??
|
|
336
|
+
(record.archiveSourceRefSha
|
|
337
|
+
? await dependencies.probeSyncedTerminalRefs(target, record.archiveSourceRefSha, dependencies.gitRunner)
|
|
338
|
+
: undefined);
|
|
339
|
+
rows.push(classifyLegacySyncedRecord(record, displayPath(repoRoot, join(root.path, record.slug)), probe));
|
|
212
340
|
}
|
|
213
341
|
}
|
|
214
342
|
}
|
|
@@ -217,26 +345,74 @@ async function collectProjectRows(repoRoot, projectsRoot, options, context, depe
|
|
|
217
345
|
left.scope.localeCompare(right.scope));
|
|
218
346
|
}
|
|
219
347
|
async function appendRemoteRows(rows, repoRoot, context, dependencies) {
|
|
220
|
-
const remote = await dependencies.gitRunner.run(['ls-remote', 'origin', 'refs/oat/projects/*'], { cwd: repoRoot, allowFailure: true });
|
|
348
|
+
const remote = await dependencies.gitRunner.run(['ls-remote', 'origin', 'refs/oat/projects/*', 'refs/oat/completed/*'], { cwd: repoRoot, allowFailure: true });
|
|
221
349
|
if (remote.code !== 0) {
|
|
222
350
|
context.logger.warn(`Warning: unable to list remote synced projects: ${remote.stderr || remote.stdout || 'origin is unreachable'}`);
|
|
223
351
|
return rows;
|
|
224
352
|
}
|
|
225
353
|
const localSlugs = new Set(rows.filter((row) => row.scope === 'synced').map((row) => row.name));
|
|
354
|
+
const refs = new Map();
|
|
226
355
|
for (const line of remote.stdout.split('\n')) {
|
|
227
|
-
const [, ref] = line.trim().split(/\s+/);
|
|
228
|
-
|
|
356
|
+
const [sha, ref] = line.trim().split(/\s+/);
|
|
357
|
+
const activePrefix = 'refs/oat/projects/';
|
|
358
|
+
const completedPrefix = 'refs/oat/completed/';
|
|
359
|
+
const prefix = ref?.startsWith(activePrefix)
|
|
360
|
+
? activePrefix
|
|
361
|
+
: ref?.startsWith(completedPrefix)
|
|
362
|
+
? completedPrefix
|
|
363
|
+
: null;
|
|
364
|
+
if (!sha || !ref || !prefix)
|
|
365
|
+
continue;
|
|
366
|
+
const name = ref.slice(prefix.length);
|
|
367
|
+
if (!name)
|
|
368
|
+
continue;
|
|
369
|
+
const entry = refs.get(name) ?? {};
|
|
370
|
+
if (prefix === activePrefix) {
|
|
371
|
+
entry.activeRef = ref;
|
|
372
|
+
entry.activeSha = sha;
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
entry.completedRef = ref;
|
|
376
|
+
entry.completedSha = sha;
|
|
377
|
+
}
|
|
378
|
+
refs.set(name, entry);
|
|
379
|
+
}
|
|
380
|
+
for (const [name, refState] of refs) {
|
|
381
|
+
if (localSlugs.has(name) || !refState.activeRef)
|
|
382
|
+
continue;
|
|
383
|
+
if (refState.completedRef && refState.completedSha === refState.activeSha) {
|
|
229
384
|
continue;
|
|
230
|
-
|
|
231
|
-
if (
|
|
385
|
+
}
|
|
386
|
+
if (refState.completedRef && refState.completedSha && refState.activeSha) {
|
|
387
|
+
rows.push({
|
|
388
|
+
kind: 'terminal-invalid',
|
|
389
|
+
name,
|
|
390
|
+
scope: 'synced',
|
|
391
|
+
checkout: 'invalid',
|
|
392
|
+
terminalState: 'ref-sha-mismatch',
|
|
393
|
+
activeRef: refState.activeRef,
|
|
394
|
+
completedRef: refState.completedRef,
|
|
395
|
+
activeSha: refState.activeSha,
|
|
396
|
+
completedSha: refState.completedSha,
|
|
397
|
+
phase: null,
|
|
398
|
+
phaseStatus: null,
|
|
399
|
+
workflowMode: null,
|
|
400
|
+
lifecycle: 'complete',
|
|
401
|
+
progress: null,
|
|
402
|
+
recommendation: {
|
|
403
|
+
skill: 'none',
|
|
404
|
+
reason: terminalMismatchReason(refState.activeSha, refState.completedSha),
|
|
405
|
+
},
|
|
406
|
+
});
|
|
232
407
|
continue;
|
|
408
|
+
}
|
|
233
409
|
rows.push({
|
|
234
410
|
kind: 'remote',
|
|
235
411
|
name,
|
|
236
412
|
scope: 'synced',
|
|
237
413
|
origin: 'remote',
|
|
238
414
|
checkout: 'absent',
|
|
239
|
-
ref,
|
|
415
|
+
ref: refState.activeRef,
|
|
240
416
|
phase: null,
|
|
241
417
|
phaseStatus: null,
|
|
242
418
|
workflowMode: null,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile as defaultReadFile, writeFile as defaultWriteFile } from 'node:fs/promises';
|
|
2
2
|
import { buildCommandContext, type CommandContext } from '../../../app/command-context.js';
|
|
3
|
+
import { guardSyncedTerminalTarget } from '../../project/pull/index.js';
|
|
3
4
|
import { type GitRunner } from '../../project/sync/git.js';
|
|
4
5
|
import { readSyncedRecord } from '../../project/sync/record.js';
|
|
5
6
|
import { commitRecordChange as defaultCommitRecordChange, type PullResult, type PushResult, type AdoptionRecordState, type SyncTarget } from '../../project/sync/ref-sync.js';
|
|
@@ -12,6 +13,7 @@ interface ProjectOpenDependencies {
|
|
|
12
13
|
buildCommandContext: (options: Parameters<typeof buildCommandContext>[0]) => CommandContext;
|
|
13
14
|
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
14
15
|
resolveProjectsRoot: (repoRoot: string, env: NodeJS.ProcessEnv) => Promise<string>;
|
|
16
|
+
guardSyncedTerminalTarget: typeof guardSyncedTerminalTarget;
|
|
15
17
|
readOatLocalConfig: (repoRoot: string) => Promise<OatLocalConfig>;
|
|
16
18
|
writeOatLocalConfig: (repoRoot: string, config: OatLocalConfig) => Promise<void>;
|
|
17
19
|
setActiveProject: (repoRoot: string, path: string) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/open/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,eAAe,EAC3B,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EACL,kBAAkB,IAAI,yBAAyB,EAG/C,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,mBAAmB,EAEpB,MAAM,uCAAuC,CAAC;AAmB/C,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,cAAc,EAIpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAG/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,UAAU,uBAAuB;IAC/B,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,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,sBAAsB,EAAE,CAAC,OAAO,EAAE;QAChC,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnC,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,gBAAgB,EAAE,OAAO,gBAAgB,CAAC;IAC1C,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,UAAU,EAAE,CACV,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,cAAc,CAAC,EAAE,mBAAmB,CAAC;QACrC,GAAG,CAAC,EAAE,IAAI,CAAC;KACZ,KACE,OAAO,CAAC,UAAU,CAAC,CAAC;IACzB,UAAU,EAAE,CACV,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAC1B,OAAO,CAAC,UAAU,CAAC,CAAC;IACzB,kBAAkB,EAAE,OAAO,yBAAyB,CAAC;IACrD,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/open/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile as defaultReadFile, writeFile as defaultWriteFile, } from 'node:fs/promises';
|
|
2
2
|
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { buildCommandContext } from '../../../app/command-context.js';
|
|
4
|
+
import { guardSyncedTerminalTarget } from '../../project/pull/index.js';
|
|
4
5
|
import { defaultGitRunner } from '../../project/sync/git.js';
|
|
5
6
|
import { readSyncedRecord } from '../../project/sync/record.js';
|
|
6
7
|
import { commitRecordChange as defaultCommitRecordChange, pullSynced as defaultPullSynced, pushSynced as defaultPushSynced, } from '../../project/sync/ref-sync.js';
|
|
@@ -21,6 +22,7 @@ const DEFAULT_DEPENDENCIES = {
|
|
|
21
22
|
buildCommandContext,
|
|
22
23
|
resolveProjectRoot,
|
|
23
24
|
resolveProjectsRoot,
|
|
25
|
+
guardSyncedTerminalTarget,
|
|
24
26
|
readOatLocalConfig,
|
|
25
27
|
writeOatLocalConfig,
|
|
26
28
|
setActiveProject,
|
|
@@ -77,7 +79,18 @@ async function resolveProject(repoRoot, projectsRoot, input, dependencies) {
|
|
|
77
79
|
const projectName = basename(fullProjectPath);
|
|
78
80
|
let syncTarget;
|
|
79
81
|
if (scope === 'synced') {
|
|
80
|
-
|
|
82
|
+
try {
|
|
83
|
+
syncTarget = await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, fullProjectPath, {}, { allowMissingCheckout: true });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof CliError &&
|
|
87
|
+
error.exitCode === 1 &&
|
|
88
|
+
error.message.startsWith('No synced project named ')) {
|
|
89
|
+
await dependencies.guardSyncedTerminalTarget(repoRoot, projectsRoot, input, 'open', dependencies.gitRunner);
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
await dependencies.guardSyncedTerminalTarget(repoRoot, projectsRoot, input, 'open', dependencies.gitRunner);
|
|
81
94
|
if (!(await dependencies.dirExists(fullProjectPath)) ||
|
|
82
95
|
syncTarget.adopt) {
|
|
83
96
|
await materializeSyncedProject(repoRoot, syncTarget, dependencies);
|
|
@@ -124,12 +137,14 @@ async function resolveProject(repoRoot, projectsRoot, input, dependencies) {
|
|
|
124
137
|
}
|
|
125
138
|
catch (error) {
|
|
126
139
|
if (error instanceof CliError && error.exitCode === 1) {
|
|
140
|
+
await dependencies.guardSyncedTerminalTarget(repoRoot, projectsRoot, input, 'open', dependencies.gitRunner);
|
|
127
141
|
throw new CliError(`Project not found: ${input}`, 1);
|
|
128
142
|
}
|
|
129
143
|
throw error;
|
|
130
144
|
}
|
|
131
145
|
}
|
|
132
146
|
if (candidate.scope === 'synced') {
|
|
147
|
+
await dependencies.guardSyncedTerminalTarget(repoRoot, projectsRoot, input, 'open', dependencies.gitRunner);
|
|
133
148
|
syncTarget ??= await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, candidate.fullProjectPath, {}, { allowMissingCheckout: true });
|
|
134
149
|
if (!candidate.present || syncTarget.adopt) {
|
|
135
150
|
await materializeSyncedProject(repoRoot, syncTarget, dependencies);
|
|
@@ -1,21 +1,27 @@
|
|
|
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 PruneResult, type SyncTarget } from '../../project/sync/ref-sync.js';
|
|
4
|
-
import { resolveSyncedTarget } from '../../project/sync/resolve-target.js';
|
|
3
|
+
import { deleteCompletedSyncedRefForPrune, type PruneResult, type SyncTarget } from '../../project/sync/ref-sync.js';
|
|
4
|
+
import { resolveSyncedTarget, type SyncedTerminalRefProbe } from '../../project/sync/resolve-target.js';
|
|
5
|
+
import { resolveProjectsRoot } from '../../shared/oat-paths.js';
|
|
5
6
|
import { Command } from 'commander';
|
|
6
7
|
interface ProjectPruneDependencies {
|
|
7
8
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
8
9
|
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
10
|
+
resolveProjectsRoot: typeof resolveProjectsRoot;
|
|
9
11
|
resolveSyncedTarget: typeof resolveSyncedTarget;
|
|
12
|
+
inspectTerminalRefs: typeof inspectTerminalRefs;
|
|
13
|
+
deleteCompletedSyncedRefForPrune: typeof deleteCompletedSyncedRefForPrune;
|
|
10
14
|
pruneSynced: (target: SyncTarget, git: GitRunner, options: {
|
|
11
15
|
force: boolean;
|
|
12
16
|
commit: boolean;
|
|
17
|
+
expectedActiveAliasSha?: string;
|
|
13
18
|
}) => Promise<PruneResult>;
|
|
14
|
-
readProjectState: (target: SyncTarget, git: GitRunner) => Promise<string | null>;
|
|
19
|
+
readProjectState: (target: SyncTarget, git: GitRunner, ref?: string) => Promise<string | null>;
|
|
15
20
|
gitRunner: GitRunner;
|
|
16
21
|
processEnv: NodeJS.ProcessEnv;
|
|
17
22
|
}
|
|
18
|
-
export declare function readPruneProjectState(target: SyncTarget, git: GitRunner): Promise<string | null>;
|
|
23
|
+
export declare function readPruneProjectState(target: SyncTarget, git: GitRunner, ref?: string): Promise<string | null>;
|
|
24
|
+
export declare function inspectTerminalRefs(target: SyncTarget, git: GitRunner): Promise<SyncedTerminalRefProbe | null>;
|
|
19
25
|
export declare function createProjectPruneCommand(overrides?: Partial<ProjectPruneDependencies>): Command;
|
|
20
26
|
export {};
|
|
21
27
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/prune/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE9E,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/prune/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE9E,OAAO,EAGL,gCAAgC,EAEhC,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAEL,mBAAmB,EACnB,KAAK,sBAAsB,EAC5B,MAAM,uCAAuC,CAAC;AAK/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAUjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,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,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,gCAAgC,EAAE,OAAO,gCAAgC,CAAC;IAC1E,WAAW,EAAE,CACX,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,EAAE;QACP,KAAK,EAAE,OAAO,CAAC;QACf,MAAM,EAAE,OAAO,CAAC;QAChB,sBAAsB,CAAC,EAAE,MAAM,CAAC;KACjC,KACE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC1B,gBAAgB,EAAE,CAChB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,GAAG,CAAC,EAAE,MAAM,KACT,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5B,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,GAAG,SAAa,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA+BxB;AAyCD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CA+BxC;AAgGD,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAmBT"}
|
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
2
|
+
import { basename, isAbsolute, join, resolve } from 'node:path';
|
|
3
3
|
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
4
4
|
import { defaultGitRunner } from '../../project/sync/git.js';
|
|
5
5
|
import { readSyncedRecord } from '../../project/sync/record.js';
|
|
6
|
-
import { pruneSynced, } from '../../project/sync/ref-sync.js';
|
|
7
|
-
import { resolveSyncedTarget } from '../../project/sync/resolve-target.js';
|
|
6
|
+
import { buildSyncTarget, classifyRemoteRefLookup, deleteCompletedSyncedRefForPrune, pruneSynced, } from '../../project/sync/ref-sync.js';
|
|
7
|
+
import { probeSyncedTerminalRefs, resolveSyncedTarget, } from '../../project/sync/resolve-target.js';
|
|
8
8
|
import { getFrontmatterBlock, parseFrontmatterScalarFields, } from '../../shared/frontmatter.js';
|
|
9
|
-
import {
|
|
9
|
+
import { resolveProjectsRoot } from '../../shared/oat-paths.js';
|
|
10
|
+
import { completedSyncedRefName, resolveProjectScope, resolveScopeRoot, syncedRecordPath, } from '../../shared/project-scope.js';
|
|
10
11
|
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
11
12
|
import { CliError } from '../../../errors/cli-error.js';
|
|
12
13
|
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
13
14
|
import { Command } from 'commander';
|
|
14
|
-
export async function readPruneProjectState(target, git) {
|
|
15
|
+
export async function readPruneProjectState(target, git, ref = target.ref) {
|
|
15
16
|
try {
|
|
16
17
|
return await readFile(join(target.projectPath, 'state.md'), 'utf8');
|
|
17
18
|
}
|
|
18
19
|
catch {
|
|
19
20
|
// A completed or not-yet-pulled project may have no checkout.
|
|
20
21
|
}
|
|
21
|
-
const fetched = await git.run(['fetch', target.remote, `+${
|
|
22
|
+
const fetched = await git.run(['fetch', target.remote, `+${ref}:${ref}`], {
|
|
23
|
+
cwd: target.repoRoot,
|
|
24
|
+
allowFailure: true,
|
|
25
|
+
});
|
|
22
26
|
if (fetched.code === 0) {
|
|
23
|
-
const shown = await git.run(['show', `${
|
|
27
|
+
const shown = await git.run(['show', `${ref}:state.md`], {
|
|
24
28
|
cwd: target.repoRoot,
|
|
25
29
|
allowFailure: true,
|
|
26
30
|
});
|
|
@@ -39,7 +43,10 @@ export async function readPruneProjectState(target, git) {
|
|
|
39
43
|
const DEFAULT_DEPENDENCIES = {
|
|
40
44
|
buildCommandContext,
|
|
41
45
|
resolveProjectRoot,
|
|
46
|
+
resolveProjectsRoot,
|
|
42
47
|
resolveSyncedTarget,
|
|
48
|
+
inspectTerminalRefs,
|
|
49
|
+
deleteCompletedSyncedRefForPrune,
|
|
43
50
|
pruneSynced,
|
|
44
51
|
readProjectState: readPruneProjectState,
|
|
45
52
|
gitRunner: defaultGitRunner,
|
|
@@ -52,20 +59,83 @@ function hasOpenPr(state) {
|
|
|
52
59
|
const parsed = parseFrontmatterScalarFields(frontmatter, ['oat_pr_status']);
|
|
53
60
|
return parsed.valid && parsed.values.oat_pr_status === 'open';
|
|
54
61
|
}
|
|
62
|
+
function pruneSlug(repoRoot, projectsRoot, pathOrSlug) {
|
|
63
|
+
if (!pathOrSlug.includes('/') && !pathOrSlug.includes('\\')) {
|
|
64
|
+
return pathOrSlug;
|
|
65
|
+
}
|
|
66
|
+
const absolute = isAbsolute(pathOrSlug)
|
|
67
|
+
? resolve(pathOrSlug)
|
|
68
|
+
: resolve(repoRoot, pathOrSlug);
|
|
69
|
+
const scope = resolveProjectScope(absolute, resolveScopeRoot(repoRoot, projectsRoot, 'shared'), repoRoot);
|
|
70
|
+
return scope === 'synced' ? basename(absolute) : null;
|
|
71
|
+
}
|
|
72
|
+
export async function inspectTerminalRefs(target, git) {
|
|
73
|
+
const completedRef = completedSyncedRefName(target.slug);
|
|
74
|
+
const lookup = await git.run(['ls-remote', '--exit-code', target.remote, completedRef], { cwd: target.repoRoot, allowFailure: true });
|
|
75
|
+
if (classifyRemoteRefLookup(lookup, target.remote, completedRef) === 'absent') {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const rows = lookup.stdout.split('\n').filter(Boolean);
|
|
79
|
+
const [sha, ref] = rows[0]?.trim().split(/\s+/) ?? [];
|
|
80
|
+
if (rows.length !== 1 ||
|
|
81
|
+
ref !== completedRef ||
|
|
82
|
+
!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(sha ?? '')) {
|
|
83
|
+
throw new CliError(`Unable to verify terminal refs for ${target.slug}: origin returned a malformed completed-ref advertisement.`, 2);
|
|
84
|
+
}
|
|
85
|
+
const probe = await probeSyncedTerminalRefs(target, sha, git);
|
|
86
|
+
if (probe.state === 'wrong-sha') {
|
|
87
|
+
throw new CliError(`Refusing destructive prune for ${target.slug}: active ${probe.activeRef} is ${probe.activeSha ?? 'absent'} while completed ${probe.completedRef} is ${probe.completedSha ?? 'absent'}. Repair the terminal ref mismatch before retrying; both refs were retained.`, 1);
|
|
88
|
+
}
|
|
89
|
+
return probe;
|
|
90
|
+
}
|
|
55
91
|
async function runPrune(context, pathOrSlug, options, dependencies) {
|
|
56
92
|
try {
|
|
57
93
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
94
|
+
const projectsRoot = await dependencies.resolveProjectsRoot(repoRoot, dependencies.processEnv);
|
|
95
|
+
let target;
|
|
96
|
+
try {
|
|
97
|
+
target = await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, pathOrSlug, { gitRunner: dependencies.gitRunner }, {
|
|
98
|
+
allowMissingCheckout: true,
|
|
99
|
+
allowStagedPruneDeletion: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
const slug = pathOrSlug
|
|
104
|
+
? pruneSlug(repoRoot, projectsRoot, pathOrSlug)
|
|
105
|
+
: null;
|
|
106
|
+
if (!slug ||
|
|
107
|
+
!(error instanceof CliError) ||
|
|
108
|
+
error.exitCode !== 1 ||
|
|
109
|
+
!error.message.startsWith('No synced project named ')) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
target = buildSyncTarget(repoRoot, projectsRoot, slug);
|
|
113
|
+
}
|
|
114
|
+
const terminal = await dependencies.inspectTerminalRefs(target, dependencies.gitRunner);
|
|
115
|
+
const terminalRef = terminal?.completedRef;
|
|
116
|
+
const state = await dependencies.readProjectState(target, dependencies.gitRunner, terminalRef);
|
|
63
117
|
if (!options.force && hasOpenPr(state)) {
|
|
64
118
|
throw new CliError(`Refusing to prune ${target.slug} while its PR is open. Pass --force to confirm; pinned links will stop resolving.`, 1);
|
|
65
119
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
120
|
+
const removedRefs = terminal?.state === 'completed-only'
|
|
121
|
+
? terminal.completedRef
|
|
122
|
+
: terminalRef
|
|
123
|
+
? `${target.ref} and ${terminalRef}`
|
|
124
|
+
: target.ref;
|
|
125
|
+
context.logger.warn(`Pruning ${target.slug} removes ${removedRefs}; pinned links will stop resolving. Durable local/S3 archives are preserved.`);
|
|
126
|
+
const result = await dependencies.pruneSynced(target, dependencies.gitRunner, {
|
|
127
|
+
force: options.force,
|
|
128
|
+
commit: options.commit,
|
|
129
|
+
...(terminal ? { expectedActiveAliasSha: terminal.expectedSha } : {}),
|
|
130
|
+
});
|
|
131
|
+
const completedDeletion = terminalRef
|
|
132
|
+
? await dependencies.deleteCompletedSyncedRefForPrune(target, dependencies.gitRunner)
|
|
133
|
+
: null;
|
|
134
|
+
const payload = {
|
|
135
|
+
...result,
|
|
136
|
+
ref: target.ref,
|
|
137
|
+
completedRef: completedDeletion?.completedRef ?? null,
|
|
138
|
+
};
|
|
69
139
|
if (context.json)
|
|
70
140
|
context.logger.json(payload);
|
|
71
141
|
else
|
|
@@ -2,10 +2,13 @@ import { type CommandContext, type GlobalOptions } from '../../../app/command-co
|
|
|
2
2
|
import { type GitRunner } from '../../project/sync/git.js';
|
|
3
3
|
import { commitRecordChange as defaultCommitRecordChange, type PullChildResult, type PullResult, type AdoptionRecordState, type SyncTarget } from '../../project/sync/ref-sync.js';
|
|
4
4
|
import { resolveSyncedTarget } from '../../project/sync/resolve-target.js';
|
|
5
|
+
import { resolveProjectsRoot } from '../../shared/oat-paths.js';
|
|
5
6
|
import { Command } from 'commander';
|
|
6
7
|
interface ProjectPullDependencies {
|
|
7
8
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
8
9
|
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
10
|
+
resolveProjectsRoot: typeof resolveProjectsRoot;
|
|
11
|
+
guardSyncedTerminalTarget: typeof guardSyncedTerminalTarget;
|
|
9
12
|
resolveSyncedTarget: typeof resolveSyncedTarget;
|
|
10
13
|
pullSynced: (target: SyncTarget, git: GitRunner, options?: {
|
|
11
14
|
adopt?: boolean;
|
|
@@ -23,6 +26,7 @@ interface ProjectPullDependencies {
|
|
|
23
26
|
gitRunner: GitRunner;
|
|
24
27
|
processEnv: NodeJS.ProcessEnv;
|
|
25
28
|
}
|
|
29
|
+
export declare function guardSyncedTerminalTarget(repoRoot: string, projectsRoot: string, pathOrSlug: string | undefined, action: 'pull' | 'open', git: GitRunner): Promise<void>;
|
|
26
30
|
export declare function createProjectPullCommand(overrides?: Partial<ProjectPullDependencies>): Command;
|
|
27
31
|
export {};
|
|
28
32
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/pull/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/pull/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;AAE9E,OAAO,EAEL,kBAAkB,IAAI,yBAAyB,EAI/C,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,UAAU,EAGhB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAEL,mBAAmB,EAEpB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAUjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,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,OAAO,mBAAmB,CAAC;IAChD,yBAAyB,EAAE,OAAO,yBAAyB,CAAC;IAC5D,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,UAAU,EAAE,CACV,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,cAAc,CAAC,EAAE,mBAAmB,CAAC;QACrC,GAAG,CAAC,EAAE,IAAI,CAAC;KACZ,KACE,OAAO,CAAC,UAAU,CAAC,CAAC;IACzB,YAAY,EAAE,CACZ,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,KACX,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAChC,kBAAkB,EAAE,OAAO,yBAAyB,CAAC;IACrD,cAAc,EAAE,CACd,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,cAAc,CAAC,EAAE,mBAAmB,CAAC;QACrC,GAAG,CAAC,EAAE,IAAI,CAAC;KACZ,KACE,OAAO,CAAC,UAAU,CAAC,CAAC;IACzB,WAAW,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAgED,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,IAAI,CAAC,CA8Df;AA6LD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAqBT"}
|