@contentful/experience-design-system-cli 2.12.0 → 2.12.1-dev-build-bae15c7.0
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/README.md +214 -357
- package/dist/package.json +1 -1
- package/dist/src/analyze/extract/astro.js +9 -0
- package/dist/src/analyze/extract/react.js +23 -0
- package/dist/src/analyze/extract/stencil.js +3 -0
- package/dist/src/analyze/extract/vue.js +6 -0
- package/dist/src/analyze/extract/web-components.js +10 -0
- package/dist/src/analyze/pre-classify.d.ts +1 -1
- package/dist/src/analyze/pre-classify.js +3 -3
- package/dist/src/analyze/select/persistence.d.ts +13 -0
- package/dist/src/analyze/select/persistence.js +38 -0
- package/dist/src/analyze/select/preview-annotations.d.ts +26 -0
- package/dist/src/analyze/select/preview-annotations.js +62 -0
- package/dist/src/analyze/select/tui/App.js +5 -2
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.d.ts +30 -0
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +137 -0
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +58 -1
- package/dist/src/analyze/select/tui/components/FieldEditor.js +637 -161
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +1 -1
- package/dist/src/analyze/select/tui/components/JsonPanel.js +5 -1
- package/dist/src/analyze/select/tui/components/RationalePanel.d.ts +34 -0
- package/dist/src/analyze/select/tui/components/RationalePanel.js +92 -0
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +13 -1
- package/dist/src/analyze/select/tui/components/Sidebar.js +27 -3
- package/dist/src/analyze/select/tui/hooks/scroll-offset.d.ts +29 -0
- package/dist/src/analyze/select/tui/hooks/scroll-offset.js +26 -0
- package/dist/src/analyze/select-agent/command.d.ts +2 -0
- package/dist/src/analyze/select-agent/command.js +260 -45
- package/dist/src/analyze/select-agent/show-rationale.d.ts +35 -0
- package/dist/src/analyze/select-agent/show-rationale.js +92 -0
- package/dist/src/apply/command.js +7 -3
- package/dist/src/apply/tui/ServerApplyView.d.ts +3 -1
- package/dist/src/apply/tui/ServerApplyView.js +3 -2
- package/dist/src/credentials-store.d.ts +5 -0
- package/dist/src/credentials-store.js +7 -1
- package/dist/src/generate/agent-runner.d.ts +14 -0
- package/dist/src/generate/agent-runner.js +16 -0
- package/dist/src/generate/command.d.ts +6 -0
- package/dist/src/generate/command.js +45 -10
- package/dist/src/generate/progress.d.ts +9 -0
- package/dist/src/generate/progress.js +11 -0
- package/dist/src/generate/prompt-builder.d.ts +8 -0
- package/dist/src/generate/prompt-builder.js +22 -11
- package/dist/src/import/agent-model-resolve.d.ts +23 -0
- package/dist/src/import/agent-model-resolve.js +35 -0
- package/dist/src/import/auto-filter-resolve.d.ts +12 -0
- package/dist/src/import/auto-filter-resolve.js +16 -0
- package/dist/src/import/command.js +248 -8
- package/dist/src/import/orchestrator.d.ts +2 -0
- package/dist/src/import/orchestrator.js +11 -3
- package/dist/src/import/print-prompt.d.ts +35 -0
- package/dist/src/import/print-prompt.js +31 -0
- package/dist/src/import/tui/CustomPromptBanner.d.ts +11 -0
- package/dist/src/import/tui/CustomPromptBanner.js +7 -0
- package/dist/src/import/tui/WizardApp.d.ts +112 -8
- package/dist/src/import/tui/WizardApp.js +820 -333
- package/dist/src/import/tui/auto-filter-error.d.ts +2 -0
- package/dist/src/import/tui/auto-filter-error.js +29 -0
- package/dist/src/import/tui/final-review-host.d.ts +15 -0
- package/dist/src/import/tui/final-review-host.js +20 -0
- package/dist/src/import/tui/merge-ai-decisions.d.ts +5 -0
- package/dist/src/import/tui/merge-ai-decisions.js +22 -0
- package/dist/src/import/tui/push-decision-gate-helpers.d.ts +8 -0
- package/dist/src/import/tui/push-decision-gate-helpers.js +8 -0
- package/dist/src/import/tui/push-progress.d.ts +23 -0
- package/dist/src/import/tui/push-progress.js +14 -0
- package/dist/src/import/tui/run-print-files-helpers.d.ts +26 -0
- package/dist/src/import/tui/run-print-files-helpers.js +8 -0
- package/dist/src/import/tui/run-teaser.d.ts +5 -0
- package/dist/src/import/tui/run-teaser.js +9 -0
- package/dist/src/import/tui/runLivePreview.d.ts +35 -0
- package/dist/src/import/tui/runLivePreview.js +73 -0
- package/dist/src/import/tui/runScopeGate.d.ts +22 -0
- package/dist/src/import/tui/runScopeGate.js +28 -0
- package/dist/src/import/tui/scope-gate-host.d.ts +21 -0
- package/dist/src/import/tui/scope-gate-host.js +20 -0
- package/dist/src/import/tui/spawn-generate.d.ts +33 -0
- package/dist/src/import/tui/spawn-generate.js +53 -0
- package/dist/src/import/tui/steps/CredentialsStep.d.ts +25 -1
- package/dist/src/import/tui/steps/CredentialsStep.js +31 -3
- package/dist/src/import/tui/steps/DoneStep.d.ts +6 -1
- package/dist/src/import/tui/steps/DoneStep.js +10 -4
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +29 -2
- package/dist/src/import/tui/steps/GenerateReviewStep.js +412 -48
- package/dist/src/import/tui/steps/PushDecisionGateStep.d.ts +19 -0
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +76 -0
- package/dist/src/import/tui/steps/PushingStep.d.ts +10 -0
- package/dist/src/import/tui/steps/PushingStep.js +40 -0
- package/dist/src/import/tui/steps/ScopeGateStep.d.ts +23 -0
- package/dist/src/import/tui/steps/ScopeGateStep.js +250 -0
- package/dist/src/import/tui/steps/TokenInputStep.js +20 -3
- package/dist/src/import/tui/useLivePreview.d.ts +35 -0
- package/dist/src/import/tui/useLivePreview.js +120 -0
- package/dist/src/import/tui/wizard-generate-progress.d.ts +21 -0
- package/dist/src/import/tui/wizard-generate-progress.js +25 -0
- package/dist/src/import/tui/wizard-save-flow.d.ts +37 -0
- package/dist/src/import/tui/wizard-save-flow.js +20 -0
- package/dist/src/import/tui/wizard-state-transitions.d.ts +80 -0
- package/dist/src/import/tui/wizard-state-transitions.js +79 -0
- package/dist/src/lib/contentful-urls.d.ts +34 -0
- package/dist/src/lib/contentful-urls.js +41 -0
- package/dist/src/program.js +2 -0
- package/dist/src/runs/export-helpers.d.ts +30 -0
- package/dist/src/runs/export-helpers.js +59 -0
- package/dist/src/runs/fingerprint.d.ts +47 -0
- package/dist/src/runs/fingerprint.js +75 -0
- package/dist/src/runs/ls-command.d.ts +12 -0
- package/dist/src/runs/ls-command.js +137 -0
- package/dist/src/runs/modify-launcher.d.ts +24 -0
- package/dist/src/runs/modify-launcher.js +36 -0
- package/dist/src/runs/path-prompt.d.ts +8 -0
- package/dist/src/runs/path-prompt.js +72 -0
- package/dist/src/runs/push-creds-prompt.d.ts +25 -0
- package/dist/src/runs/push-creds-prompt.js +39 -0
- package/dist/src/runs/push-helpers.d.ts +29 -0
- package/dist/src/runs/push-helpers.js +69 -0
- package/dist/src/runs/replay-helpers.d.ts +67 -0
- package/dist/src/runs/replay-helpers.js +148 -0
- package/dist/src/runs/resolve-run-target.d.ts +12 -0
- package/dist/src/runs/resolve-run-target.js +45 -0
- package/dist/src/runs/run-picker-mount.d.ts +46 -0
- package/dist/src/runs/run-picker-mount.js +72 -0
- package/dist/src/runs/run-picker.d.ts +17 -0
- package/dist/src/runs/run-picker.js +145 -0
- package/dist/src/runs/save-conflict.d.ts +8 -0
- package/dist/src/runs/save-conflict.js +56 -0
- package/dist/src/runs/save-path-resolver.d.ts +55 -0
- package/dist/src/runs/save-path-resolver.js +71 -0
- package/dist/src/runs/staleness.d.ts +38 -0
- package/dist/src/runs/staleness.js +140 -0
- package/dist/src/runs/store.d.ts +78 -0
- package/dist/src/runs/store.js +136 -0
- package/dist/src/session/cache-keys.d.ts +15 -0
- package/dist/src/session/cache-keys.js +38 -0
- package/dist/src/session/db.d.ts +89 -3
- package/dist/src/session/db.js +447 -104
- package/dist/src/setup/auto-filter-prompt.d.ts +13 -0
- package/dist/src/setup/auto-filter-prompt.js +24 -0
- package/dist/src/setup/command.d.ts +7 -0
- package/dist/src/setup/command.js +58 -2
- package/dist/src/types.d.ts +6 -0
- package/package.json +2 -2
- package/skills/generate-components.md +54 -6
- package/skills/select-components.md +13 -4
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { listRuns } from './store.js';
|
|
2
|
+
import { resolveRunTarget } from './resolve-run-target.js';
|
|
3
|
+
import { checkRunStaleness, shortStalenessSummary, formatStalenessDetail } from './staleness.js';
|
|
4
|
+
function pad(value, width) {
|
|
5
|
+
if (value.length >= width)
|
|
6
|
+
return value;
|
|
7
|
+
return value + ' '.repeat(width - value.length);
|
|
8
|
+
}
|
|
9
|
+
function formatCreated(iso) {
|
|
10
|
+
// Drop ms + Z, render as `YYYY-MM-DD HH:MM`.
|
|
11
|
+
const m = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})/.exec(iso);
|
|
12
|
+
return m ? `${m[1]} ${m[2]}` : iso;
|
|
13
|
+
}
|
|
14
|
+
function formatPushed(record) {
|
|
15
|
+
if (!record.pushedTo)
|
|
16
|
+
return '(not pushed)';
|
|
17
|
+
return `${record.pushedTo.spaceId}/${record.pushedTo.environmentId}`;
|
|
18
|
+
}
|
|
19
|
+
function makeColumns(staleness) {
|
|
20
|
+
return [
|
|
21
|
+
{ header: 'ID', get: (r) => r.id },
|
|
22
|
+
{ header: 'CREATED', get: (r) => formatCreated(r.createdAt) },
|
|
23
|
+
{ header: 'PROJECT', get: (r) => r.projectPath },
|
|
24
|
+
{ header: 'SAVED TO', get: (r) => r.savePath },
|
|
25
|
+
{ header: 'COMPONENTS', get: (r) => String(r.componentCount) },
|
|
26
|
+
{ header: 'PUSHED', get: formatPushed },
|
|
27
|
+
{ header: 'STALE', get: (_r, i) => shortStalenessSummary(staleness[i]) },
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
function renderFooter(runs, write) {
|
|
31
|
+
if (runs.length === 0)
|
|
32
|
+
return;
|
|
33
|
+
const id = runs[0].id;
|
|
34
|
+
write('\n');
|
|
35
|
+
write(`Push run ${id}: experiences import --push-from-run ${id}\n`);
|
|
36
|
+
write(`Modify run ${id}: experiences import --modify ${id}\n`);
|
|
37
|
+
}
|
|
38
|
+
async function renderDetail(run, write) {
|
|
39
|
+
const lines = [
|
|
40
|
+
`Run ${run.id}`,
|
|
41
|
+
`Created: ${formatCreated(run.createdAt)}`,
|
|
42
|
+
`Project: ${run.projectPath}`,
|
|
43
|
+
`Saved: ${run.savePath}`,
|
|
44
|
+
`Components: ${run.componentCount}`,
|
|
45
|
+
`Tokens: ${run.tokenCount}`,
|
|
46
|
+
];
|
|
47
|
+
if (run.tokensPath) {
|
|
48
|
+
lines.push(`Tokens saved: ${run.tokensPath}`);
|
|
49
|
+
}
|
|
50
|
+
lines.push(`Agent: ${run.agent}`, `Pushed: ${formatPushed(run)}`, '');
|
|
51
|
+
// Staleness status block. v2/v1 records (no sourceFingerprint) render as
|
|
52
|
+
// UNKNOWN — the operator can re-extract for a fresh run.
|
|
53
|
+
if (!run.sourceFingerprint) {
|
|
54
|
+
lines.push('Status: UNKNOWN (run pre-dates the staleness fingerprint).');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const staleness = await checkRunStaleness(run);
|
|
58
|
+
lines.push(...formatStalenessDetail(staleness));
|
|
59
|
+
}
|
|
60
|
+
lines.push('', `Push to Contentful: experiences import --push-from-run ${run.id}`, `Modify in wizard: experiences import --modify ${run.id}`, '');
|
|
61
|
+
write(lines.join('\n'));
|
|
62
|
+
}
|
|
63
|
+
export async function runLsCommand(opts = {}) {
|
|
64
|
+
const write = opts.write ?? ((s) => process.stdout.write(s));
|
|
65
|
+
if (opts.pushed && opts.notPushed) {
|
|
66
|
+
throw new Error('--pushed and --not-pushed are mutually exclusive');
|
|
67
|
+
}
|
|
68
|
+
if (opts.target) {
|
|
69
|
+
const run = await resolveRunTarget(opts.target);
|
|
70
|
+
if (opts.json) {
|
|
71
|
+
write(JSON.stringify(run, null, 2) + '\n');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
await renderDetail(run, write);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let runs = await listRuns({
|
|
78
|
+
...(opts.projectPath ? { projectPath: opts.projectPath } : {}),
|
|
79
|
+
...(typeof opts.limit === 'number' ? { limit: opts.limit } : {}),
|
|
80
|
+
});
|
|
81
|
+
if (opts.pushed)
|
|
82
|
+
runs = runs.filter((r) => r.pushedTo !== null);
|
|
83
|
+
if (opts.notPushed)
|
|
84
|
+
runs = runs.filter((r) => r.pushedTo === null);
|
|
85
|
+
if (opts.json) {
|
|
86
|
+
write(JSON.stringify(runs, null, 2) + '\n');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (runs.length === 0) {
|
|
90
|
+
write('No runs recorded yet. Run `experiences import` to create one.\n');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Compute staleness once per row so the table is consistent across the
|
|
94
|
+
// STALE column and any future detail-equivalent surfaces.
|
|
95
|
+
const stalenessByIdx = await Promise.all(runs.map(async (r) => (r.sourceFingerprint ? checkRunStaleness(r) : null)));
|
|
96
|
+
const stalenessForCol = stalenessByIdx.map((s) => s ?? {
|
|
97
|
+
stale: false,
|
|
98
|
+
staleComponents: [],
|
|
99
|
+
staleTokens: false,
|
|
100
|
+
savedComponentsEdited: false,
|
|
101
|
+
savedTokensEdited: false,
|
|
102
|
+
missingSourceFiles: [],
|
|
103
|
+
});
|
|
104
|
+
const columns = makeColumns(stalenessForCol);
|
|
105
|
+
// Auto-expand each column to max(header, max(row value)) so long paths
|
|
106
|
+
// aren't silently truncated.
|
|
107
|
+
const widths = columns.map((c) => Math.max(c.header.length, ...runs.map((r, i) => c.get(r, i).length)));
|
|
108
|
+
const headerRow = columns.map((c, i) => pad(c.header, widths[i])).join(' ');
|
|
109
|
+
write(headerRow + '\n');
|
|
110
|
+
for (let i = 0; i < runs.length; i++) {
|
|
111
|
+
const r = runs[i];
|
|
112
|
+
const row = columns.map((c, ci) => pad(c.get(r, i), widths[ci])).join(' ');
|
|
113
|
+
write(row + '\n');
|
|
114
|
+
}
|
|
115
|
+
renderFooter(runs, write);
|
|
116
|
+
}
|
|
117
|
+
export function registerRunsCommand(program) {
|
|
118
|
+
program
|
|
119
|
+
.command('runs [target]')
|
|
120
|
+
.alias('ls')
|
|
121
|
+
.description('List recorded import runs from ~/.config/experiences/runs.json, or show one run when [target] (id or path) is given')
|
|
122
|
+
.option('--project <path>', 'Filter by source project path (absolute)')
|
|
123
|
+
.option('--limit <n>', 'Limit the number of rows', (v) => parseInt(v, 10))
|
|
124
|
+
.option('--json', 'Emit RunRecord JSON to stdout (array, or single object with [target])')
|
|
125
|
+
.option('--pushed', 'Only show runs that have been pushed to Contentful')
|
|
126
|
+
.option('--not-pushed', 'Only show runs that have not been pushed')
|
|
127
|
+
.action(async (target, options) => {
|
|
128
|
+
await runLsCommand({
|
|
129
|
+
...(target ? { target } : {}),
|
|
130
|
+
...(options.project ? { projectPath: options.project } : {}),
|
|
131
|
+
...(typeof options.limit === 'number' && !Number.isNaN(options.limit) ? { limit: options.limit } : {}),
|
|
132
|
+
...(options.json ? { json: true } : {}),
|
|
133
|
+
...(options.pushed ? { pushed: true } : {}),
|
|
134
|
+
...(options.notPushed ? { notPushed: true } : {}),
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin indirection so `runModifyCommand` can be unit-tested without spinning
|
|
3
|
+
* up the real Ink wizard. The production implementation lives here.
|
|
4
|
+
*/
|
|
5
|
+
export type ModifyLauncherInput = {
|
|
6
|
+
extractSessionId: string;
|
|
7
|
+
generateSessionId: string | null;
|
|
8
|
+
/** Token session id from the run record; null when no tokens were
|
|
9
|
+
* generated. Forwarded to the wizard so the modify entry pre-loads
|
|
10
|
+
* tokens alongside the extract/generate sessions. */
|
|
11
|
+
tokenSessionId?: string | null;
|
|
12
|
+
projectPath: string;
|
|
13
|
+
savePath: string;
|
|
14
|
+
entryStep: 'scope-gate' | 'final-review';
|
|
15
|
+
saveMode: 'overwrite' | 'new' | 'prompt';
|
|
16
|
+
outDirOverride?: string;
|
|
17
|
+
/** Pre-fill space id (from the run record's pushedTo). */
|
|
18
|
+
initialSpaceId?: string;
|
|
19
|
+
/** Pre-fill environment id (from the run record's pushedTo). */
|
|
20
|
+
initialEnvironmentId?: string;
|
|
21
|
+
/** Pre-fill host (from the run record's pushedTo). */
|
|
22
|
+
initialHost?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function launchModifyWizard(input: ModifyLauncherInput): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin indirection so `runModifyCommand` can be unit-tested without spinning
|
|
3
|
+
* up the real Ink wizard. The production implementation lives here.
|
|
4
|
+
*/
|
|
5
|
+
export async function launchModifyWizard(input) {
|
|
6
|
+
const { render } = await import('ink');
|
|
7
|
+
const { createElement } = await import('react');
|
|
8
|
+
const { WizardApp } = await import('../import/tui/WizardApp.js');
|
|
9
|
+
// Modify entry: re-open the wizard with the prior run's sessions seeded so
|
|
10
|
+
// extract + generate are skipped. The wizard short-circuits to `initialStep`
|
|
11
|
+
// (typically `final-review`) using state derived from the seed IDs. Saved
|
|
12
|
+
// credentials from the run record's `pushedTo` pre-fill the credentials
|
|
13
|
+
// step (CMA token is never persisted, so it still falls through to the
|
|
14
|
+
// env/credentials.json/prompt resolution path).
|
|
15
|
+
const props = {
|
|
16
|
+
initialProjectPath: input.projectPath,
|
|
17
|
+
seedExtractSessionId: input.extractSessionId,
|
|
18
|
+
initialStep: input.entryStep,
|
|
19
|
+
};
|
|
20
|
+
if (input.generateSessionId)
|
|
21
|
+
props.seedGenerateSessionId = input.generateSessionId;
|
|
22
|
+
if (input.tokenSessionId)
|
|
23
|
+
props.seedTokenSessionId = input.tokenSessionId;
|
|
24
|
+
if (input.saveMode === 'overwrite')
|
|
25
|
+
props.outDirOverride = input.savePath;
|
|
26
|
+
if (input.outDirOverride)
|
|
27
|
+
props.outDirOverride = input.outDirOverride;
|
|
28
|
+
if (input.initialSpaceId)
|
|
29
|
+
props.initialSpaceId = input.initialSpaceId;
|
|
30
|
+
if (input.initialEnvironmentId)
|
|
31
|
+
props.initialEnvironmentId = input.initialEnvironmentId;
|
|
32
|
+
if (input.initialHost)
|
|
33
|
+
props.initialHost = input.initialHost;
|
|
34
|
+
const { waitUntilExit } = render(createElement(WizardApp, props));
|
|
35
|
+
await waitUntilExit();
|
|
36
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type PathPromptProps = {
|
|
3
|
+
defaultPath: string;
|
|
4
|
+
onSubmit: (path: string) => void;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
label?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function PathPrompt({ defaultPath, onSubmit, onCancel, label, }: PathPromptProps): React.ReactElement;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { readdirSync } from 'node:fs';
|
|
5
|
+
import { dirname, basename, join } from 'node:path';
|
|
6
|
+
import { useImmediateInput } from '../analyze/select/tui/hooks/useImmediateInput.js';
|
|
7
|
+
import { normalizePath } from '../import/path-utils.js';
|
|
8
|
+
function autocomplete(partial) {
|
|
9
|
+
if (!partial)
|
|
10
|
+
return null;
|
|
11
|
+
const normalized = normalizePath(partial);
|
|
12
|
+
const dir = dirname(normalized);
|
|
13
|
+
const base = basename(normalized);
|
|
14
|
+
let entries;
|
|
15
|
+
try {
|
|
16
|
+
entries = readdirSync(dir);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const matches = entries.filter((e) => e.startsWith(base));
|
|
22
|
+
if (matches.length === 0)
|
|
23
|
+
return null;
|
|
24
|
+
if (matches.length === 1)
|
|
25
|
+
return join(dir, matches[0]);
|
|
26
|
+
// Common prefix
|
|
27
|
+
let prefix = matches[0];
|
|
28
|
+
for (const m of matches) {
|
|
29
|
+
while (!m.startsWith(prefix))
|
|
30
|
+
prefix = prefix.slice(0, -1);
|
|
31
|
+
if (!prefix)
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
return prefix.length > base.length ? join(dir, prefix) : null;
|
|
35
|
+
}
|
|
36
|
+
export function PathPrompt({ defaultPath, onSubmit, onCancel, label = 'Save to', }) {
|
|
37
|
+
const [input, setInput] = useState('');
|
|
38
|
+
const [cursorVisible, setCursorVisible] = useState(true);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const interval = setInterval(() => setCursorVisible((v) => !v), 500);
|
|
41
|
+
return () => clearInterval(interval);
|
|
42
|
+
}, []);
|
|
43
|
+
useImmediateInput((rawInput, key) => {
|
|
44
|
+
if (key.return) {
|
|
45
|
+
const trimmed = input.trim();
|
|
46
|
+
onSubmit(trimmed === '' ? defaultPath : normalizePath(trimmed));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (key.escape) {
|
|
50
|
+
onCancel();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (key.tab) {
|
|
54
|
+
const target = input.trim() || defaultPath;
|
|
55
|
+
const completed = autocomplete(target);
|
|
56
|
+
if (completed)
|
|
57
|
+
setInput(completed);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (key.backspace || key.delete) {
|
|
61
|
+
setInput((v) => v.slice(0, -1));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (rawInput && !key.ctrl && !key.meta) {
|
|
65
|
+
setInput((v) => v + rawInput);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const shown = input || defaultPath;
|
|
69
|
+
const isPlaceholder = input === '';
|
|
70
|
+
const cursor = cursorVisible ? '█' : ' ';
|
|
71
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "cyan", children: "?" }), _jsxs(Text, { bold: true, children: [label, ":"] }), _jsx(Text, { dimColor: isPlaceholder, children: shown }), _jsx(Text, { children: cursor })] }), _jsxs(Box, { gap: 3, children: [_jsx(Text, { dimColor: true, children: "[Enter] Confirm" }), _jsx(Text, { dimColor: true, children: "[Tab] Autocomplete" }), _jsx(Text, { dimColor: true, children: "[Esc] Cancel" })] })] }));
|
|
72
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive credentials fallback for `experiences import --push-from-run`.
|
|
3
|
+
*
|
|
4
|
+
* The wizard ships a full `CredentialsStep` Ink component already — rather
|
|
5
|
+
* than reimplementing the field-entry UI, we mount that step in a tiny Ink
|
|
6
|
+
* app that resolves a promise once the operator confirms.
|
|
7
|
+
*
|
|
8
|
+
* Returns the four credential values (host comes back as a configured host
|
|
9
|
+
* string, e.g. `api.contentful.com`).
|
|
10
|
+
*/
|
|
11
|
+
export type CollectedCredentials = {
|
|
12
|
+
spaceId: string;
|
|
13
|
+
environmentId: string;
|
|
14
|
+
cmaToken: string;
|
|
15
|
+
host: string;
|
|
16
|
+
};
|
|
17
|
+
export type PromptForCredentialsOptions = {
|
|
18
|
+
initialSpaceId?: string;
|
|
19
|
+
initialEnvironmentId?: string;
|
|
20
|
+
initialCmaToken?: string;
|
|
21
|
+
initialHost?: string;
|
|
22
|
+
/** Short summary line rendered above the form. */
|
|
23
|
+
summary?: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function promptForPushCredentials(opts?: PromptForCredentialsOptions): Promise<CollectedCredentials>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export async function promptForPushCredentials(opts = {}) {
|
|
3
|
+
const { render, Box, Text } = await import('ink');
|
|
4
|
+
const { useState } = await import('react');
|
|
5
|
+
const { CredentialsStep } = await import('../import/tui/steps/CredentialsStep.js');
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
let app = null;
|
|
8
|
+
function App() {
|
|
9
|
+
const [done, setDone] = useState(false);
|
|
10
|
+
const handle = (spaceId, environmentId, cmaToken, host) => {
|
|
11
|
+
if (done)
|
|
12
|
+
return;
|
|
13
|
+
setDone(true);
|
|
14
|
+
// Defer unmount so Ink flushes the final render.
|
|
15
|
+
queueMicrotask(() => {
|
|
16
|
+
app?.unmount();
|
|
17
|
+
resolve({ spaceId, environmentId, cmaToken, host });
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
if (done) {
|
|
21
|
+
return React.createElement(Box, null, React.createElement(Text, null, ''));
|
|
22
|
+
}
|
|
23
|
+
return React.createElement(CredentialsStep, {
|
|
24
|
+
summary: opts.summary ?? 'Enter Contentful credentials to push this run. Press Enter on each field to advance.',
|
|
25
|
+
...(opts.initialSpaceId !== undefined ? { initialSpaceId: opts.initialSpaceId } : {}),
|
|
26
|
+
...(opts.initialEnvironmentId !== undefined ? { initialEnvironmentId: opts.initialEnvironmentId } : {}),
|
|
27
|
+
...(opts.initialCmaToken !== undefined ? { initialCmaToken: opts.initialCmaToken } : {}),
|
|
28
|
+
...(opts.initialHost !== undefined ? { initialHost: opts.initialHost } : {}),
|
|
29
|
+
onConfirm: handle,
|
|
30
|
+
onContinue: handle,
|
|
31
|
+
onQuit: () => {
|
|
32
|
+
app?.unmount();
|
|
33
|
+
reject(new Error('Credentials entry cancelled.'));
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
app = render(React.createElement(App));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type PushSessionResult = {
|
|
2
|
+
ok: true;
|
|
3
|
+
} | {
|
|
4
|
+
ok: false;
|
|
5
|
+
error: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the package's CLI binary. Duplicates the walk from export-helpers
|
|
9
|
+
* so push-helpers can be imported independently.
|
|
10
|
+
*/
|
|
11
|
+
declare function findCliPath(): string;
|
|
12
|
+
export type PushSessionOptions = {
|
|
13
|
+
sessionId: string;
|
|
14
|
+
spaceId: string;
|
|
15
|
+
environmentId: string;
|
|
16
|
+
cmaToken: string;
|
|
17
|
+
host?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Push a recorded pipeline.db session's components + tokens to Contentful by
|
|
21
|
+
* shelling out to `experiences apply push --session <id>`. Does no local
|
|
22
|
+
* file I/O — that is the whole point of --push-from-run.
|
|
23
|
+
*/
|
|
24
|
+
export declare function pushRunSession(opts: PushSessionOptions): Promise<PushSessionResult>;
|
|
25
|
+
/** Internal test surface. Not part of the public API. */
|
|
26
|
+
export declare const __testing: {
|
|
27
|
+
findCliPath: typeof findCliPath;
|
|
28
|
+
};
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the package's CLI binary. Duplicates the walk from export-helpers
|
|
7
|
+
* so push-helpers can be imported independently.
|
|
8
|
+
*/
|
|
9
|
+
function findCliPath() {
|
|
10
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
for (let i = 0; i < 8; i++) {
|
|
12
|
+
const candidate = join(dir, 'bin', 'cli.js');
|
|
13
|
+
if (existsSync(candidate))
|
|
14
|
+
return candidate;
|
|
15
|
+
const parent = dirname(dir);
|
|
16
|
+
if (parent === dir)
|
|
17
|
+
break;
|
|
18
|
+
dir = parent;
|
|
19
|
+
}
|
|
20
|
+
return join(fileURLToPath(import.meta.url), '..', '..', '..', '..', 'bin', 'cli.js');
|
|
21
|
+
}
|
|
22
|
+
function runCli(args) {
|
|
23
|
+
const cliPath = findCliPath();
|
|
24
|
+
if (!existsSync(cliPath)) {
|
|
25
|
+
return Promise.resolve({
|
|
26
|
+
exitCode: 1,
|
|
27
|
+
stdout: '',
|
|
28
|
+
stderr: `Cannot find CLI binary at ${cliPath}`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return new Promise((res) => {
|
|
32
|
+
execFile('node', [cliPath, ...args], (err, stdout, stderr) => {
|
|
33
|
+
res({
|
|
34
|
+
exitCode: err && 'code' in err && typeof err.code === 'number' ? err.code : err ? 1 : 0,
|
|
35
|
+
stdout,
|
|
36
|
+
stderr,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Push a recorded pipeline.db session's components + tokens to Contentful by
|
|
43
|
+
* shelling out to `experiences apply push --session <id>`. Does no local
|
|
44
|
+
* file I/O — that is the whole point of --push-from-run.
|
|
45
|
+
*/
|
|
46
|
+
export async function pushRunSession(opts) {
|
|
47
|
+
const args = [
|
|
48
|
+
'apply',
|
|
49
|
+
'push',
|
|
50
|
+
'--session',
|
|
51
|
+
opts.sessionId,
|
|
52
|
+
'--space-id',
|
|
53
|
+
opts.spaceId,
|
|
54
|
+
'--environment-id',
|
|
55
|
+
opts.environmentId,
|
|
56
|
+
'--cma-token',
|
|
57
|
+
opts.cmaToken,
|
|
58
|
+
'--yes',
|
|
59
|
+
];
|
|
60
|
+
if (opts.host) {
|
|
61
|
+
args.push('--host', opts.host);
|
|
62
|
+
}
|
|
63
|
+
const r = await runCli(args);
|
|
64
|
+
if (r.exitCode === 0)
|
|
65
|
+
return { ok: true };
|
|
66
|
+
return { ok: false, error: r.stderr.trim() || r.stdout.trim() || `exit ${r.exitCode}` };
|
|
67
|
+
}
|
|
68
|
+
/** Internal test surface. Not part of the public API. */
|
|
69
|
+
export const __testing = { findCliPath };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type ReplayRunOptions = {
|
|
2
|
+
runIdOrPath: string;
|
|
3
|
+
/** From `--space-id` flag. */
|
|
4
|
+
spaceId?: string;
|
|
5
|
+
/** From `--environment-id` flag. */
|
|
6
|
+
environmentId?: string;
|
|
7
|
+
/** From `--cma-token` flag (or CONTENTFUL_MANAGEMENT_TOKEN env). */
|
|
8
|
+
cmaToken?: string;
|
|
9
|
+
/** From `--host` flag. */
|
|
10
|
+
host?: string;
|
|
11
|
+
/**
|
|
12
|
+
* When true (interactive TTY), prompt for missing credentials via the
|
|
13
|
+
* wizard's CredentialsStep. When false (non-TTY / CI), throw with the
|
|
14
|
+
* pinned error string from the spec.
|
|
15
|
+
*/
|
|
16
|
+
interactive?: boolean;
|
|
17
|
+
/** When true, bypass the source/saved-file staleness check. */
|
|
18
|
+
force?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Test seam: replace the interactive prompt with a deterministic resolver.
|
|
21
|
+
* The CLI surface never sets this; only used by tests.
|
|
22
|
+
*/
|
|
23
|
+
promptForCredentials?: (need: {
|
|
24
|
+
spaceId?: string;
|
|
25
|
+
environmentId?: string;
|
|
26
|
+
cmaToken?: string;
|
|
27
|
+
host?: string;
|
|
28
|
+
}) => Promise<{
|
|
29
|
+
spaceId: string;
|
|
30
|
+
environmentId: string;
|
|
31
|
+
cmaToken: string;
|
|
32
|
+
host: string;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Push a prior run's recorded pipeline.db session directly to Contentful
|
|
37
|
+
* without re-running extraction or generation, and without writing any
|
|
38
|
+
* components.json / tokens.json to disk.
|
|
39
|
+
*
|
|
40
|
+
* Exposed as `experiences import --push-from-run <id-or-path>`.
|
|
41
|
+
*
|
|
42
|
+
* Credentials resolution order:
|
|
43
|
+
* 1. Flags on the command (`--space-id`, `--environment-id`, `--cma-token`,
|
|
44
|
+
* `--host`).
|
|
45
|
+
* 2. The run record's `pushedTo` field (space / env / host only — the
|
|
46
|
+
* CMA token is never persisted).
|
|
47
|
+
* 3. `~/.config/experiences/credentials.json` (the `experiences setup`
|
|
48
|
+
* store) and matching env vars.
|
|
49
|
+
* 4. If still incomplete and interactive: open the wizard's existing
|
|
50
|
+
* credentials step. If non-TTY: error.
|
|
51
|
+
*/
|
|
52
|
+
export declare function replayRun(opts: ReplayRunOptions): Promise<void>;
|
|
53
|
+
export type ModifyRunOptions = {
|
|
54
|
+
runIdOrPath: string;
|
|
55
|
+
saveAsNew?: boolean;
|
|
56
|
+
overwrite?: boolean;
|
|
57
|
+
outDir?: string;
|
|
58
|
+
/** When true, bypass the source/saved-file staleness check. */
|
|
59
|
+
force?: boolean;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Re-open the wizard with a prior run's extract/generate session pre-populated
|
|
63
|
+
* so the operator can tweak fields. Exposed as
|
|
64
|
+
* `experiences import --modify <id-or-path>`. Default entry step is
|
|
65
|
+
* `final-review`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function modifyRun(opts: ModifyRunOptions): Promise<void>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { resolveRunTarget } from './resolve-run-target.js';
|
|
3
|
+
import { updateRun } from './store.js';
|
|
4
|
+
import { pushRunSession } from './push-helpers.js';
|
|
5
|
+
import { readExperiencesCredentials } from '../credentials-store.js';
|
|
6
|
+
import { launchModifyWizard } from './modify-launcher.js';
|
|
7
|
+
import { checkRunStaleness, formatStalenessDetail } from './staleness.js';
|
|
8
|
+
function formatStalenessRefusal(run, detail) {
|
|
9
|
+
return [
|
|
10
|
+
`Refusing to replay run ${run.id} — source or saved files have drifted since the run was recorded.`,
|
|
11
|
+
...detail,
|
|
12
|
+
'',
|
|
13
|
+
'Re-extract with `experiences import --project <path>` for a fresh run, or pass --force to bypass.',
|
|
14
|
+
].join('\n');
|
|
15
|
+
}
|
|
16
|
+
const MISSING_CREDS_ERROR = "--push-from-run requires credentials (space-id / environment-id / cma-token). Pass --cma-token or run 'experiences setup' first.";
|
|
17
|
+
/**
|
|
18
|
+
* Push a prior run's recorded pipeline.db session directly to Contentful
|
|
19
|
+
* without re-running extraction or generation, and without writing any
|
|
20
|
+
* components.json / tokens.json to disk.
|
|
21
|
+
*
|
|
22
|
+
* Exposed as `experiences import --push-from-run <id-or-path>`.
|
|
23
|
+
*
|
|
24
|
+
* Credentials resolution order:
|
|
25
|
+
* 1. Flags on the command (`--space-id`, `--environment-id`, `--cma-token`,
|
|
26
|
+
* `--host`).
|
|
27
|
+
* 2. The run record's `pushedTo` field (space / env / host only — the
|
|
28
|
+
* CMA token is never persisted).
|
|
29
|
+
* 3. `~/.config/experiences/credentials.json` (the `experiences setup`
|
|
30
|
+
* store) and matching env vars.
|
|
31
|
+
* 4. If still incomplete and interactive: open the wizard's existing
|
|
32
|
+
* credentials step. If non-TTY: error.
|
|
33
|
+
*/
|
|
34
|
+
export async function replayRun(opts) {
|
|
35
|
+
const run = await resolveRunTarget(opts.runIdOrPath);
|
|
36
|
+
// Staleness gate: refuse before any push side effect when source files or
|
|
37
|
+
// saved artifacts have drifted. Bypassed by --force.
|
|
38
|
+
if (!opts.force) {
|
|
39
|
+
const staleness = await checkRunStaleness(run);
|
|
40
|
+
if (staleness.stale) {
|
|
41
|
+
throw new Error(formatStalenessRefusal(run, formatStalenessDetail(staleness)));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const sessionId = run.generateSessionId ?? run.extractSessionId;
|
|
45
|
+
// Layered credentials resolution.
|
|
46
|
+
const stored = await readExperiencesCredentials();
|
|
47
|
+
let spaceId = opts.spaceId || run.pushedTo?.spaceId || stored.spaceId || '';
|
|
48
|
+
let environmentId = opts.environmentId || run.pushedTo?.environmentId || stored.environmentId || '';
|
|
49
|
+
let cmaToken = opts.cmaToken || stored.cmaToken || '';
|
|
50
|
+
let host = opts.host || run.pushedTo?.host || stored.host || '';
|
|
51
|
+
if (!spaceId || !environmentId || !cmaToken) {
|
|
52
|
+
if (!opts.interactive) {
|
|
53
|
+
throw new Error(MISSING_CREDS_ERROR);
|
|
54
|
+
}
|
|
55
|
+
const prompt = opts.promptForCredentials ??
|
|
56
|
+
(async (need) => {
|
|
57
|
+
const { promptForPushCredentials } = await import('./push-creds-prompt.js');
|
|
58
|
+
return promptForPushCredentials({
|
|
59
|
+
...(need.spaceId ? { initialSpaceId: need.spaceId } : {}),
|
|
60
|
+
...(need.environmentId ? { initialEnvironmentId: need.environmentId } : {}),
|
|
61
|
+
...(need.cmaToken ? { initialCmaToken: need.cmaToken } : {}),
|
|
62
|
+
...(need.host ? { initialHost: need.host } : {}),
|
|
63
|
+
summary: `Push run ${run.id} → enter credentials. Defaults reflect the run record and your saved setup.`,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
const collected = await prompt({
|
|
67
|
+
spaceId,
|
|
68
|
+
environmentId,
|
|
69
|
+
cmaToken,
|
|
70
|
+
host,
|
|
71
|
+
});
|
|
72
|
+
spaceId = collected.spaceId;
|
|
73
|
+
environmentId = collected.environmentId;
|
|
74
|
+
cmaToken = collected.cmaToken;
|
|
75
|
+
host = collected.host;
|
|
76
|
+
if (!spaceId || !environmentId || !cmaToken) {
|
|
77
|
+
throw new Error(MISSING_CREDS_ERROR);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const result = await pushRunSession({
|
|
81
|
+
sessionId,
|
|
82
|
+
spaceId,
|
|
83
|
+
environmentId,
|
|
84
|
+
cmaToken,
|
|
85
|
+
...(host ? { host } : {}),
|
|
86
|
+
});
|
|
87
|
+
if (!result.ok) {
|
|
88
|
+
throw new Error(result.error);
|
|
89
|
+
}
|
|
90
|
+
// Push the tokens session too when the run has one. Tokens live in their
|
|
91
|
+
// own pipeline.db session (separate from extract/generate), so a single
|
|
92
|
+
// `apply push --session <id>` only covers components. Without this second
|
|
93
|
+
// push, design tokens recorded by the run never reach Contentful on replay.
|
|
94
|
+
if (run.tokenSessionId) {
|
|
95
|
+
const tokenResult = await pushRunSession({
|
|
96
|
+
sessionId: run.tokenSessionId,
|
|
97
|
+
spaceId,
|
|
98
|
+
environmentId,
|
|
99
|
+
cmaToken,
|
|
100
|
+
...(host ? { host } : {}),
|
|
101
|
+
});
|
|
102
|
+
if (!tokenResult.ok) {
|
|
103
|
+
throw new Error(tokenResult.error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
await updateRun(run.id, {
|
|
107
|
+
pushedTo: { spaceId, environmentId, host: host || '' },
|
|
108
|
+
});
|
|
109
|
+
const componentLine = `Pushed ${run.componentCount} component${run.componentCount === 1 ? '' : 's'} to ${spaceId}/${environmentId}`;
|
|
110
|
+
const tokensLine = run.tokenCount > 0 ? ` (also: ${run.tokenCount} token${run.tokenCount === 1 ? '' : 's'})` : '';
|
|
111
|
+
process.stdout.write(`${componentLine}${tokensLine}\n`);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Re-open the wizard with a prior run's extract/generate session pre-populated
|
|
115
|
+
* so the operator can tweak fields. Exposed as
|
|
116
|
+
* `experiences import --modify <id-or-path>`. Default entry step is
|
|
117
|
+
* `final-review`.
|
|
118
|
+
*/
|
|
119
|
+
export async function modifyRun(opts) {
|
|
120
|
+
if (opts.saveAsNew && opts.overwrite) {
|
|
121
|
+
throw new Error('--save-as-new and --overwrite are mutually exclusive.');
|
|
122
|
+
}
|
|
123
|
+
const run = await resolveRunTarget(opts.runIdOrPath);
|
|
124
|
+
if (!opts.force) {
|
|
125
|
+
const staleness = await checkRunStaleness(run);
|
|
126
|
+
if (staleness.stale) {
|
|
127
|
+
throw new Error(formatStalenessRefusal(run, formatStalenessDetail(staleness)));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const saveMode = opts.overwrite ? 'overwrite' : opts.saveAsNew ? 'new' : 'prompt';
|
|
131
|
+
await launchModifyWizard({
|
|
132
|
+
extractSessionId: run.extractSessionId,
|
|
133
|
+
generateSessionId: run.generateSessionId,
|
|
134
|
+
tokenSessionId: run.tokenSessionId,
|
|
135
|
+
projectPath: run.projectPath,
|
|
136
|
+
savePath: run.savePath,
|
|
137
|
+
entryStep: 'final-review',
|
|
138
|
+
saveMode,
|
|
139
|
+
...(opts.outDir ? { outDirOverride: resolve(opts.outDir) } : {}),
|
|
140
|
+
// Pre-fill credentials from the run record's last push so the operator
|
|
141
|
+
// doesn't have to re-type space/environment/host on modify. The CMA
|
|
142
|
+
// token is never persisted, so it still resolves via env var /
|
|
143
|
+
// credentials.json / interactive prompt at the credentials step.
|
|
144
|
+
...(run.pushedTo?.spaceId ? { initialSpaceId: run.pushedTo.spaceId } : {}),
|
|
145
|
+
...(run.pushedTo?.environmentId ? { initialEnvironmentId: run.pushedTo.environmentId } : {}),
|
|
146
|
+
...(run.pushedTo?.host ? { initialHost: run.pushedTo.host } : {}),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type RunRecord } from './store.js';
|
|
2
|
+
/**
|
|
3
|
+
* Decide whether a positional CLI arg refers to a run-id or a filesystem path,
|
|
4
|
+
* then look up the corresponding RunRecord.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors `git checkout` accepting either a sha or a ref: anything that *looks
|
|
7
|
+
* like* a path is treated as one; otherwise we fall back to a run-id lookup.
|
|
8
|
+
*
|
|
9
|
+
* Path detection: arg starts with `/`, `./`, `../`, or `~/`, OR equals `.`,
|
|
10
|
+
* OR equals `~`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveRunTarget(arg: string): Promise<RunRecord>;
|