@contentful/experience-design-system-cli 2.26.1-dev-build-a852362.0 → 2.26.1-dev-build-f03f014.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/dist/package.json +14 -10
- package/dist/src/index.js +34937 -9
- package/dist/src/lib/cli-path.d.ts +4 -0
- package/dist/src/runs/export-helpers.d.ts +1 -12
- package/dist/src/runs/push-helpers.d.ts +1 -6
- package/package.json +12 -7
- package/skills/generate-components.md +427 -0
- package/skills/generate-tokens.md +194 -0
- package/skills/select-components.md +224 -0
- package/dist/src/analytics/apply.js +0 -30
- package/dist/src/analytics/client.js +0 -81
- package/dist/src/analytics/constants.js +0 -4
- package/dist/src/analytics/env.js +0 -14
- package/dist/src/analytics/exit.js +0 -24
- package/dist/src/analytics/index.js +0 -9
- package/dist/src/analytics/normalize.js +0 -18
- package/dist/src/analytics/os.js +0 -13
- package/dist/src/analytics/session.js +0 -15
- package/dist/src/analytics/tracker.js +0 -126
- package/dist/src/analytics/types.js +0 -1
- package/dist/src/analyze/build-analyze-view-rows.js +0 -63
- package/dist/src/analyze/command.js +0 -599
- package/dist/src/analyze/composite-closure.js +0 -0
- package/dist/src/analyze/composition/agent-parser/author-prompt.js +0 -22
- package/dist/src/analyze/composition/agent-parser/extract-parser.js +0 -44
- package/dist/src/analyze/composition/agent-parser/load-prompt.js +0 -31
- package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +0 -88
- package/dist/src/analyze/composition/agent-parser/sandbox.js +0 -187
- package/dist/src/analyze/composition/apply-mapping.js +0 -67
- package/dist/src/analyze/composition/candidate-critic-agent.js +0 -39
- package/dist/src/analyze/composition/candidate-critic.js +0 -63
- package/dist/src/analyze/composition/candidate-files.js +0 -131
- package/dist/src/analyze/composition/composition-cache-key.js +0 -19
- package/dist/src/analyze/composition/interchange-schema.js +0 -61
- package/dist/src/analyze/composition/manifest-doc-evidence.js +0 -136
- package/dist/src/analyze/composition/merge-edges.js +0 -70
- package/dist/src/analyze/composition/parse-map-edges.js +0 -55
- package/dist/src/analyze/composition/resolve-mapping-cli.js +0 -29
- package/dist/src/analyze/composition/resolve-mapping.js +0 -111
- package/dist/src/analyze/cycle-detection.js +0 -234
- package/dist/src/analyze/cycle-view.js +0 -28
- package/dist/src/analyze/fuzzy-search.js +0 -55
- package/dist/src/analyze/issue-inheritance.js +0 -108
- package/dist/src/analyze/lineage.js +0 -0
- package/dist/src/analyze/scope-gate-cascade.js +0 -170
- package/dist/src/analyze/search-neighborhood.js +0 -56
- package/dist/src/analyze/select/command.js +0 -484
- package/dist/src/analyze/select/index.js +0 -5
- package/dist/src/analyze/select/parser.js +0 -53
- package/dist/src/analyze/select/persistence.js +0 -80
- package/dist/src/analyze/select/preview-annotations.js +0 -62
- package/dist/src/analyze/select/stdout.js +0 -3
- package/dist/src/analyze/select/tui/App.js +0 -483
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +0 -48
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +0 -125
- package/dist/src/analyze/select/tui/components/FieldEditor.js +0 -1131
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +0 -36
- package/dist/src/analyze/select/tui/components/GotoBanner.js +0 -30
- package/dist/src/analyze/select/tui/components/GroupedSidebar.js +0 -517
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +0 -17
- package/dist/src/analyze/select/tui/components/JsonEditor.js +0 -131
- package/dist/src/analyze/select/tui/components/JsonPanel.js +0 -66
- package/dist/src/analyze/select/tui/components/LineagePanel.js +0 -22
- package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +0 -29
- package/dist/src/analyze/select/tui/components/QuitDialog.js +0 -14
- package/dist/src/analyze/select/tui/components/RationalePanel.js +0 -80
- package/dist/src/analyze/select/tui/components/Sidebar.js +0 -92
- package/dist/src/analyze/select/tui/components/SourcePanel.js +0 -52
- package/dist/src/analyze/select/tui/components/StatusBar.js +0 -7
- package/dist/src/analyze/select/tui/components/TopBar.js +0 -10
- package/dist/src/analyze/select/tui/components/removed-components-text.js +0 -11
- package/dist/src/analyze/select/tui/hooks/scroll-offset.js +0 -26
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +0 -66
- package/dist/src/analyze/select/tui/hooks/useKeymap.js +0 -67
- package/dist/src/analyze/select/tui/hooks/useSession.js +0 -52
- package/dist/src/analyze/select/tui/hooks/useUndo.js +0 -26
- package/dist/src/analyze/select/tui/theme.js +0 -28
- package/dist/src/analyze/select/types.js +0 -39
- package/dist/src/analyze/select-agent/command.js +0 -558
- package/dist/src/analyze/select-agent/context-builder.js +0 -197
- package/dist/src/analyze/select-agent/show-rationale.js +0 -92
- package/dist/src/analyze/selection-cascade.js +0 -19
- package/dist/src/analyze/slot-graph.js +0 -17
- package/dist/src/analyze/tui/AnalyzeView.js +0 -64
- package/dist/src/apply/api-client.js +0 -440
- package/dist/src/apply/command.js +0 -845
- package/dist/src/apply/manifest.js +0 -1
- package/dist/src/apply/preview-utils.js +0 -26
- package/dist/src/apply/tui/SelectView.js +0 -34
- package/dist/src/apply/tui/ServerApplyView.js +0 -49
- package/dist/src/apply/tui/ServerPreviewView.js +0 -23
- package/dist/src/credentials-store.js +0 -74
- package/dist/src/generate/command.js +0 -597
- package/dist/src/generate/edit/command.js +0 -127
- package/dist/src/generate/tui/GenerateView.js +0 -10
- package/dist/src/host-utils.js +0 -26
- package/dist/src/import/agent-model-resolve.js +0 -35
- package/dist/src/import/auto-filter-resolve.js +0 -16
- package/dist/src/import/command.js +0 -380
- package/dist/src/import/cycle-auto-reject.js +0 -14
- package/dist/src/import/orchestrator.js +0 -603
- package/dist/src/import/path-utils.js +0 -30
- package/dist/src/import/picker-dispatch.js +0 -44
- package/dist/src/import/print-prompt.js +0 -31
- package/dist/src/import/strip-allowed-components.js +0 -22
- package/dist/src/import/tui/CustomPromptBanner.js +0 -8
- package/dist/src/import/tui/WizardApp.js +0 -1670
- package/dist/src/import/tui/ai-flag.js +0 -6
- package/dist/src/import/tui/auto-filter-error.js +0 -29
- package/dist/src/import/tui/autocomplete.js +0 -32
- package/dist/src/import/tui/components/AutoFilterBanner.js +0 -19
- package/dist/src/import/tui/components/CounterStrip.js +0 -12
- package/dist/src/import/tui/components/LegendEntry.js +0 -6
- package/dist/src/import/tui/cycle-panel-scroll.js +0 -33
- package/dist/src/import/tui/final-review-host.js +0 -25
- package/dist/src/import/tui/group-collapse.js +0 -11
- package/dist/src/import/tui/history.js +0 -53
- package/dist/src/import/tui/hooks/useLineage.js +0 -50
- package/dist/src/import/tui/hooks/useOverlayPanel.js +0 -27
- package/dist/src/import/tui/lineage-layout.js +0 -82
- package/dist/src/import/tui/merge-ai-decisions.js +0 -22
- package/dist/src/import/tui/push-decision-gate-helpers.js +0 -8
- package/dist/src/import/tui/push-progress.js +0 -1
- package/dist/src/import/tui/run-print-files-helpers.js +0 -8
- package/dist/src/import/tui/run-teaser.js +0 -9
- package/dist/src/import/tui/runLivePreview.js +0 -82
- package/dist/src/import/tui/runScopeGate.js +0 -28
- package/dist/src/import/tui/scope-gate-columns.js +0 -94
- package/dist/src/import/tui/scope-gate-host.js +0 -28
- package/dist/src/import/tui/sidebar-width.js +0 -3
- package/dist/src/import/tui/spawn-generate.js +0 -53
- package/dist/src/import/tui/step-filters.js +0 -42
- package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +0 -726
- package/dist/src/import/tui/steps/AtomicScopeGateStep.js +0 -210
- package/dist/src/import/tui/steps/CredentialsStep.js +0 -108
- package/dist/src/import/tui/steps/DoneStep.js +0 -24
- package/dist/src/import/tui/steps/ErrorStep.js +0 -16
- package/dist/src/import/tui/steps/GateStep.js +0 -22
- package/dist/src/import/tui/steps/GenerateReviewStep.js +0 -1618
- package/dist/src/import/tui/steps/PathValidationStep.js +0 -152
- package/dist/src/import/tui/steps/PreviewStep.js +0 -37
- package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +0 -23
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +0 -73
- package/dist/src/import/tui/steps/PushingStep.js +0 -24
- package/dist/src/import/tui/steps/RunningStep.js +0 -21
- package/dist/src/import/tui/steps/ScopeGateStep.js +0 -907
- package/dist/src/import/tui/steps/TokenInputStep.js +0 -88
- package/dist/src/import/tui/steps/WelcomeStep.js +0 -34
- package/dist/src/import/tui/steps/WizardPreviewStep.js +0 -169
- package/dist/src/import/tui/steps/auto-reject-decision.js +0 -9
- package/dist/src/import/tui/steps/breaking-change-format.js +0 -24
- package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
- package/dist/src/import/tui/steps/preview-diff.js +0 -175
- package/dist/src/import/tui/useFinalizePreview.js +0 -0
- package/dist/src/import/tui/useLivePreview.js +0 -122
- package/dist/src/import/tui/wizard-422-helpers.js +0 -60
- package/dist/src/import/tui/wizard-generate-progress.js +0 -25
- package/dist/src/import/tui/wizard-save-flow.js +0 -20
- package/dist/src/import/tui/wizard-state-transitions.js +0 -32
- package/dist/src/lib/agent-model-options.js +0 -15
- package/dist/src/lib/command-options.js +0 -30
- package/dist/src/lib/composition-mode.js +0 -39
- package/dist/src/lib/contentful-urls.js +0 -27
- package/dist/src/lib/debug-logger.js +0 -229
- package/dist/src/lib/debug-preamble.js +0 -33
- package/dist/src/lib/error-parser.js +0 -269
- package/dist/src/lib/prompt-overrides.js +0 -69
- package/dist/src/lib/terminal-capabilities.js +0 -51
- package/dist/src/lib/user-agent.js +0 -29
- package/dist/src/output/format.js +0 -110
- package/dist/src/print/command.js +0 -221
- package/dist/src/print/validate/tui/ValidateView.js +0 -37
- package/dist/src/print/validate/validators/cdf-validator.js +0 -104
- package/dist/src/print/validate/validators/dtcg-validator.js +0 -110
- package/dist/src/print/validate/validators/format-errors.js +0 -18
- package/dist/src/program.js +0 -105
- package/dist/src/runs/export-helpers.js +0 -59
- package/dist/src/runs/fingerprint.js +0 -75
- package/dist/src/runs/ls-command.js +0 -137
- package/dist/src/runs/modify-launcher.js +0 -42
- package/dist/src/runs/path-prompt.js +0 -72
- package/dist/src/runs/push-creds-prompt.js +0 -39
- package/dist/src/runs/push-helpers.js +0 -72
- package/dist/src/runs/push-launcher.js +0 -77
- package/dist/src/runs/replay-helpers.js +0 -164
- package/dist/src/runs/resolve-run-target.js +0 -45
- package/dist/src/runs/run-picker-mount.js +0 -72
- package/dist/src/runs/run-picker.js +0 -129
- package/dist/src/runs/save-conflict.js +0 -56
- package/dist/src/runs/save-path-resolver.js +0 -75
- package/dist/src/runs/staleness.js +0 -140
- package/dist/src/runs/store.js +0 -136
- package/dist/src/session/cache-keys.js +0 -44
- package/dist/src/session/command.js +0 -261
- package/dist/src/session/db.js +0 -1538
- package/dist/src/session/migration.js +0 -117
- package/dist/src/session/session-id.js +0 -212
- package/dist/src/session/stats.js +0 -89
- package/dist/src/setup/analytics-prompt.js +0 -24
- package/dist/src/setup/auto-filter-prompt.js +0 -24
- package/dist/src/setup/command.js +0 -966
- package/dist/src/setup/composition-mode-prompt.js +0 -37
- package/dist/src/setup/debug-mode-prompt.js +0 -22
- package/dist/src/types.js +0 -1
|
@@ -1,164 +0,0 @@
|
|
|
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
|
-
...(opts.allowDeletions ? { allowDeletions: true } : {}),
|
|
87
|
-
});
|
|
88
|
-
if (!result.ok) {
|
|
89
|
-
throw new Error(result.error);
|
|
90
|
-
}
|
|
91
|
-
// Push the tokens session too when the run has one. Tokens live in their
|
|
92
|
-
// own pipeline.db session (separate from extract/generate), so a single
|
|
93
|
-
// `apply push --session <id>` only covers components. Without this second
|
|
94
|
-
// push, design tokens recorded by the run never reach Contentful on replay.
|
|
95
|
-
if (run.tokenSessionId) {
|
|
96
|
-
const tokenResult = await pushRunSession({
|
|
97
|
-
sessionId: run.tokenSessionId,
|
|
98
|
-
spaceId,
|
|
99
|
-
environmentId,
|
|
100
|
-
cmaToken,
|
|
101
|
-
...(host ? { host } : {}),
|
|
102
|
-
...(opts.allowDeletions ? { allowDeletions: true } : {}),
|
|
103
|
-
});
|
|
104
|
-
if (!tokenResult.ok) {
|
|
105
|
-
throw new Error(tokenResult.error);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
await updateRun(run.id, {
|
|
109
|
-
pushedTo: { spaceId, environmentId, host: host || '' },
|
|
110
|
-
});
|
|
111
|
-
const componentLine = `Pushed ${run.componentCount} component${run.componentCount === 1 ? '' : 's'} to ${spaceId}/${environmentId}`;
|
|
112
|
-
const tokensLine = run.tokenCount > 0 ? ` (also: ${run.tokenCount} token${run.tokenCount === 1 ? '' : 's'})` : '';
|
|
113
|
-
process.stdout.write(`${componentLine}${tokensLine}\n`);
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Re-open the wizard with a prior run's extract/generate session pre-populated
|
|
117
|
-
* so the operator can tweak fields. Exposed as
|
|
118
|
-
* `experiences import --modify <id-or-path>`. Default entry step is
|
|
119
|
-
* `final-review`.
|
|
120
|
-
*/
|
|
121
|
-
export async function modifyRun(opts) {
|
|
122
|
-
if (opts.saveAsNew && opts.overwrite) {
|
|
123
|
-
throw new Error('--save-as-new and --overwrite are mutually exclusive.');
|
|
124
|
-
}
|
|
125
|
-
const run = await resolveRunTarget(opts.runIdOrPath);
|
|
126
|
-
if (!opts.force) {
|
|
127
|
-
const staleness = await checkRunStaleness(run);
|
|
128
|
-
if (staleness.stale) {
|
|
129
|
-
throw new Error(formatStalenessRefusal(run, formatStalenessDetail(staleness)));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const saveMode = opts.overwrite ? 'overwrite' : opts.saveAsNew ? 'new' : 'prompt';
|
|
133
|
-
// Credentials pre-fill precedence: run.pushedTo field-by-field, then
|
|
134
|
-
// credentials.json (which itself falls back to env vars). The modify entry
|
|
135
|
-
// step goes straight to final-review and does not surface the credentials
|
|
136
|
-
// step, so if `pushedTo` is null (last push failed or never happened) we
|
|
137
|
-
// must consult the on-disk store here — otherwise the wizard mounts with
|
|
138
|
-
// built-in defaults (environmentId='master', host='api.contentful.com')
|
|
139
|
-
// and preview fires against the wrong endpoint. CMA token is now included
|
|
140
|
-
// in the fallback because credentials-store.ts persists it (INTEG-4410);
|
|
141
|
-
// if it's still empty after the merge, the wizard's final-review preview
|
|
142
|
-
// will 401 and the operator sees a real gap — out of scope for this fix.
|
|
143
|
-
const stored = await readExperiencesCredentials();
|
|
144
|
-
const mergedSpaceId = run.pushedTo?.spaceId || stored.spaceId || '';
|
|
145
|
-
const mergedEnvironmentId = run.pushedTo?.environmentId || stored.environmentId || '';
|
|
146
|
-
const mergedHost = run.pushedTo?.host || stored.host || '';
|
|
147
|
-
const mergedCmaToken = stored.cmaToken || '';
|
|
148
|
-
await launchModifyWizard({
|
|
149
|
-
extractSessionId: run.extractSessionId,
|
|
150
|
-
generateSessionId: run.generateSessionId,
|
|
151
|
-
tokenSessionId: run.tokenSessionId,
|
|
152
|
-
projectPath: run.projectPath,
|
|
153
|
-
savePath: run.savePath,
|
|
154
|
-
entryStep: 'final-review',
|
|
155
|
-
saveMode,
|
|
156
|
-
...(run.compositionMode ? { compositionMode: run.compositionMode } : {}),
|
|
157
|
-
...(opts.outDir ? { outDirOverride: resolve(opts.outDir) } : {}),
|
|
158
|
-
...(mergedSpaceId ? { initialSpaceId: mergedSpaceId } : {}),
|
|
159
|
-
...(mergedEnvironmentId ? { initialEnvironmentId: mergedEnvironmentId } : {}),
|
|
160
|
-
...(mergedHost ? { initialHost: mergedHost } : {}),
|
|
161
|
-
...(mergedCmaToken ? { initialCmaToken: mergedCmaToken } : {}),
|
|
162
|
-
...(opts.allowDeletions !== undefined ? { allowDeletions: opts.allowDeletions } : {}),
|
|
163
|
-
});
|
|
164
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { resolve as resolvePath } from 'node:path';
|
|
3
|
-
import { findAllRunsBySavePath, getRun } from './store.js';
|
|
4
|
-
/**
|
|
5
|
-
* Decide whether a positional CLI arg refers to a run-id or a filesystem path,
|
|
6
|
-
* then look up the corresponding RunRecord.
|
|
7
|
-
*
|
|
8
|
-
* Mirrors `git checkout` accepting either a sha or a ref: anything that *looks
|
|
9
|
-
* like* a path is treated as one; otherwise we fall back to a run-id lookup.
|
|
10
|
-
*
|
|
11
|
-
* Path detection: arg starts with `/`, `./`, `../`, or `~/`, OR equals `.`,
|
|
12
|
-
* OR equals `~`.
|
|
13
|
-
*/
|
|
14
|
-
export async function resolveRunTarget(arg) {
|
|
15
|
-
if (looksLikePath(arg)) {
|
|
16
|
-
const absolute = resolvePath(expandHome(arg));
|
|
17
|
-
const matches = await findAllRunsBySavePath(absolute);
|
|
18
|
-
if (matches.length === 0) {
|
|
19
|
-
throw new Error(`No run recorded for path ${absolute}. Run 'experiences runs' to list known runs.`);
|
|
20
|
-
}
|
|
21
|
-
if (matches.length === 1) {
|
|
22
|
-
return matches[0];
|
|
23
|
-
}
|
|
24
|
-
// Multi-match: newest createdAt wins. ISO UTC timestamps compare
|
|
25
|
-
// lexicographically (see store.ts: createdAt = new Date().toISOString()).
|
|
26
|
-
const sorted = [...matches].sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
|
|
27
|
-
const newest = sorted[0];
|
|
28
|
-
const olderCount = matches.length - 1;
|
|
29
|
-
process.stderr.write(`Multiple runs at ${absolute}; using newest ${newest.id} (createdAt ${newest.createdAt}). ${olderCount} older candidates ignored.\n`);
|
|
30
|
-
return newest;
|
|
31
|
-
}
|
|
32
|
-
return getRun(arg);
|
|
33
|
-
}
|
|
34
|
-
function looksLikePath(arg) {
|
|
35
|
-
if (arg === '.' || arg === '~')
|
|
36
|
-
return true;
|
|
37
|
-
return arg.startsWith('/') || arg.startsWith('./') || arg.startsWith('../') || arg.startsWith('~/');
|
|
38
|
-
}
|
|
39
|
-
function expandHome(arg) {
|
|
40
|
-
if (arg === '~')
|
|
41
|
-
return homedir();
|
|
42
|
-
if (arg.startsWith('~/'))
|
|
43
|
-
return resolvePath(homedir(), arg.slice(2));
|
|
44
|
-
return arg;
|
|
45
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { READABLE_VERSIONS, runsFilePath } from './store.js';
|
|
3
|
-
function migrateRecord(rec) {
|
|
4
|
-
return {
|
|
5
|
-
...rec,
|
|
6
|
-
tokensPath: rec.tokensPath ?? null,
|
|
7
|
-
tokenSessionId: rec.tokenSessionId ?? null,
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
function hasBlockingFlag(flags) {
|
|
11
|
-
if (flags.pushFromRun !== undefined)
|
|
12
|
-
return true;
|
|
13
|
-
if (flags.modify !== undefined)
|
|
14
|
-
return true;
|
|
15
|
-
if (flags.project !== undefined)
|
|
16
|
-
return true;
|
|
17
|
-
if (flags.autoAcceptScope)
|
|
18
|
-
return true;
|
|
19
|
-
if (flags.printPrompt)
|
|
20
|
-
return true;
|
|
21
|
-
if (flags.dryRun)
|
|
22
|
-
return true;
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Pure decision helper for whether the wizard should open with the run picker.
|
|
27
|
-
*
|
|
28
|
-
* Picker shows iff ALL hold:
|
|
29
|
-
* 1. `~/.config/experiences/runs.json` exists
|
|
30
|
-
* 2. The file has >=1 run entry
|
|
31
|
-
* 3. None of `--push-from-run`, `--modify`, `--project`,
|
|
32
|
-
* `--auto-accept-scope`, `--print-prompt`, or `--dry-run` were passed
|
|
33
|
-
* 4. stdin is a TTY
|
|
34
|
-
*
|
|
35
|
-
* On any failure (including ENOENT or a parse error), the helper returns
|
|
36
|
-
* `{ shouldShow: false, runs: [] }`. We deliberately swallow parse errors
|
|
37
|
-
* here: the wizard's normal entry point is more important than surfacing a
|
|
38
|
-
* malformed runs.json — the operator can still use `experiences runs ls` to
|
|
39
|
-
* diagnose later.
|
|
40
|
-
*/
|
|
41
|
-
export async function shouldShowRunPicker(input) {
|
|
42
|
-
if (!input.isTTY)
|
|
43
|
-
return { shouldShow: false, runs: [] };
|
|
44
|
-
if (hasBlockingFlag(input.flags))
|
|
45
|
-
return { shouldShow: false, runs: [] };
|
|
46
|
-
const path = input.runsJsonPath ?? runsFilePath();
|
|
47
|
-
let raw;
|
|
48
|
-
try {
|
|
49
|
-
raw = await readFile(path, 'utf8');
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return { shouldShow: false, runs: [] };
|
|
53
|
-
}
|
|
54
|
-
let parsed;
|
|
55
|
-
try {
|
|
56
|
-
parsed = JSON.parse(raw);
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return { shouldShow: false, runs: [] };
|
|
60
|
-
}
|
|
61
|
-
// Accept any version this CLI can read (see store.ts READABLE_VERSIONS).
|
|
62
|
-
// Pre-PR-#78 v1 files are migrated to v2 in memory below; gating on strict
|
|
63
|
-
// equality with RUNS_FILE_VERSION would silently hide every v1 run.
|
|
64
|
-
if (!READABLE_VERSIONS.has(parsed.version)) {
|
|
65
|
-
return { shouldShow: false, runs: [] };
|
|
66
|
-
}
|
|
67
|
-
const rawRuns = Array.isArray(parsed.runs) ? parsed.runs : [];
|
|
68
|
-
if (rawRuns.length === 0)
|
|
69
|
-
return { shouldShow: false, runs: [] };
|
|
70
|
-
const runs = rawRuns.map(migrateRecord);
|
|
71
|
-
return { shouldShow: true, runs };
|
|
72
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
4
|
-
import { useImmediateInput } from '../analyze/select/tui/hooks/useImmediateInput.js';
|
|
5
|
-
const COLLAPSED_LIMIT = 10;
|
|
6
|
-
const COLLAPSE_THRESHOLD = 12;
|
|
7
|
-
function formatCreatedAt(iso) {
|
|
8
|
-
const d = new Date(iso);
|
|
9
|
-
if (Number.isNaN(d.getTime()))
|
|
10
|
-
return iso;
|
|
11
|
-
const yyyy = d.getFullYear();
|
|
12
|
-
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
13
|
-
const dd = String(d.getDate()).padStart(2, '0');
|
|
14
|
-
const hh = String(d.getHours()).padStart(2, '0');
|
|
15
|
-
const mi = String(d.getMinutes()).padStart(2, '0');
|
|
16
|
-
return `${yyyy}-${mm}-${dd} ${hh}:${mi}`;
|
|
17
|
-
}
|
|
18
|
-
function runLine(run) {
|
|
19
|
-
const date = formatCreatedAt(run.createdAt);
|
|
20
|
-
const pushedTag = run.pushedTo ? 'pushed' : 'not pushed';
|
|
21
|
-
const count = `${run.componentCount} component${run.componentCount === 1 ? '' : 's'}`;
|
|
22
|
-
return `${run.id} - ${date} - ${run.projectPath} (${count}, ${pushedTag})`;
|
|
23
|
-
}
|
|
24
|
-
const ACTION_OPTIONS = [
|
|
25
|
-
{ key: 'push', label: 'Push', description: "Push this run's recorded session to Contentful." },
|
|
26
|
-
{ key: 'modify', label: 'Modify', description: 'Re-open the wizard at final-review with this run pre-filled.' },
|
|
27
|
-
{ key: 'cancel', label: 'Cancel', description: 'Back to the run list.' },
|
|
28
|
-
];
|
|
29
|
-
function ActionScreen({ runId, onChoose, onBack, }) {
|
|
30
|
-
const [focusIdx, setFocusIdx] = useState(0);
|
|
31
|
-
const fire = (opt) => {
|
|
32
|
-
if (opt === 'cancel')
|
|
33
|
-
return onBack();
|
|
34
|
-
onChoose(opt);
|
|
35
|
-
};
|
|
36
|
-
useImmediateInput((rawInput, key) => {
|
|
37
|
-
if (key.upArrow || rawInput === 'k') {
|
|
38
|
-
setFocusIdx((i) => (i - 1 + ACTION_OPTIONS.length) % ACTION_OPTIONS.length);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (key.downArrow || rawInput === 'j') {
|
|
42
|
-
setFocusIdx((i) => (i + 1) % ACTION_OPTIONS.length);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (key.return) {
|
|
46
|
-
fire(ACTION_OPTIONS[focusIdx].key);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (key.escape || rawInput === 'q') {
|
|
50
|
-
onBack();
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, children: "Push or modify?" }), _jsxs(Text, { dimColor: true, children: ["Run ", runId] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ACTION_OPTIONS.map((opt, i) => {
|
|
55
|
-
const focused = i === focusIdx;
|
|
56
|
-
return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: focused ? 'cyan' : undefined, children: focused ? '>' : ' ' }), _jsx(Text, { color: focused ? 'cyan' : undefined, children: opt.label }), _jsxs(Text, { dimColor: true, children: ["- ", opt.description] })] }, opt.key));
|
|
57
|
-
}) }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[j/k] Navigate" }), _jsx(Text, { dimColor: true, children: "[Enter] Select" }), _jsx(Text, { dimColor: true, children: "[Esc] Back" })] })] }));
|
|
58
|
-
}
|
|
59
|
-
function buildRows(runs, expanded) {
|
|
60
|
-
const showAll = expanded || runs.length < COLLAPSE_THRESHOLD;
|
|
61
|
-
const visible = showAll ? runs : runs.slice(0, COLLAPSED_LIMIT);
|
|
62
|
-
const rows = visible.map((run) => ({ kind: 'run', run }));
|
|
63
|
-
if (!showAll)
|
|
64
|
-
rows.push({ kind: 'show-all' });
|
|
65
|
-
rows.push({ kind: 'new' });
|
|
66
|
-
return rows;
|
|
67
|
-
}
|
|
68
|
-
export function RunPicker({ runs, staleRunIds, onSelect, onCancel }) {
|
|
69
|
-
const [expanded, setExpanded] = useState(false);
|
|
70
|
-
const [focusIdx, setFocusIdx] = useState(0);
|
|
71
|
-
const [actionRunId, setActionRunId] = useState(null);
|
|
72
|
-
const rows = buildRows(runs, expanded);
|
|
73
|
-
const clampedFocus = Math.min(focusIdx, rows.length - 1);
|
|
74
|
-
const selectRow = (row) => {
|
|
75
|
-
if (row.kind === 'show-all') {
|
|
76
|
-
setExpanded(true);
|
|
77
|
-
setFocusIdx(COLLAPSED_LIMIT);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (row.kind === 'new') {
|
|
81
|
-
onSelect({ runId: null, action: 'new' });
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
setActionRunId(row.run.id);
|
|
85
|
-
};
|
|
86
|
-
useImmediateInput((rawInput, key) => {
|
|
87
|
-
if (actionRunId !== null) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (key.upArrow || rawInput === 'k') {
|
|
91
|
-
setFocusIdx((i) => (i - 1 + rows.length) % rows.length);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (key.downArrow || rawInput === 'j') {
|
|
95
|
-
setFocusIdx((i) => (i + 1) % rows.length);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (rawInput === 'n') {
|
|
99
|
-
onSelect({ runId: null, action: 'new' });
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (rawInput === 'q' || key.escape) {
|
|
103
|
-
onCancel();
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (key.return) {
|
|
107
|
-
const row = rows[clampedFocus];
|
|
108
|
-
if (row)
|
|
109
|
-
selectRow(row);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
if (actionRunId !== null) {
|
|
114
|
-
return (_jsx(ActionScreen, { runId: actionRunId, onChoose: (action) => onSelect({ runId: actionRunId, action }), onBack: () => setActionRunId(null) }));
|
|
115
|
-
}
|
|
116
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, children: ["Found ", runs.length, " prior run", runs.length === 1 ? '' : 's', ". Continue from one?"] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: rows.map((row, i) => {
|
|
117
|
-
const focused = i === clampedFocus;
|
|
118
|
-
const color = focused ? 'cyan' : undefined;
|
|
119
|
-
const cursor = focused ? '>' : ' ';
|
|
120
|
-
if (row.kind === 'run') {
|
|
121
|
-
const isStale = staleRunIds?.has(row.run.id) ?? false;
|
|
122
|
-
return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: color, children: cursor }), _jsx(Text, { color: color, children: runLine(row.run) }), isStale ? _jsx(Text, { dimColor: true, children: " (stale)" }) : null] }, `run-${row.run.id}`));
|
|
123
|
-
}
|
|
124
|
-
if (row.kind === 'show-all') {
|
|
125
|
-
return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: color, children: cursor }), _jsxs(Text, { color: color, children: ["Show all (", runs.length, ")"] })] }, "show-all"));
|
|
126
|
-
}
|
|
127
|
-
return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: color, children: cursor }), _jsx(Text, { color: color, children: "[n] Start a new run" })] }, "new"));
|
|
128
|
-
}) }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[j/k] Navigate" }), _jsx(Text, { dimColor: true, children: "[Enter] Select" }), _jsx(Text, { dimColor: true, children: "[n] New" }), _jsx(Text, { dimColor: true, children: "[q] Quit" })] })] }));
|
|
129
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
4
|
-
import { useImmediateInput } from '../analyze/select/tui/hooks/useImmediateInput.js';
|
|
5
|
-
const OPTIONS = [
|
|
6
|
-
{ key: 'overwrite', label: 'overwrite', description: 'Replace existing files at this path.' },
|
|
7
|
-
{ key: 'new', label: 'new', description: 'Write to a timestamped subdirectory.' },
|
|
8
|
-
{ key: 'cancel', label: 'cancel', description: 'Go back and pick a different path.' },
|
|
9
|
-
];
|
|
10
|
-
export function SaveConflictGate({ path, onOverwrite, onNew, onCancel }) {
|
|
11
|
-
// Default cursor on "new" (index 1) — the safer non-destructive choice.
|
|
12
|
-
const [focusIdx, setFocusIdx] = useState(1);
|
|
13
|
-
const fire = (opt) => {
|
|
14
|
-
if (opt === 'overwrite')
|
|
15
|
-
onOverwrite();
|
|
16
|
-
else if (opt === 'new')
|
|
17
|
-
onNew();
|
|
18
|
-
else
|
|
19
|
-
onCancel();
|
|
20
|
-
};
|
|
21
|
-
useImmediateInput((rawInput, key) => {
|
|
22
|
-
if (rawInput === 'o') {
|
|
23
|
-
fire('overwrite');
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (rawInput === 'n') {
|
|
27
|
-
fire('new');
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
if (rawInput === 'c') {
|
|
31
|
-
fire('cancel');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (key.upArrow) {
|
|
35
|
-
setFocusIdx((i) => (i - 1 + OPTIONS.length) % OPTIONS.length);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (key.downArrow) {
|
|
39
|
-
setFocusIdx((i) => (i + 1) % OPTIONS.length);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (key.return) {
|
|
43
|
-
fire(OPTIONS[focusIdx].key);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
if (key.escape) {
|
|
47
|
-
onCancel();
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "A prior export exists at:" }), _jsx(Text, { children: path }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: OPTIONS.map((opt, i) => {
|
|
52
|
-
const focused = i === focusIdx;
|
|
53
|
-
const shortcut = opt.label[0];
|
|
54
|
-
return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: focused ? 'cyan' : undefined, children: focused ? '❯' : ' ' }), _jsxs(Text, { color: focused ? 'cyan' : undefined, children: ["[", shortcut, "]", opt.label.slice(1)] }), _jsxs(Text, { dimColor: true, children: ["\u2014 ", opt.description] })] }, opt.key));
|
|
55
|
-
}) }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[o/n/c] Shortcut" }), _jsx(Text, { dimColor: true, children: "[\u2191/\u2193 + Enter] Navigate" }), _jsx(Text, { dimColor: true, children: "[Esc] Cancel" })] })] }));
|
|
56
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { access } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
export const CONFLICT_MODES = ['overwrite', 'skip', 'fail'];
|
|
4
|
-
export function isConflictMode(value) {
|
|
5
|
-
return CONFLICT_MODES.includes(value);
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* The set of files the wizard / CLI may overwrite in the save directory.
|
|
9
|
-
* Centralised so conflict detection and conflict reporting stay in lock-step.
|
|
10
|
-
*/
|
|
11
|
-
const SAVE_FILES = ['components.json', 'tokens.json'];
|
|
12
|
-
/**
|
|
13
|
-
* Returns the subset of `SAVE_FILES` that already exist at `path`. Order is
|
|
14
|
-
* stable so test assertions can reason about it.
|
|
15
|
-
*/
|
|
16
|
-
async function listConflictingFiles(path) {
|
|
17
|
-
const conflicts = [];
|
|
18
|
-
for (const name of SAVE_FILES) {
|
|
19
|
-
try {
|
|
20
|
-
await access(join(path, name));
|
|
21
|
-
conflicts.push(name);
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
// ENOENT — file not present, keep checking the others.
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return conflicts;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Returns true if `components.json` or `tokens.json` already exists at `path`.
|
|
31
|
-
* Used by the wizard to decide whether to render the save-conflict gate before
|
|
32
|
-
* writing.
|
|
33
|
-
*/
|
|
34
|
-
export async function detectSaveConflict(path) {
|
|
35
|
-
return (await listConflictingFiles(path)).length > 0;
|
|
36
|
-
}
|
|
37
|
-
function pad2(n) {
|
|
38
|
-
return n < 10 ? `0${n}` : String(n);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Build a timestamped subdirectory under `base` for the "new" branch of the
|
|
42
|
-
* save-conflict gate. Format: `<base>/dsi-YYYYMMDD-HHMMSS` (local time).
|
|
43
|
-
*/
|
|
44
|
-
export function buildTimestampedSubdir(base, now = new Date()) {
|
|
45
|
-
const y = now.getFullYear();
|
|
46
|
-
const m = pad2(now.getMonth() + 1);
|
|
47
|
-
const d = pad2(now.getDate());
|
|
48
|
-
const hh = pad2(now.getHours());
|
|
49
|
-
const mm = pad2(now.getMinutes());
|
|
50
|
-
const ss = pad2(now.getSeconds());
|
|
51
|
-
return join(base, `dsi-${y}${m}${d}-${hh}${mm}${ss}`);
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Probe the requested save path and report whether the wizard needs to render
|
|
55
|
-
* the conflict gate, or — when `onConflict` is supplied — resolve the conflict
|
|
56
|
-
* headlessly using the chosen mode.
|
|
57
|
-
*/
|
|
58
|
-
export async function resolveSavePath(path, options = {}) {
|
|
59
|
-
const conflicts = await listConflictingFiles(path);
|
|
60
|
-
const hasConflict = conflicts.length > 0;
|
|
61
|
-
if (options.onConflict === undefined) {
|
|
62
|
-
return hasConflict ? { kind: 'conflict', path } : { kind: 'no-conflict', path };
|
|
63
|
-
}
|
|
64
|
-
if (!hasConflict) {
|
|
65
|
-
return { kind: 'write', path };
|
|
66
|
-
}
|
|
67
|
-
switch (options.onConflict) {
|
|
68
|
-
case 'overwrite':
|
|
69
|
-
return { kind: 'write', path };
|
|
70
|
-
case 'skip':
|
|
71
|
-
return { kind: 'write', path: buildTimestampedSubdir(path, options.now ?? new Date()) };
|
|
72
|
-
case 'fail':
|
|
73
|
-
return { kind: 'fail', conflict: { path, files: conflicts } };
|
|
74
|
-
}
|
|
75
|
-
}
|