@open-agent-toolkit/cli 0.2.40 → 0.2.45
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/agents/oat-codebase-mapper.md +15 -3
- package/assets/agents/oat-phase-implementer.md +34 -9
- package/assets/agents/oat-reviewer.md +2 -2
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/configuration.md +11 -1
- package/assets/docs/cli-utilities/tool-packs.md +8 -0
- package/assets/docs/contributing/skills.md +67 -14
- package/assets/docs/quickstart.md +9 -0
- package/assets/docs/reference/cli-reference.md +56 -1
- package/assets/docs/reference/file-locations.md +18 -1
- package/assets/docs/reference/oat-directory-structure.md +20 -4
- package/assets/docs/workflows/projects/artifacts.md +27 -1
- package/assets/docs/workflows/projects/implementation-execution.md +22 -0
- package/assets/docs/workflows/projects/index.md +6 -0
- package/assets/docs/workflows/projects/lifecycle.md +19 -5
- package/assets/docs/workflows/projects/picking-up-projects.md +75 -0
- package/assets/docs/workflows/projects/pr-flow.md +22 -1
- package/assets/docs/workflows/projects/reviewing-oat-prs.md +60 -0
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/analyze/SKILL.md +13 -5
- package/assets/skills/compare/SKILL.md +11 -2
- package/assets/skills/oat-brainstorm/SKILL.md +135 -32
- package/assets/skills/oat-brainstorm/references/destinations.md +9 -6
- package/assets/skills/oat-cursor-cloud-projects/SKILL.md +11 -1
- package/assets/skills/oat-dispatch-subagents/SKILL.md +19 -6
- package/assets/skills/oat-doctor/SKILL.md +8 -1
- package/assets/skills/oat-project-autonomous/SKILL.md +45 -1
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +8 -8
- package/assets/skills/oat-project-capture/SKILL.md +29 -9
- package/assets/skills/oat-project-complete/SKILL.md +610 -38
- package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +265 -0
- package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +764 -0
- package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +134 -0
- package/assets/skills/oat-project-complete/scripts/validate-nonarchive-lifecycle-receipt.mjs +160 -0
- package/assets/skills/oat-project-design/SKILL.md +33 -9
- package/assets/skills/oat-project-discover/SKILL.md +16 -3
- package/assets/skills/oat-project-document/SKILL.md +10 -4
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +8 -8
- package/assets/skills/oat-project-implement/SKILL.md +14 -3
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +18 -6
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +8 -8
- package/assets/skills/oat-project-implement/references/phase-execution.md +9 -3
- package/assets/skills/oat-project-implement/references/plan-and-resume.md +3 -0
- package/assets/skills/oat-project-import-plan/SKILL.md +15 -9
- package/assets/skills/oat-project-new/SKILL.md +7 -5
- package/assets/skills/oat-project-next/SKILL.md +32 -10
- package/assets/skills/oat-project-plan/SKILL.md +18 -3
- package/assets/skills/oat-project-pr-final/SKILL.md +23 -2
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +8 -8
- package/assets/skills/oat-project-pr-progress/SKILL.md +23 -2
- package/assets/skills/oat-project-progress/SKILL.md +23 -9
- package/assets/skills/oat-project-promote-spec-driven/SKILL.md +20 -2
- package/assets/skills/oat-project-quick-start/SKILL.md +48 -18
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +8 -8
- package/assets/skills/oat-project-reconcile/SKILL.md +14 -10
- package/assets/skills/oat-project-retro/SKILL.md +3 -3
- package/assets/skills/oat-project-retro/references/apply-procedure.md +102 -6
- package/assets/skills/oat-project-retro-file/SKILL.md +57 -6
- package/assets/skills/oat-project-review-provide/SKILL.md +56 -6
- package/assets/skills/oat-project-review-receive/SKILL.md +16 -7
- package/assets/skills/oat-project-review-receive-remote/SKILL.md +9 -3
- package/assets/skills/oat-project-revise/SKILL.md +12 -3
- package/assets/skills/oat-project-spec/SKILL.md +16 -3
- package/assets/skills/oat-project-summary/SKILL.md +113 -13
- package/assets/skills/oat-repo-improve/SKILL.md +18 -7
- package/assets/skills/oat-review-provide-remote/SKILL.md +11 -2
- package/assets/skills/oat-wave-execute/SKILL.md +34 -2
- package/assets/skills/oat-worktree-bootstrap/SKILL.md +13 -1
- package/assets/skills/oat-worktree-bootstrap-auto/SKILL.md +13 -1
- package/dist/commands/config/index.d.ts +1 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +30 -2
- package/dist/commands/doctor/index.d.ts +2 -0
- package/dist/commands/doctor/index.d.ts.map +1 -1
- package/dist/commands/doctor/index.js +27 -3
- package/dist/commands/doctor/synced-projects.d.ts +12 -0
- package/dist/commands/doctor/synced-projects.d.ts.map +1 -0
- package/dist/commands/doctor/synced-projects.js +208 -0
- package/dist/commands/init/gitattributes.d.ts +4 -0
- package/dist/commands/init/gitattributes.d.ts.map +1 -0
- package/dist/commands/init/gitattributes.js +12 -0
- package/dist/commands/init/gitignore.d.ts +8 -1
- package/dist/commands/init/gitignore.d.ts.map +1 -1
- package/dist/commands/init/gitignore.js +146 -41
- package/dist/commands/init/index.d.ts +2 -0
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +3 -0
- package/dist/commands/init/managed-block.d.ts +10 -0
- package/dist/commands/init/managed-block.d.ts.map +1 -0
- package/dist/commands/init/managed-block.js +40 -0
- package/dist/commands/init/tools/workflows/install-workflows.js +1 -1
- package/dist/commands/local/index.d.ts.map +1 -1
- package/dist/commands/local/index.js +5 -1
- package/dist/commands/local/sync.d.ts +4 -0
- package/dist/commands/local/sync.d.ts.map +1 -1
- package/dist/commands/local/sync.js +123 -2
- package/dist/commands/pjm/adoption.d.ts +2 -0
- package/dist/commands/pjm/adoption.d.ts.map +1 -1
- package/dist/commands/pjm/adoption.js +1 -1
- package/dist/commands/project/archive/archive-utils.d.ts +24 -0
- package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
- package/dist/commands/project/archive/archive-utils.js +349 -22
- package/dist/commands/project/archive/index.d.ts.map +1 -1
- package/dist/commands/project/archive/index.js +1 -0
- package/dist/commands/project/archive/push-runner.d.ts +5 -1
- package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
- package/dist/commands/project/archive/push-runner.js +33 -4
- package/dist/commands/project/complete-state/index.d.ts.map +1 -1
- package/dist/commands/project/complete-state/index.js +3 -1
- package/dist/commands/project/index.d.ts.map +1 -1
- package/dist/commands/project/index.js +12 -0
- package/dist/commands/project/links/compute.d.ts +9 -0
- package/dist/commands/project/links/compute.d.ts.map +1 -0
- package/dist/commands/project/links/compute.js +27 -0
- package/dist/commands/project/links/index.d.ts +17 -0
- package/dist/commands/project/links/index.d.ts.map +1 -0
- package/dist/commands/project/links/index.js +72 -0
- package/dist/commands/project/links/refresh.d.ts +26 -0
- package/dist/commands/project/links/refresh.d.ts.map +1 -0
- package/dist/commands/project/links/refresh.js +92 -0
- package/dist/commands/project/links/render.d.ts +24 -0
- package/dist/commands/project/links/render.d.ts.map +1 -0
- package/dist/commands/project/links/render.js +68 -0
- package/dist/commands/project/list.d.ts +5 -0
- package/dist/commands/project/list.d.ts.map +1 -1
- package/dist/commands/project/list.js +182 -9
- package/dist/commands/project/migrate/index.d.ts +22 -0
- package/dist/commands/project/migrate/index.d.ts.map +1 -0
- package/dist/commands/project/migrate/index.js +79 -0
- package/dist/commands/project/new/index.d.ts +2 -0
- package/dist/commands/project/new/index.d.ts.map +1 -1
- package/dist/commands/project/new/index.js +14 -2
- package/dist/commands/project/new/scaffold.d.ts +22 -1
- package/dist/commands/project/new/scaffold.d.ts.map +1 -1
- package/dist/commands/project/new/scaffold.js +287 -69
- package/dist/commands/project/open/index.d.ts +16 -0
- package/dist/commands/project/open/index.d.ts.map +1 -1
- package/dist/commands/project/open/index.js +151 -19
- package/dist/commands/project/pause/index.d.ts +8 -0
- package/dist/commands/project/pause/index.d.ts.map +1 -1
- package/dist/commands/project/pause/index.js +129 -19
- package/dist/commands/project/prune/index.d.ts +21 -0
- package/dist/commands/project/prune/index.d.ts.map +1 -0
- package/dist/commands/project/prune/index.js +95 -0
- package/dist/commands/project/pull/index.d.ts +28 -0
- package/dist/commands/project/pull/index.d.ts.map +1 -0
- package/dist/commands/project/pull/index.js +157 -0
- package/dist/commands/project/push/index.d.ts +22 -0
- package/dist/commands/project/push/index.d.ts.map +1 -0
- package/dist/commands/project/push/index.js +100 -0
- package/dist/commands/project/scope/index.d.ts +17 -0
- package/dist/commands/project/scope/index.d.ts.map +1 -0
- package/dist/commands/project/scope/index.js +107 -0
- package/dist/commands/project/split/run.d.ts +4 -0
- package/dist/commands/project/split/run.d.ts.map +1 -1
- package/dist/commands/project/split/run.js +48 -10
- package/dist/commands/project/sync/git.d.ts +18 -0
- package/dist/commands/project/sync/git.d.ts.map +1 -0
- package/dist/commands/project/sync/git.js +60 -0
- package/dist/commands/project/sync/record.d.ts +22 -0
- package/dist/commands/project/sync/record.d.ts.map +1 -0
- package/dist/commands/project/sync/record.js +133 -0
- package/dist/commands/project/sync/ref-sync.d.ts +120 -0
- package/dist/commands/project/sync/ref-sync.d.ts.map +1 -0
- package/dist/commands/project/sync/ref-sync.js +962 -0
- package/dist/commands/project/sync/resolve-target.d.ts +27 -0
- package/dist/commands/project/sync/resolve-target.d.ts.map +1 -0
- package/dist/commands/project/sync/resolve-target.js +159 -0
- package/dist/commands/shared/oat-paths.d.ts +4 -1
- package/dist/commands/shared/oat-paths.d.ts.map +1 -1
- package/dist/commands/shared/oat-paths.js +2 -2
- package/dist/commands/shared/project-scope.d.ts +23 -0
- package/dist/commands/shared/project-scope.d.ts.map +1 -0
- package/dist/commands/shared/project-scope.js +106 -0
- package/dist/commands/tools/migrate/index.js +1 -1
- package/dist/commands/tools/shared/pack-inventory.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-inventory.js +11 -0
- package/dist/commands/tools/shared/pack-lifecycle.js +1 -1
- package/dist/commands/tools/shared/pack-reconcile.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-reconcile.js +4 -1
- package/dist/commands/tools/update/index.d.ts.map +1 -1
- package/dist/commands/tools/update/index.js +20 -10
- package/dist/commands/tools/update/update-tools.d.ts +2 -0
- package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
- package/dist/config/oat-config.d.ts +6 -3
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +41 -7
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +2 -1
- package/dist/fs/io.d.ts +2 -1
- package/dist/fs/io.d.ts.map +1 -1
- package/dist/fs/io.js +5 -2
- package/dist/projects/split/finalize.d.ts.map +1 -1
- package/dist/projects/split/finalize.js +55 -2
- package/dist/projects/split/resume.d.ts +3 -0
- package/dist/projects/split/resume.d.ts.map +1 -1
- package/dist/projects/split/resume.js +34 -7
- package/dist/projects/split/seed-children.d.ts.map +1 -1
- package/dist/projects/split/seed-children.js +6 -0
- package/dist/projects/split/write-parent.d.ts +7 -0
- package/dist/projects/split/write-parent.d.ts.map +1 -1
- package/dist/projects/split/write-parent.js +7 -4
- package/dist/release/public-package-contract.d.ts.map +1 -1
- package/dist/release/public-package-contract.js +1 -0
- package/dist/validation/skills.d.ts.map +1 -1
- package/dist/validation/skills.js +434 -0
- package/package.json +4 -4
- package/dist/__tests__/skills/split-flow-fixtures.d.ts +0 -50
- package/dist/__tests__/skills/split-flow-fixtures.d.ts.map +0 -1
- package/dist/__tests__/skills/split-flow-fixtures.js +0 -161
- package/dist/commands/__tests__/helpers.d.ts +0 -12
- package/dist/commands/__tests__/helpers.d.ts.map +0 -1
- package/dist/commands/__tests__/helpers.js +0 -36
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
2
|
+
import { defaultGitRunner } from '../../project/sync/git.js';
|
|
3
|
+
import { abortSynced as defaultAbortSynced, commitRecordChange as defaultCommitRecordChange, continueSynced as defaultContinueSynced, pullChildren as defaultPullChildren, pullSynced as defaultPullSynced, } from '../../project/sync/ref-sync.js';
|
|
4
|
+
import { resolveSyncedTarget, } from '../../project/sync/resolve-target.js';
|
|
5
|
+
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
6
|
+
import { CliError } from '../../../errors/cli-error.js';
|
|
7
|
+
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
const DEFAULT_DEPENDENCIES = {
|
|
10
|
+
buildCommandContext,
|
|
11
|
+
resolveProjectRoot,
|
|
12
|
+
resolveSyncedTarget,
|
|
13
|
+
pullSynced: defaultPullSynced,
|
|
14
|
+
pullChildren: defaultPullChildren,
|
|
15
|
+
commitRecordChange: defaultCommitRecordChange,
|
|
16
|
+
continueSynced: defaultContinueSynced,
|
|
17
|
+
abortSynced: defaultAbortSynced,
|
|
18
|
+
gitRunner: defaultGitRunner,
|
|
19
|
+
processEnv: process.env,
|
|
20
|
+
};
|
|
21
|
+
function shellQuote(value) {
|
|
22
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
23
|
+
}
|
|
24
|
+
function reportPullResult(context, result, target, targetArg, children = []) {
|
|
25
|
+
const payload = {
|
|
26
|
+
...result,
|
|
27
|
+
adopted: result.adopted ?? false,
|
|
28
|
+
ref: target.ref,
|
|
29
|
+
children,
|
|
30
|
+
};
|
|
31
|
+
if (context.json) {
|
|
32
|
+
context.logger.json(payload);
|
|
33
|
+
}
|
|
34
|
+
else if (result.status === 'created' ||
|
|
35
|
+
result.status === 'updated' ||
|
|
36
|
+
result.status === 'up-to-date') {
|
|
37
|
+
context.logger.info(`Pull ${result.status}: ${target.slug} at ${result.sha}.`);
|
|
38
|
+
}
|
|
39
|
+
else if (result.status === 'conflict') {
|
|
40
|
+
context.logger.error(`Pull ${result.status}${result.conflicts?.length ? ` (${result.conflicts.join(', ')})` : ''}. Resolve the files, then run oat project pull ${shellQuote(targetArg)} --continue; or run oat project pull ${shellQuote(targetArg)} --abort.`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
context.logger.error(`Pull dirty. Commit and publish with oat project push ${shellQuote(targetArg)}, or explicitly stash/clean the synced checkout before retrying oat project pull ${shellQuote(targetArg)}.`);
|
|
44
|
+
}
|
|
45
|
+
if (!context.json) {
|
|
46
|
+
for (const child of children) {
|
|
47
|
+
if (child.status !== 'missing' &&
|
|
48
|
+
child.status !== 'error' &&
|
|
49
|
+
child.status !== 'conflict' &&
|
|
50
|
+
child.status !== 'dirty') {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const childArg = shellQuote(child.slug);
|
|
54
|
+
const diagnostic = child.message ??
|
|
55
|
+
(child.conflicts?.length
|
|
56
|
+
? `conflicts: ${child.conflicts.join(', ')}`
|
|
57
|
+
: 'no diagnostic provided');
|
|
58
|
+
const retry = child.status === 'conflict'
|
|
59
|
+
? `Resolve its files, then run oat project pull ${childArg} --continue; or oat project pull ${childArg} --abort.`
|
|
60
|
+
: child.status === 'dirty'
|
|
61
|
+
? `Run oat project push ${childArg}, or explicitly stash/clean it before retrying oat project pull ${childArg}.`
|
|
62
|
+
: child.status === 'error'
|
|
63
|
+
? `Repair the reported Git/system failure, then retry oat project pull ${childArg}.`
|
|
64
|
+
: `Retry with oat project pull ${childArg}.`;
|
|
65
|
+
context.logger.error(`Child ${child.slug} ${child.status}: ${diagnostic}. ${retry}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const childExitCode = children.reduce((highest, child) => {
|
|
69
|
+
const current = child.exitCode ??
|
|
70
|
+
(child.status === 'error'
|
|
71
|
+
? 2
|
|
72
|
+
: child.status === 'missing' ||
|
|
73
|
+
child.status === 'conflict' ||
|
|
74
|
+
child.status === 'dirty'
|
|
75
|
+
? 1
|
|
76
|
+
: 0);
|
|
77
|
+
return current > highest ? current : highest;
|
|
78
|
+
}, 0);
|
|
79
|
+
process.exitCode =
|
|
80
|
+
result.status === 'created' ||
|
|
81
|
+
result.status === 'updated' ||
|
|
82
|
+
result.status === 'up-to-date'
|
|
83
|
+
? childExitCode
|
|
84
|
+
: 1;
|
|
85
|
+
}
|
|
86
|
+
async function runPull(context, pathOrSlug, options, dependencies) {
|
|
87
|
+
try {
|
|
88
|
+
if (options.continue && options.abort) {
|
|
89
|
+
throw new CliError('`--continue` and `--abort` are mutually exclusive.', 1);
|
|
90
|
+
}
|
|
91
|
+
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
92
|
+
const target = await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, pathOrSlug, {}, { allowMissingCheckout: !options.continue && !options.abort });
|
|
93
|
+
const targetArg = pathOrSlug ?? target.projectPath;
|
|
94
|
+
if (options.abort) {
|
|
95
|
+
await dependencies.abortSynced(target, dependencies.gitRunner);
|
|
96
|
+
if (context.json) {
|
|
97
|
+
context.logger.json({ status: 'aborted', ref: target.ref });
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
context.logger.info(`Aborted pull recovery for ${target.slug}.`);
|
|
101
|
+
}
|
|
102
|
+
process.exitCode = 0;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const result = options.continue
|
|
106
|
+
? await dependencies.continueSynced(target, dependencies.gitRunner, {
|
|
107
|
+
adopt: target.adoptionRecord !== 'durable',
|
|
108
|
+
adoptionRecord: target.adoptionRecord,
|
|
109
|
+
})
|
|
110
|
+
: await dependencies.pullSynced(target, dependencies.gitRunner, {
|
|
111
|
+
adopt: target.adopt,
|
|
112
|
+
adoptionRecord: target.adoptionRecord,
|
|
113
|
+
});
|
|
114
|
+
const successful = result.status === 'created' ||
|
|
115
|
+
result.status === 'updated' ||
|
|
116
|
+
result.status === 'up-to-date';
|
|
117
|
+
const children = successful && options.children !== false
|
|
118
|
+
? await dependencies.pullChildren(target, dependencies.gitRunner)
|
|
119
|
+
: [];
|
|
120
|
+
const pendingRecordPaths = [
|
|
121
|
+
...(result.pendingRecordPaths ?? []),
|
|
122
|
+
...children.flatMap((child) => child.pendingRecordPaths ?? []),
|
|
123
|
+
];
|
|
124
|
+
if (options.commit !== false && pendingRecordPaths.length > 0) {
|
|
125
|
+
const slugs = [
|
|
126
|
+
...(result.adopted ? [target.slug] : []),
|
|
127
|
+
...children.filter((child) => child.adopted).map((child) => child.slug),
|
|
128
|
+
];
|
|
129
|
+
await dependencies.commitRecordChange(repoRoot, pendingRecordPaths, `chore(oat): adopt synced project${slugs.length === 1 ? '' : 's'} ${slugs.join(', ')}`, dependencies.gitRunner, { projectRoots: target });
|
|
130
|
+
}
|
|
131
|
+
reportPullResult(context, result, target, targetArg, children);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
135
|
+
if (context.json) {
|
|
136
|
+
context.logger.json({ status: 'error', message });
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
context.logger.error(message);
|
|
140
|
+
}
|
|
141
|
+
process.exitCode = error instanceof CliError ? error.exitCode : 2;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function createProjectPullCommand(overrides = {}) {
|
|
145
|
+
const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
|
|
146
|
+
return new Command('pull')
|
|
147
|
+
.description('Materialize or update a synced OAT project')
|
|
148
|
+
.argument('[project-path|slug]', 'Synced project path or slug')
|
|
149
|
+
.option('--continue', 'Continue a resolved sync rebase')
|
|
150
|
+
.option('--abort', 'Abort an in-progress sync rebase')
|
|
151
|
+
.option('--no-children', 'Do not pull coordination child projects')
|
|
152
|
+
.option('--no-commit', 'Leave adopted discovery records uncommitted')
|
|
153
|
+
.action(async (pathOrSlug, options, command) => {
|
|
154
|
+
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
155
|
+
await runPull(context, pathOrSlug, options, dependencies);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
3
|
+
import { refreshPrLinks } from '../../project/links/refresh.js';
|
|
4
|
+
import { type GitRunner } from '../../project/sync/git.js';
|
|
5
|
+
import { type PushResult, type SyncTarget } from '../../project/sync/ref-sync.js';
|
|
6
|
+
import { resolveSyncedTarget } from '../../project/sync/resolve-target.js';
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
interface ProjectPushDependencies {
|
|
9
|
+
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
10
|
+
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
11
|
+
resolveSyncedTarget: typeof resolveSyncedTarget;
|
|
12
|
+
pushSynced: (target: SyncTarget, git: GitRunner, options: {
|
|
13
|
+
message?: string;
|
|
14
|
+
}) => Promise<PushResult>;
|
|
15
|
+
gitRunner: GitRunner;
|
|
16
|
+
processEnv: NodeJS.ProcessEnv;
|
|
17
|
+
refreshPrLinks: typeof refreshPrLinks;
|
|
18
|
+
readFile: typeof readFile;
|
|
19
|
+
}
|
|
20
|
+
export declare function createProjectPushCommand(overrides?: Partial<ProjectPushDependencies>): Command;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/push/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAQ5E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,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,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,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,cAAc,EAAE,OAAO,cAAc,CAAC;IACtC,QAAQ,EAAE,OAAO,QAAQ,CAAC;CAC3B;AAqGD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAmBT"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
4
|
+
import { refreshPrLinks } from '../../project/links/refresh.js';
|
|
5
|
+
import { defaultGitRunner } from '../../project/sync/git.js';
|
|
6
|
+
import { pushSynced as defaultPushSynced, } from '../../project/sync/ref-sync.js';
|
|
7
|
+
import { resolveSyncedTarget } from '../../project/sync/resolve-target.js';
|
|
8
|
+
import { getFrontmatterBlock, parseFrontmatterScalarFields, } from '../../shared/frontmatter.js';
|
|
9
|
+
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
10
|
+
import { CliError } from '../../../errors/cli-error.js';
|
|
11
|
+
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
12
|
+
import { Command } from 'commander';
|
|
13
|
+
const DEFAULT_DEPENDENCIES = {
|
|
14
|
+
buildCommandContext,
|
|
15
|
+
resolveProjectRoot,
|
|
16
|
+
resolveSyncedTarget,
|
|
17
|
+
pushSynced: defaultPushSynced,
|
|
18
|
+
gitRunner: defaultGitRunner,
|
|
19
|
+
processEnv: process.env,
|
|
20
|
+
refreshPrLinks,
|
|
21
|
+
readFile,
|
|
22
|
+
};
|
|
23
|
+
function shellQuote(value) {
|
|
24
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
25
|
+
}
|
|
26
|
+
async function runPush(context, pathOrSlug, options, dependencies) {
|
|
27
|
+
try {
|
|
28
|
+
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
29
|
+
const target = await dependencies.resolveSyncedTarget({ repoRoot, env: dependencies.processEnv }, pathOrSlug);
|
|
30
|
+
const result = await dependencies.pushSynced(target, dependencies.gitRunner, { message: options.message });
|
|
31
|
+
let prRefresh;
|
|
32
|
+
if (options.refreshPr &&
|
|
33
|
+
(result.status === 'pushed' || result.status === 'up-to-date')) {
|
|
34
|
+
const state = await dependencies
|
|
35
|
+
.readFile(join(target.projectPath, 'state.md'), 'utf8')
|
|
36
|
+
.catch(() => null);
|
|
37
|
+
const frontmatter = state ? getFrontmatterBlock(state) : null;
|
|
38
|
+
if (frontmatter) {
|
|
39
|
+
const parsed = parseFrontmatterScalarFields(frontmatter, [
|
|
40
|
+
'oat_pr_status',
|
|
41
|
+
'oat_pr_url',
|
|
42
|
+
]);
|
|
43
|
+
if (parsed.valid &&
|
|
44
|
+
parsed.values.oat_pr_status === 'open' &&
|
|
45
|
+
parsed.values.oat_pr_url) {
|
|
46
|
+
try {
|
|
47
|
+
prRefresh = await dependencies.refreshPrLinks(target, parsed.values.oat_pr_url, { warn: (message) => context.logger.warn(message) });
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
context.logger.warn(`Could not refresh PR links after successfully pushing ${target.slug}: ${message}`);
|
|
52
|
+
prRefresh = 'failed';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const payload = { ...result, ref: target.ref, prRefresh };
|
|
58
|
+
if (context.json) {
|
|
59
|
+
context.logger.json(payload);
|
|
60
|
+
}
|
|
61
|
+
else if (result.status === 'pushed') {
|
|
62
|
+
context.logger.info(`Pushed ${target.slug} at ${result.sha}.`);
|
|
63
|
+
}
|
|
64
|
+
else if (result.status === 'up-to-date') {
|
|
65
|
+
context.logger.info(`${target.slug} is up to date at ${result.sha}.`);
|
|
66
|
+
}
|
|
67
|
+
else if (result.status === 'conflict') {
|
|
68
|
+
const targetArg = pathOrSlug ?? target.projectPath;
|
|
69
|
+
context.logger.error(`Push conflict${result.conflicts?.length ? ` (${result.conflicts.join(', ')})` : ''}. Resolve the files, then run oat project pull ${shellQuote(targetArg)} --continue; or run oat project pull ${shellQuote(targetArg)} --abort.`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const targetArg = pathOrSlug ?? target.projectPath;
|
|
73
|
+
context.logger.error(`Push rejected. Run oat project pull ${shellQuote(targetArg)}, then run oat project push ${shellQuote(targetArg)}.`);
|
|
74
|
+
}
|
|
75
|
+
process.exitCode =
|
|
76
|
+
result.status === 'pushed' || result.status === 'up-to-date' ? 0 : 1;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
if (context.json) {
|
|
81
|
+
context.logger.json({ status: 'error', message });
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
context.logger.error(message);
|
|
85
|
+
}
|
|
86
|
+
process.exitCode = error instanceof CliError ? error.exitCode : 2;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function createProjectPushCommand(overrides = {}) {
|
|
90
|
+
const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
|
|
91
|
+
return new Command('push')
|
|
92
|
+
.description('Commit and publish a synced OAT project')
|
|
93
|
+
.argument('[project-path|slug]', 'Synced project path or slug')
|
|
94
|
+
.option('--message <message>', 'Artifact commit message')
|
|
95
|
+
.option('--no-refresh-pr', 'Do not refresh PR artifact links')
|
|
96
|
+
.action(async (pathOrSlug, options, command) => {
|
|
97
|
+
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
98
|
+
await runPush(context, pathOrSlug, options, dependencies);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
2
|
+
import { readSyncedRecord } from '../../project/sync/record.js';
|
|
3
|
+
import { isSyncedCheckout } from '../../shared/project-scope.js';
|
|
4
|
+
import { type OatLocalConfig } from '../../../config/oat-config.js';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
interface ProjectScopeDependencies {
|
|
7
|
+
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
8
|
+
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
9
|
+
resolveProjectsRoot: (repoRoot: string, env: NodeJS.ProcessEnv) => Promise<string>;
|
|
10
|
+
readOatLocalConfig: (repoRoot: string) => Promise<OatLocalConfig>;
|
|
11
|
+
readSyncedRecord: typeof readSyncedRecord;
|
|
12
|
+
isSyncedCheckout: typeof isSyncedCheckout;
|
|
13
|
+
processEnv: NodeJS.ProcessEnv;
|
|
14
|
+
}
|
|
15
|
+
export declare function createProjectScopeCommand(overrides?: Partial<ProjectScopeDependencies>): Command;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/scope/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAEL,gBAAgB,EAKjB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAG7E,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAM5C,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,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,gBAAgB,EAAE,OAAO,gBAAgB,CAAC;IAC1C,gBAAgB,EAAE,OAAO,gBAAgB,CAAC;IAC1C,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAoHD,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAuBT"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
2
|
+
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
3
|
+
import { readSyncedRecord } from '../../project/sync/record.js';
|
|
4
|
+
import { resolveProjectsRoot } from '../../shared/oat-paths.js';
|
|
5
|
+
import { canonicalizePath, isSyncedCheckout, resolveProjectScope, resolveProjectsParent, resolveScopeRoot, syncedRecordPath, } from '../../shared/project-scope.js';
|
|
6
|
+
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
7
|
+
import { readOatLocalConfig } from '../../../config/oat-config.js';
|
|
8
|
+
import { CliError } from '../../../errors/cli-error.js';
|
|
9
|
+
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
10
|
+
import { Command, Option } from 'commander';
|
|
11
|
+
const DEFAULT_DEPENDENCIES = {
|
|
12
|
+
buildCommandContext,
|
|
13
|
+
resolveProjectRoot,
|
|
14
|
+
resolveProjectsRoot,
|
|
15
|
+
readOatLocalConfig,
|
|
16
|
+
readSyncedRecord,
|
|
17
|
+
isSyncedCheckout,
|
|
18
|
+
processEnv: process.env,
|
|
19
|
+
};
|
|
20
|
+
function isWithin(root, candidate) {
|
|
21
|
+
const child = relative(root, candidate);
|
|
22
|
+
return (child === '' ||
|
|
23
|
+
(child !== '..' && !child.startsWith(`..${sep}`) && !isAbsolute(child)));
|
|
24
|
+
}
|
|
25
|
+
async function runProjectScope(context, pathArg, options, dependencies) {
|
|
26
|
+
try {
|
|
27
|
+
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
28
|
+
const projectsRoot = await dependencies.resolveProjectsRoot(repoRoot, dependencies.processEnv);
|
|
29
|
+
const canonicalRepoRoot = canonicalizePath(repoRoot);
|
|
30
|
+
const configuredRoot = canonicalizePath(isAbsolute(projectsRoot)
|
|
31
|
+
? resolve(projectsRoot)
|
|
32
|
+
: resolve(repoRoot, projectsRoot));
|
|
33
|
+
const configuredParent = resolveProjectsParent(repoRoot, projectsRoot);
|
|
34
|
+
const archivedRoot = join(configuredParent, 'archived');
|
|
35
|
+
let target = pathArg;
|
|
36
|
+
if (!target) {
|
|
37
|
+
target =
|
|
38
|
+
(await dependencies.readOatLocalConfig(repoRoot)).activeProject ??
|
|
39
|
+
undefined;
|
|
40
|
+
if (!target) {
|
|
41
|
+
throw new CliError('No active project is configured.', 1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const absoluteProjectPath = canonicalizePath(isAbsolute(target) ? resolve(target) : resolve(repoRoot, target));
|
|
45
|
+
if (isWithin(archivedRoot, absoluteProjectPath)) {
|
|
46
|
+
throw new CliError('Archived projects have no active project scope. Restore or open a live project instead.', 1);
|
|
47
|
+
}
|
|
48
|
+
const scope = resolveProjectScope(absoluteProjectPath, configuredRoot, repoRoot);
|
|
49
|
+
if (!scope) {
|
|
50
|
+
throw new CliError(`Project path is outside the shared, local, and synced scope roots: ${target}`, 1);
|
|
51
|
+
}
|
|
52
|
+
const projectPath = relative(canonicalRepoRoot, absoluteProjectPath)
|
|
53
|
+
.split(sep)
|
|
54
|
+
.join('/');
|
|
55
|
+
const payload = {
|
|
56
|
+
status: 'ok',
|
|
57
|
+
projectPath,
|
|
58
|
+
scope,
|
|
59
|
+
checkout: 'n/a',
|
|
60
|
+
};
|
|
61
|
+
if (scope === 'synced') {
|
|
62
|
+
const slug = basename(absoluteProjectPath);
|
|
63
|
+
const scopeRoot = resolveScopeRoot(repoRoot, projectsRoot, 'synced');
|
|
64
|
+
const record = await dependencies.readSyncedRecord(syncedRecordPath(scopeRoot, slug));
|
|
65
|
+
payload['ref'] = record?.ref;
|
|
66
|
+
payload['record'] = record ?? undefined;
|
|
67
|
+
payload['checkout'] = (await dependencies.isSyncedCheckout(absoluteProjectPath))
|
|
68
|
+
? 'present'
|
|
69
|
+
: 'absent';
|
|
70
|
+
}
|
|
71
|
+
if (options.format === 'value') {
|
|
72
|
+
context.logger.info(scope);
|
|
73
|
+
}
|
|
74
|
+
else if (context.json || options.format === 'json') {
|
|
75
|
+
context.logger.json(payload);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
context.logger.info(`Project: ${projectPath}`);
|
|
79
|
+
context.logger.info(`Scope: ${scope}`);
|
|
80
|
+
}
|
|
81
|
+
process.exitCode = 0;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
if (context.json || options.format === 'json') {
|
|
86
|
+
context.logger.json({ status: 'error', message });
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
context.logger.error(message);
|
|
90
|
+
}
|
|
91
|
+
process.exitCode = error instanceof CliError ? error.exitCode : 2;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function createProjectScopeCommand(overrides = {}) {
|
|
95
|
+
const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
|
|
96
|
+
return new Command('scope')
|
|
97
|
+
.description('Report the scope of an OAT project')
|
|
98
|
+
.argument('[project-path]', 'Project path; defaults to activeProject')
|
|
99
|
+
.addOption(new Option('--format <format>', 'Output format').choices([
|
|
100
|
+
'json',
|
|
101
|
+
'value',
|
|
102
|
+
]))
|
|
103
|
+
.action(async (projectPath, options, command) => {
|
|
104
|
+
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
105
|
+
await runProjectScope(context, projectPath, options, dependencies);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { appendFile, readFile, readdir, stat } from 'node:fs/promises';
|
|
2
2
|
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
3
|
+
import { type GitRunner } from '../../project/sync/git.js';
|
|
4
|
+
import { pushSynced as defaultPushSynced } from '../../project/sync/ref-sync.js';
|
|
3
5
|
import { confirmAction } from '../../shared/shared.prompts.js';
|
|
4
6
|
import { Command } from 'commander';
|
|
5
7
|
interface RunSplitDependencies {
|
|
@@ -15,6 +17,8 @@ interface RunSplitDependencies {
|
|
|
15
17
|
}) => Promise<void>;
|
|
16
18
|
confirmAction: typeof confirmAction;
|
|
17
19
|
processEnv: NodeJS.ProcessEnv;
|
|
20
|
+
gitRunner: GitRunner;
|
|
21
|
+
pushSynced: typeof defaultPushSynced;
|
|
18
22
|
}
|
|
19
23
|
export declare function createProjectSplitRunCommand(overrides?: Partial<RunSplitDependencies>): Command;
|
|
20
24
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/split/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGvE,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/split/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGvE,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAOlF,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAMhE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,UAAU,oBAAoB;IAC5B,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,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,OAAO,EAAE,OAAO,OAAO,CAAC;IACxB,IAAI,EAAE,OAAO,IAAI,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,gBAAgB,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,aAAa,EAAE,OAAO,aAAa,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,OAAO,iBAAiB,CAAC;CACtC;AAiND,wBAAgB,4BAA4B,CAC1C,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,OAAO,CAkLT"}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { appendFile, readFile, readdir, stat } from 'node:fs/promises';
|
|
2
2
|
import { isAbsolute, join, relative } from 'node:path';
|
|
3
3
|
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
4
|
+
import { defaultGitRunner } from '../../project/sync/git.js';
|
|
5
|
+
import { pushSynced as defaultPushSynced } from '../../project/sync/ref-sync.js';
|
|
4
6
|
import { resolveProjectsRoot } from '../../shared/oat-paths.js';
|
|
7
|
+
import { resolveProjectScope, resolveScopeRoot, } from '../../shared/project-scope.js';
|
|
5
8
|
import { confirmAction } from '../../shared/shared.prompts.js';
|
|
6
9
|
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
7
10
|
import { generateStateDashboard } from '../../state/generate.js';
|
|
8
11
|
import { readOatLocalConfig } from '../../../config/oat-config.js';
|
|
12
|
+
import { CliError } from '../../../errors/cli-error.js';
|
|
9
13
|
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
10
14
|
import { Command } from 'commander';
|
|
11
15
|
import { validateSplitPlanDocumentShape } from '../../../projects/split/document-validation.js';
|
|
@@ -27,6 +31,8 @@ const DEFAULT_DEPENDENCIES = {
|
|
|
27
31
|
},
|
|
28
32
|
confirmAction,
|
|
29
33
|
processEnv: process.env,
|
|
34
|
+
gitRunner: defaultGitRunner,
|
|
35
|
+
pushSynced: defaultPushSynced,
|
|
30
36
|
};
|
|
31
37
|
async function exists(path, dependencies) {
|
|
32
38
|
try {
|
|
@@ -122,13 +128,33 @@ async function runFreshSplit(document, repoRoot, projectsRoot, dependencies, opt
|
|
|
122
128
|
}
|
|
123
129
|
const validation = validateChildPlan(document.plan, slugs);
|
|
124
130
|
if (!validation.ok) {
|
|
125
|
-
throw new
|
|
131
|
+
throw new CliError(`Split plan validation failed: ${validation.errors
|
|
126
132
|
.map((error) => error.message)
|
|
127
|
-
.join('; ')}
|
|
133
|
+
.join('; ')}`, 1);
|
|
128
134
|
}
|
|
129
|
-
await
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
const localConfig = await readOatLocalConfig(repoRoot);
|
|
136
|
+
const scope = localConfig.activeProject
|
|
137
|
+
? resolveProjectScope(localConfig.activeProject, projectsRoot, repoRoot)
|
|
138
|
+
: null;
|
|
139
|
+
const inheritedScope = scope ?? 'shared';
|
|
140
|
+
const absoluteScopeRoot = inheritedScope === 'shared'
|
|
141
|
+
? isAbsolute(projectsRoot)
|
|
142
|
+
? projectsRoot
|
|
143
|
+
: join(repoRoot, projectsRoot)
|
|
144
|
+
: resolveScopeRoot(repoRoot, projectsRoot, inheritedScope);
|
|
145
|
+
const scopeRoot = toRepoRelativeProjectPath(repoRoot, absoluteScopeRoot);
|
|
146
|
+
const splitContext = {
|
|
147
|
+
repoRoot,
|
|
148
|
+
projectsRoot,
|
|
149
|
+
scope: inheritedScope,
|
|
150
|
+
scopeRoot,
|
|
151
|
+
env: dependencies.processEnv,
|
|
152
|
+
gitRunner: dependencies.gitRunner,
|
|
153
|
+
pushSynced: dependencies.pushSynced,
|
|
154
|
+
};
|
|
155
|
+
await writeCoordinationParent(document, splitContext);
|
|
156
|
+
await seedChildren(document.plan, splitContext);
|
|
157
|
+
await finalizeSplit(document.plan, splitContext);
|
|
132
158
|
}
|
|
133
159
|
export function createProjectSplitRunCommand(overrides = {}) {
|
|
134
160
|
const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
|
|
@@ -146,9 +172,9 @@ export function createProjectSplitRunCommand(overrides = {}) {
|
|
|
146
172
|
const parsed = JSON.parse(raw);
|
|
147
173
|
const documentShape = validateSplitPlanDocumentShape(parsed);
|
|
148
174
|
if (!documentShape.ok) {
|
|
149
|
-
throw new
|
|
175
|
+
throw new CliError(`Invalid SplitPlanDocument: ${documentShape.errors
|
|
150
176
|
.map((error) => error.message)
|
|
151
|
-
.join('; ')}
|
|
177
|
+
.join('; ')}`, 1);
|
|
152
178
|
}
|
|
153
179
|
const document = documentShape.document;
|
|
154
180
|
if (isEffectivelyNonInteractive(options, context, dependencies.processEnv) &&
|
|
@@ -159,7 +185,14 @@ export function createProjectSplitRunCommand(overrides = {}) {
|
|
|
159
185
|
process.exitCode = 1;
|
|
160
186
|
return;
|
|
161
187
|
}
|
|
162
|
-
const
|
|
188
|
+
const localConfig = await readOatLocalConfig(repoRoot);
|
|
189
|
+
const activeScope = localConfig.activeProject
|
|
190
|
+
? resolveProjectScope(localConfig.activeProject, projectsRoot, repoRoot)
|
|
191
|
+
: null;
|
|
192
|
+
const parentRoot = activeScope && activeScope !== 'shared'
|
|
193
|
+
? resolveScopeRoot(repoRoot, projectsRoot, activeScope)
|
|
194
|
+
: projectsRoot;
|
|
195
|
+
const parentPath = join(parentRoot, document.plan.parentSlug)
|
|
163
196
|
.split('\\')
|
|
164
197
|
.join('/');
|
|
165
198
|
const absoluteParentPath = isAbsolute(parentPath)
|
|
@@ -216,7 +249,12 @@ export function createProjectSplitRunCommand(overrides = {}) {
|
|
|
216
249
|
throw new SplitResumeError('Split resume cancelled.');
|
|
217
250
|
}
|
|
218
251
|
}
|
|
219
|
-
await continueSplitResume(partial, {
|
|
252
|
+
await continueSplitResume(partial, {
|
|
253
|
+
repoRoot,
|
|
254
|
+
projectsRoot,
|
|
255
|
+
gitRunner: dependencies.gitRunner,
|
|
256
|
+
pushSynced: dependencies.pushSynced,
|
|
257
|
+
});
|
|
220
258
|
}
|
|
221
259
|
else {
|
|
222
260
|
await runFreshSplit(document, repoRoot, projectsRoot, dependencies);
|
|
@@ -239,7 +277,7 @@ export function createProjectSplitRunCommand(overrides = {}) {
|
|
|
239
277
|
process.exitCode = 1;
|
|
240
278
|
return;
|
|
241
279
|
}
|
|
242
|
-
process.exitCode =
|
|
280
|
+
process.exitCode = error instanceof CliError ? error.exitCode : 2;
|
|
243
281
|
}
|
|
244
282
|
});
|
|
245
283
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ExecFileException, type ExecFileOptionsWithStringEncoding } from 'node:child_process';
|
|
2
|
+
export interface GitResult {
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
code: number;
|
|
6
|
+
}
|
|
7
|
+
export interface GitRunOptions {
|
|
8
|
+
cwd: string;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
allowFailure?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface GitRunner {
|
|
13
|
+
run(args: string[], options: GitRunOptions): Promise<GitResult>;
|
|
14
|
+
}
|
|
15
|
+
export type ExecFileImplementation = (file: string, args: readonly string[], options: ExecFileOptionsWithStringEncoding, callback: (error: ExecFileException | null, stdout: string, stderr: string) => void) => unknown;
|
|
16
|
+
export declare function createGitRunner(execFileImpl?: ExecFileImplementation): GitRunner;
|
|
17
|
+
export declare const defaultGitRunner: GitRunner;
|
|
18
|
+
//# sourceMappingURL=git.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/sync/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,iCAAiC,EACvC,MAAM,oBAAoB,CAAC;AAI5B,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACjE;AAED,MAAM,MAAM,sBAAsB,GAAG,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,iCAAiC,EAC1C,QAAQ,EAAE,CACR,KAAK,EAAE,iBAAiB,GAAG,IAAI,EAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,KACX,IAAI,KACN,OAAO,CAAC;AAqCb,wBAAgB,eAAe,CAC7B,YAAY,GAAE,sBAAiC,GAC9C,SAAS,CAqCX;AAED,eAAO,MAAM,gBAAgB,WAAoB,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { execFile, } from 'node:child_process';
|
|
2
|
+
import { CliError } from '../../../errors/cli-error.js';
|
|
3
|
+
function numericExitCode(error) {
|
|
4
|
+
return typeof error?.code === 'number' ? error.code : 0;
|
|
5
|
+
}
|
|
6
|
+
function spawnFailure(error) {
|
|
7
|
+
const code = typeof error.code === 'string' ? ` (${error.code})` : '';
|
|
8
|
+
return new CliError(`Unable to run the Git executable${code}: ${error.message || 'unknown process spawn error'}. Install Git and ensure it is available on PATH.`, 2);
|
|
9
|
+
}
|
|
10
|
+
function gitEnvironment(overrides) {
|
|
11
|
+
const env = {
|
|
12
|
+
...process.env,
|
|
13
|
+
...overrides,
|
|
14
|
+
LANG: 'C',
|
|
15
|
+
LANGUAGE: 'C',
|
|
16
|
+
LC_ALL: 'C',
|
|
17
|
+
};
|
|
18
|
+
for (const variable of [
|
|
19
|
+
'GIT_DIR',
|
|
20
|
+
'GIT_WORK_TREE',
|
|
21
|
+
'GIT_INDEX_FILE',
|
|
22
|
+
'GIT_COMMON_DIR',
|
|
23
|
+
'GIT_OBJECT_DIRECTORY',
|
|
24
|
+
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
|
25
|
+
'GIT_NAMESPACE',
|
|
26
|
+
'GIT_CEILING_DIRECTORIES',
|
|
27
|
+
]) {
|
|
28
|
+
delete env[variable];
|
|
29
|
+
}
|
|
30
|
+
return env;
|
|
31
|
+
}
|
|
32
|
+
export function createGitRunner(execFileImpl = execFile) {
|
|
33
|
+
return {
|
|
34
|
+
async run(args, options) {
|
|
35
|
+
const result = await new Promise((resolve, reject) => {
|
|
36
|
+
execFileImpl('git', args, {
|
|
37
|
+
cwd: options.cwd,
|
|
38
|
+
env: gitEnvironment(options.env),
|
|
39
|
+
encoding: 'utf8',
|
|
40
|
+
}, (error, stdout, stderr) => {
|
|
41
|
+
if (error && typeof error.code !== 'number') {
|
|
42
|
+
reject(spawnFailure(error));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
resolve({
|
|
46
|
+
stdout: stdout.trim(),
|
|
47
|
+
stderr: stderr.trim(),
|
|
48
|
+
code: numericExitCode(error),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
if (result.code !== 0 && !options.allowFailure) {
|
|
53
|
+
const detail = result.stderr || result.stdout || 'unknown git error';
|
|
54
|
+
throw new CliError(`git ${args.join(' ')} failed (exit ${result.code}): ${detail}`, 2);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export const defaultGitRunner = createGitRunner();
|