@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,484 +0,0 @@
|
|
|
1
|
-
import { access, readFile } from 'node:fs/promises';
|
|
2
|
-
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import { createElement } from 'react';
|
|
4
|
-
import { render } from 'ink';
|
|
5
|
-
import { requireInteractiveTerminal } from '../../lib/terminal-capabilities.js';
|
|
6
|
-
import { getRefineArtifactsRoot, ensureRefineSession, getRefineSessionPaths, saveReviewState } from './persistence.js';
|
|
7
|
-
import { loadReviewInput } from './parser.js';
|
|
8
|
-
import { App } from './tui/App.js';
|
|
9
|
-
import { openPipelineDb, loadRawComponents, storeRawComponents, createStep, updateStep } from '../../session/db.js';
|
|
10
|
-
import { validateExtractedComponents, shouldExcludeDueToValidation, formatExclusionWarning, } from '@contentful/experience-design-system-extraction';
|
|
11
|
-
import { bindAnalyticsSessionId, enrichCommandResult, exitWithAnalytics } from '../../analytics/index.js';
|
|
12
|
-
const SAFE_PATH_RE = /^[a-zA-Z0-9_.$[\]=]+$/;
|
|
13
|
-
const PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
14
|
-
function applyDotPath(obj, path, value) {
|
|
15
|
-
if (!SAFE_PATH_RE.test(path)) {
|
|
16
|
-
process.stderr.write(`Warning: --patch path contains invalid characters: '${path}', skipping\n`);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
const parts = path.split('.');
|
|
20
|
-
if (parts.some((p) => PROTO_KEYS.has(p))) {
|
|
21
|
-
process.stderr.write(`Warning: --patch path contains forbidden key: '${path}', skipping\n`);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
let current = obj;
|
|
25
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
26
|
-
const part = parts[i];
|
|
27
|
-
// Handle array predicate syntax: field[name=value]
|
|
28
|
-
const arrayMatch = /^(.+)\[name=(.+)\]$/.exec(part);
|
|
29
|
-
if (arrayMatch) {
|
|
30
|
-
const [, fieldName, matchValue] = arrayMatch;
|
|
31
|
-
const arr = current[fieldName];
|
|
32
|
-
if (Array.isArray(arr)) {
|
|
33
|
-
const item = arr.find((el) => el['name'] === matchValue);
|
|
34
|
-
if (item) {
|
|
35
|
-
current = item;
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
process.stderr.write(`Warning: --patch array item [name=${matchValue}] not found in '${fieldName}', skipping\n`);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
if (typeof current[part] !== 'object' || current[part] === null) {
|
|
45
|
-
process.stderr.write(`Warning: --patch path '${path}' — '${part}' is not an object, skipping\n`);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
current = current[part];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const lastPart = parts[parts.length - 1];
|
|
52
|
-
current[lastPart] = value;
|
|
53
|
-
}
|
|
54
|
-
function applyPatch(snapshot, ops) {
|
|
55
|
-
const components = snapshot.components.map((c) => {
|
|
56
|
-
const op = ops.find((o) => o.component === c.name);
|
|
57
|
-
if (!op)
|
|
58
|
-
return c;
|
|
59
|
-
let updated = { ...c };
|
|
60
|
-
if (op.status) {
|
|
61
|
-
updated = { ...updated, status: op.status };
|
|
62
|
-
}
|
|
63
|
-
if (op.set) {
|
|
64
|
-
const editedProposal = structuredClone(updated.editedProposal);
|
|
65
|
-
for (const [path, value] of Object.entries(op.set)) {
|
|
66
|
-
applyDotPath(editedProposal, path, value);
|
|
67
|
-
}
|
|
68
|
-
updated = {
|
|
69
|
-
...updated,
|
|
70
|
-
editedProposal: editedProposal,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return updated;
|
|
74
|
-
});
|
|
75
|
-
return { ...snapshot, components };
|
|
76
|
-
}
|
|
77
|
-
async function runNonInteractive(snapshot, opts, paths, sessionId) {
|
|
78
|
-
// --exclude-components on its own (no --select-all / --select / --deselect /
|
|
79
|
-
// --reject / --patch) is the orchestrator's 422 retry-loop invocation: only
|
|
80
|
-
// the named components should change. The rebuild path below calls
|
|
81
|
-
// storeRawComponents which DELETEs all rows and re-inserts with default
|
|
82
|
-
// status='extracted', wiping the post-generate state required by
|
|
83
|
-
// loadCDFComponents (status='generated' + raw_props.cdf_type populated).
|
|
84
|
-
// Short-circuit through rejectComponentsByName, which is a pure UPDATE.
|
|
85
|
-
const onlyExcludeComponents = !!opts.excludeComponents &&
|
|
86
|
-
!opts.acceptAll &&
|
|
87
|
-
!opts.selectAll &&
|
|
88
|
-
!(opts.select ?? []).length &&
|
|
89
|
-
!(opts.deselect ?? []).length &&
|
|
90
|
-
!(opts.reject ?? []).length &&
|
|
91
|
-
!opts.patch;
|
|
92
|
-
if (onlyExcludeComponents) {
|
|
93
|
-
const names = opts
|
|
94
|
-
.excludeComponents.split(',')
|
|
95
|
-
.map((n) => n.trim())
|
|
96
|
-
.filter(Boolean);
|
|
97
|
-
if (names.length > 0) {
|
|
98
|
-
// paths.sessionDir = resolve(artifactsRoot, sessionId), so dirname gives
|
|
99
|
-
// back the artifactsRoot — keeps rejectComponentsByName from re-resolving
|
|
100
|
-
// it via env var when the caller already knows it.
|
|
101
|
-
const artifactsRoot = dirname(paths.sessionDir);
|
|
102
|
-
await rejectComponentsByName(sessionId, names, { artifactsRoot });
|
|
103
|
-
}
|
|
104
|
-
const accepted = snapshot.components.filter((c) => c.status === 'accepted').length;
|
|
105
|
-
const rejected = snapshot.components.filter((c) => c.status === 'rejected').length + (names.length || 0);
|
|
106
|
-
enrichCommandResult({ accepted_component_count: accepted });
|
|
107
|
-
process.stderr.write(`Accepted: ${accepted} Rejected: ${rejected}\n`);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
let result = { ...snapshot };
|
|
111
|
-
const rejectPatterns = [...(opts.reject ?? []), ...(opts.deselect ?? [])].map((p) => p.toLowerCase());
|
|
112
|
-
const selectPatterns = (opts.select ?? []).map((p) => p.toLowerCase());
|
|
113
|
-
const selectAll = opts.acceptAll || opts.selectAll;
|
|
114
|
-
// Fail-loud gate: --select-all stops with a non-zero exit when ANY component
|
|
115
|
-
// has error-severity validation issues. The user must opt in to auto-rejection
|
|
116
|
-
// with --exclude-invalid. Silent exclusion in CI / orchestrator contexts is
|
|
117
|
-
// dangerous — the caller should see and acknowledge that components are
|
|
118
|
-
// being dropped from the import.
|
|
119
|
-
if (selectAll && !opts.excludeInvalid) {
|
|
120
|
-
const invalid = result.components.filter((c) => shouldExcludeDueToValidation(c.originalProposal));
|
|
121
|
-
if (invalid.length > 0) {
|
|
122
|
-
const lines = [
|
|
123
|
-
`Error: ${invalid.length} component(s) failed validation; refusing --select-all without --exclude-invalid:`,
|
|
124
|
-
];
|
|
125
|
-
for (const c of invalid) {
|
|
126
|
-
const codes = (c.originalProposal.validationIssues ?? [])
|
|
127
|
-
.filter((i) => i.severity === 'error')
|
|
128
|
-
.map((i) => i.code)
|
|
129
|
-
.join(', ');
|
|
130
|
-
lines.push(` ✗ ${c.name} ${codes}`);
|
|
131
|
-
}
|
|
132
|
-
lines.push('');
|
|
133
|
-
lines.push('Re-run with --exclude-invalid to auto-reject these components, or run analyze select interactively to fix them.');
|
|
134
|
-
process.stderr.write(lines.join('\n') + '\n');
|
|
135
|
-
await exitWithAnalytics(1);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (selectAll || rejectPatterns.length > 0 || selectPatterns.length > 0) {
|
|
140
|
-
result = {
|
|
141
|
-
...result,
|
|
142
|
-
components: result.components.map((c) => {
|
|
143
|
-
const nameLower = c.name.toLowerCase();
|
|
144
|
-
if (rejectPatterns.some((p) => nameLower.includes(p))) {
|
|
145
|
-
return { ...c, status: 'rejected' };
|
|
146
|
-
}
|
|
147
|
-
if (selectAll && opts.excludeInvalid && shouldExcludeDueToValidation(c.originalProposal)) {
|
|
148
|
-
return { ...c, status: 'rejected' };
|
|
149
|
-
}
|
|
150
|
-
if (selectAll || selectPatterns.some((p) => nameLower.includes(p))) {
|
|
151
|
-
return { ...c, status: 'accepted' };
|
|
152
|
-
}
|
|
153
|
-
return c;
|
|
154
|
-
}),
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
// Apply --exclude-components: force-reject the named components regardless of
|
|
158
|
-
// how --select-all / --select / --deselect classified them. Used by the
|
|
159
|
-
// orchestrator's 422 retry loop to exclude server-validation offenders.
|
|
160
|
-
if (opts.excludeComponents) {
|
|
161
|
-
const excludeNames = new Set(opts.excludeComponents
|
|
162
|
-
.split(',')
|
|
163
|
-
.map((n) => n.trim())
|
|
164
|
-
.filter(Boolean));
|
|
165
|
-
if (excludeNames.size > 0) {
|
|
166
|
-
result = {
|
|
167
|
-
...result,
|
|
168
|
-
components: result.components.map((c) => excludeNames.has(c.name) ? { ...c, status: 'rejected' } : c),
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// Apply --patch
|
|
173
|
-
if (opts.patch) {
|
|
174
|
-
let patchOps;
|
|
175
|
-
try {
|
|
176
|
-
const raw = await readFile(resolve(opts.patch), 'utf8');
|
|
177
|
-
const parsed = JSON.parse(raw);
|
|
178
|
-
if (!Array.isArray(parsed)) {
|
|
179
|
-
process.stderr.write(`Error: --patch file must be a JSON array of patch operations: ${opts.patch}\n`);
|
|
180
|
-
await exitWithAnalytics(1);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
patchOps = parsed;
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
process.stderr.write(`Error: cannot read or parse --patch file: ${opts.patch}\n`);
|
|
187
|
-
await exitWithAnalytics(1);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
// Warn on unknown component names
|
|
191
|
-
const knownNames = new Set(result.components.map((c) => c.name));
|
|
192
|
-
for (const op of patchOps) {
|
|
193
|
-
if (!knownNames.has(op.component)) {
|
|
194
|
-
process.stderr.write(`Warning: --patch targets unknown component '${op.component}', skipping\n`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
result = applyPatch(result, patchOps);
|
|
198
|
-
}
|
|
199
|
-
const accepted = result.components.filter((c) => c.status === 'accepted');
|
|
200
|
-
const rejected = result.components.filter((c) => c.status === 'rejected');
|
|
201
|
-
// Surface what was excluded by --select-all --exclude-invalid so the
|
|
202
|
-
// non-interactive caller (CI, orchestrator, scripted pipeline) doesn't
|
|
203
|
-
// have to guess what failed validation. The warning is printed BEFORE
|
|
204
|
-
// saveReviewState so the message lands ahead of the bare counts.
|
|
205
|
-
if (selectAll && opts.excludeInvalid) {
|
|
206
|
-
const autoRejected = rejected
|
|
207
|
-
.filter((c) => shouldExcludeDueToValidation(c.originalProposal))
|
|
208
|
-
.map((c) => ({ name: c.name, validationIssues: c.originalProposal.validationIssues }));
|
|
209
|
-
process.stderr.write(formatExclusionWarning(autoRejected));
|
|
210
|
-
}
|
|
211
|
-
// Persist decisions to session state so pipeline orchestrator can read them
|
|
212
|
-
await saveReviewState(paths.statePath, result);
|
|
213
|
-
// Sync edited proposals back to the DB so generation uses the user's edits.
|
|
214
|
-
//
|
|
215
|
-
// Only --patch can mutate editedProposal — the other non-interactive flags
|
|
216
|
-
// (--select-all / --select / --deselect / --reject / --exclude-invalid)
|
|
217
|
-
// change status only. Calling storeRawComponents in the no-edit case is
|
|
218
|
-
// not just wasteful: it's destructive. storeRawComponents starts with
|
|
219
|
-
// `DELETE FROM raw_components WHERE session_id = ?` and re-inserts only
|
|
220
|
-
// the components passed in — so on a --select-all run, every rejected
|
|
221
|
-
// component (including its raw_slots / raw_props children) is physically
|
|
222
|
-
// removed. A second invocation of the same command would then see a
|
|
223
|
-
// smaller snapshot, lose the rejected component(s), and report different
|
|
224
|
-
// counts.
|
|
225
|
-
if (opts.patch && accepted.length > 0) {
|
|
226
|
-
const db = openPipelineDb();
|
|
227
|
-
try {
|
|
228
|
-
storeRawComponents(db, sessionId, accepted.map((c) => c.editedProposal));
|
|
229
|
-
}
|
|
230
|
-
finally {
|
|
231
|
-
db.close();
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
enrichCommandResult({ accepted_component_count: accepted.length });
|
|
235
|
-
process.stderr.write(`Accepted: ${accepted.length} Rejected: ${rejected.length}\n`);
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Load components from the pipeline DB and re-run extraction validation.
|
|
239
|
-
*
|
|
240
|
-
* `validationIssues` is intentionally not persisted (the validator is pure
|
|
241
|
-
* and cheap to re-run), so any cold-start of `analyze select` from a prior
|
|
242
|
-
* `analyze extract` session needs to recompute it before building the
|
|
243
|
-
* review snapshot — otherwise the TUI sees no validation errors at all.
|
|
244
|
-
*/
|
|
245
|
-
export async function loadAndValidateForReview(sessionId, projectRoot) {
|
|
246
|
-
const db = openPipelineDb();
|
|
247
|
-
let rawComponents;
|
|
248
|
-
try {
|
|
249
|
-
rawComponents = loadRawComponents(db, sessionId);
|
|
250
|
-
}
|
|
251
|
-
finally {
|
|
252
|
-
db.close();
|
|
253
|
-
}
|
|
254
|
-
const validatedComponents = validateExtractedComponents(rawComponents);
|
|
255
|
-
return loadReviewInput(validatedComponents, { reviewRoot: projectRoot });
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Synthesize SERVER_VALIDATION_FAILED issues onto the review state file's
|
|
259
|
-
* matching components. Used by the wizard's 422 recovery path: after a
|
|
260
|
-
* preview-API validation error, this runs before re-launching the
|
|
261
|
-
* analyze-select TUI so the Sidebar's existing red-warning rendering
|
|
262
|
-
* pins the offenders to the top.
|
|
263
|
-
*
|
|
264
|
-
* Idempotent on identical input is NOT a contract: each call appends.
|
|
265
|
-
* The wizard's 422 path is a one-shot per preview attempt, so duplicate
|
|
266
|
-
* issues only matter if a caller invokes this twice without the user
|
|
267
|
-
* round-tripping through the TUI in between.
|
|
268
|
-
*/
|
|
269
|
-
export async function patchReviewStateWithValidationErrors(sessionId, errors, opts = {}) {
|
|
270
|
-
const artifactsRoot = opts.artifactsRoot ?? getRefineArtifactsRoot();
|
|
271
|
-
const paths = await getRefineSessionPaths(sessionId, artifactsRoot);
|
|
272
|
-
let snapshot;
|
|
273
|
-
try {
|
|
274
|
-
await access(paths.statePath);
|
|
275
|
-
snapshot = JSON.parse(await readFile(paths.statePath, 'utf8'));
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
snapshot = await loadAndValidateForReview(sessionId, undefined);
|
|
279
|
-
snapshot = await ensureRefineSession(sessionId, artifactsRoot, snapshot);
|
|
280
|
-
}
|
|
281
|
-
const errorsByName = new Map();
|
|
282
|
-
for (const err of errors) {
|
|
283
|
-
const list = errorsByName.get(err.componentName) ?? [];
|
|
284
|
-
list.push(err);
|
|
285
|
-
errorsByName.set(err.componentName, list);
|
|
286
|
-
}
|
|
287
|
-
const patchedNames = [];
|
|
288
|
-
const knownNames = new Set(snapshot.components.map((c) => c.name));
|
|
289
|
-
const components = snapshot.components.map((c) => {
|
|
290
|
-
const matched = errorsByName.get(c.name);
|
|
291
|
-
if (!matched || matched.length === 0)
|
|
292
|
-
return c;
|
|
293
|
-
patchedNames.push(c.name);
|
|
294
|
-
const newIssues = matched.map((err) => ({
|
|
295
|
-
severity: 'error',
|
|
296
|
-
code: 'SERVER_VALIDATION_FAILED',
|
|
297
|
-
message: err.message,
|
|
298
|
-
}));
|
|
299
|
-
const existing = c.originalProposal.validationIssues ?? [];
|
|
300
|
-
return {
|
|
301
|
-
...c,
|
|
302
|
-
originalProposal: {
|
|
303
|
-
...c.originalProposal,
|
|
304
|
-
validationIssues: [...existing, ...newIssues],
|
|
305
|
-
},
|
|
306
|
-
};
|
|
307
|
-
});
|
|
308
|
-
const missingNames = [...errorsByName.keys()].filter((n) => !knownNames.has(n));
|
|
309
|
-
await saveReviewState(paths.statePath, { ...snapshot, components });
|
|
310
|
-
return { patchedNames, missingNames };
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Exclude components whose names appear in `names` from the next preview/push.
|
|
314
|
-
* Used by the wizard's "skip and retry" path after a 422.
|
|
315
|
-
*
|
|
316
|
-
* Two stores are updated:
|
|
317
|
-
* - Pipeline DB (`raw_components.status` → `'generate-rejected'`) so that
|
|
318
|
-
* `loadCDFComponents()` excludes them from the next manifest. This is the
|
|
319
|
-
* load-bearing change — preview reads from the DB, not the JSON file.
|
|
320
|
-
* - Review state JSON (`status` → `'rejected'`) so that if the user re-enters
|
|
321
|
-
* the analyze-select TUI later, the rejected status is reflected.
|
|
322
|
-
*
|
|
323
|
-
* `'generate-rejected'` matches the existing convention for "component was
|
|
324
|
-
* generated successfully but later excluded" (see GenerateReviewStep) — the
|
|
325
|
-
* component generated fine locally but failed server-side validation.
|
|
326
|
-
*/
|
|
327
|
-
export async function rejectComponentsByName(sessionId, names, opts = {}) {
|
|
328
|
-
if (names.length === 0)
|
|
329
|
-
return;
|
|
330
|
-
const db = openPipelineDb();
|
|
331
|
-
try {
|
|
332
|
-
const placeholders = names.map(() => '?').join(',');
|
|
333
|
-
db.prepare(`UPDATE raw_components SET status = 'generate-rejected' WHERE session_id = ? AND name IN (${placeholders})`).run(sessionId, ...names);
|
|
334
|
-
}
|
|
335
|
-
finally {
|
|
336
|
-
db.close();
|
|
337
|
-
}
|
|
338
|
-
const artifactsRoot = opts.artifactsRoot ?? getRefineArtifactsRoot();
|
|
339
|
-
const paths = await getRefineSessionPaths(sessionId, artifactsRoot);
|
|
340
|
-
const nameSet = new Set(names);
|
|
341
|
-
let snapshot;
|
|
342
|
-
try {
|
|
343
|
-
snapshot = JSON.parse(await readFile(paths.statePath, 'utf8'));
|
|
344
|
-
}
|
|
345
|
-
catch {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const components = snapshot.components.map((c) => (nameSet.has(c.name) ? { ...c, status: 'rejected' } : c));
|
|
349
|
-
await saveReviewState(paths.statePath, { ...snapshot, components });
|
|
350
|
-
}
|
|
351
|
-
async function resolveSessionId(sessionFlag) {
|
|
352
|
-
if (sessionFlag)
|
|
353
|
-
return sessionFlag;
|
|
354
|
-
const db = openPipelineDb();
|
|
355
|
-
try {
|
|
356
|
-
const row = db
|
|
357
|
-
.prepare(`SELECT s.id FROM sessions s
|
|
358
|
-
JOIN steps st ON st.session_id = s.id
|
|
359
|
-
WHERE st.command = 'analyze extract'
|
|
360
|
-
AND st.status = 'complete'
|
|
361
|
-
ORDER BY st.started_at DESC
|
|
362
|
-
LIMIT 1`)
|
|
363
|
-
.get();
|
|
364
|
-
if (!row) {
|
|
365
|
-
process.stderr.write('Error: no completed analyze extract session found. Run analyze extract first, or pass --session <id>.\n');
|
|
366
|
-
return await exitWithAnalytics(1);
|
|
367
|
-
}
|
|
368
|
-
return row.id;
|
|
369
|
-
}
|
|
370
|
-
finally {
|
|
371
|
-
db.close();
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
export function registerAnalyzeEditCommand(program) {
|
|
375
|
-
program
|
|
376
|
-
.command('select')
|
|
377
|
-
.alias('edit')
|
|
378
|
-
.description('Select components for generation and optionally patch their definitions')
|
|
379
|
-
.option('--session <id>', 'Session ID from analyze extract (defaults to most recent)')
|
|
380
|
-
.option('--project-root <path>', 'Project root for resolving component source files')
|
|
381
|
-
.option('--select-all', 'Select all components without launching the TUI')
|
|
382
|
-
.option('--select <pattern>', 'Select components whose name contains pattern (repeatable)', collect, [])
|
|
383
|
-
.option('--deselect <pattern>', 'Deselect components whose name contains pattern (repeatable)', collect, [])
|
|
384
|
-
.option('--accept-all', 'Alias for --select-all', false)
|
|
385
|
-
.option('--reject <pattern>', 'Alias for --deselect <pattern> (repeatable)', collect, [])
|
|
386
|
-
.option('--patch <path>', 'Path to a JSON patch file for structured component overrides')
|
|
387
|
-
.option('--exclude-invalid', 'With --select-all: auto-reject components with validation errors instead of failing loud (opt-in bypass for the gate)')
|
|
388
|
-
.option('--exclude-components <names>', 'Comma-separated component names to force-reject, regardless of other selection flags')
|
|
389
|
-
.action(async ({ session: sessionFlag, projectRoot, acceptAll, selectAll, reject, deselect, select, patch, excludeInvalid, excludeComponents, }) => {
|
|
390
|
-
const sessionId = await resolveSessionId(sessionFlag);
|
|
391
|
-
await bindAnalyticsSessionId(sessionId);
|
|
392
|
-
const db = openPipelineDb();
|
|
393
|
-
let rawComponentCount = 0;
|
|
394
|
-
try {
|
|
395
|
-
rawComponentCount = loadRawComponents(db, sessionId).length;
|
|
396
|
-
}
|
|
397
|
-
finally {
|
|
398
|
-
db.close();
|
|
399
|
-
}
|
|
400
|
-
if (rawComponentCount === 0) {
|
|
401
|
-
process.stderr.write(`Error: session '${sessionId}' has no raw components. Run analyze extract first.\n`);
|
|
402
|
-
await exitWithAnalytics(1);
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
const artifactsRoot = getRefineArtifactsRoot();
|
|
406
|
-
const nonInteractive = acceptAll ||
|
|
407
|
-
selectAll ||
|
|
408
|
-
(reject ?? []).length > 0 ||
|
|
409
|
-
(deselect ?? []).length > 0 ||
|
|
410
|
-
(select ?? []).length > 0 ||
|
|
411
|
-
!!patch ||
|
|
412
|
-
!!excludeComponents;
|
|
413
|
-
let paths;
|
|
414
|
-
let snapshot;
|
|
415
|
-
try {
|
|
416
|
-
snapshot = await loadAndValidateForReview(sessionId, projectRoot);
|
|
417
|
-
paths = await getRefineSessionPaths(sessionId, artifactsRoot);
|
|
418
|
-
if (!nonInteractive) {
|
|
419
|
-
snapshot = await ensureRefineSession(sessionId, artifactsRoot, snapshot);
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
await ensureRefineSession(sessionId, artifactsRoot, snapshot);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
catch (error) {
|
|
426
|
-
process.stderr.write(`Error: unable to initialize refine session.\n${error instanceof Error ? error.message : String(error)}\n`);
|
|
427
|
-
await exitWithAnalytics(1);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
// Non-interactive path
|
|
431
|
-
if (nonInteractive) {
|
|
432
|
-
const stepDb = openPipelineDb();
|
|
433
|
-
const stepId = createStep(stepDb, sessionId, 'analyze select', {
|
|
434
|
-
sessionId,
|
|
435
|
-
});
|
|
436
|
-
try {
|
|
437
|
-
await runNonInteractive(snapshot, {
|
|
438
|
-
session: sessionFlag,
|
|
439
|
-
projectRoot,
|
|
440
|
-
acceptAll,
|
|
441
|
-
selectAll,
|
|
442
|
-
reject,
|
|
443
|
-
deselect,
|
|
444
|
-
select,
|
|
445
|
-
patch,
|
|
446
|
-
excludeInvalid,
|
|
447
|
-
excludeComponents,
|
|
448
|
-
}, paths, sessionId);
|
|
449
|
-
updateStep(stepDb, stepId, 'complete', { sessionId });
|
|
450
|
-
}
|
|
451
|
-
catch (err) {
|
|
452
|
-
updateStep(stepDb, stepId, 'failed', {}, err instanceof Error ? err.message : String(err));
|
|
453
|
-
stepDb.close();
|
|
454
|
-
throw err;
|
|
455
|
-
}
|
|
456
|
-
stepDb.close();
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
// Test mode: print launch contract and return without launching TUI
|
|
460
|
-
if (process.env.EDS_REVIEW_TEST_MODE === '1') {
|
|
461
|
-
process.stdout.write(`session=${sessionId}\n` +
|
|
462
|
-
`session_dir=${paths.sessionDir}\n` +
|
|
463
|
-
`events.jsonl=${paths.eventsPath}\n` +
|
|
464
|
-
`current-review-state.json=${paths.statePath}\n`);
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
requireInteractiveTerminal({
|
|
468
|
-
alternative: 'pass `--accept-all`, `--select-all`, `--reject`, `--deselect`, `--select`, or `--patch`',
|
|
469
|
-
});
|
|
470
|
-
if (process.stdout.columns !== undefined && process.stdout.columns < 60) {
|
|
471
|
-
process.stderr.write(`Error: terminal too narrow (${process.stdout.columns} cols). Resize to 60+ columns.\n`);
|
|
472
|
-
await exitWithAnalytics(1);
|
|
473
|
-
}
|
|
474
|
-
const { waitUntilExit } = render(createElement(App, {
|
|
475
|
-
sessionId,
|
|
476
|
-
artifactsRoot,
|
|
477
|
-
reviewRoot: projectRoot,
|
|
478
|
-
}));
|
|
479
|
-
await waitUntilExit();
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
function collect(val, prev) {
|
|
483
|
-
return [...prev, val];
|
|
484
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { registerAnalyzeEditCommand } from './command.js';
|
|
2
|
-
export { loadReviewInput } from './parser.js';
|
|
3
|
-
export { appendReviewEvent, ensureRefineSession, getRefineArtifactsRoot, getRefineSessionPaths, saveReviewState, } from './persistence.js';
|
|
4
|
-
export { formatFinalizeContract } from './stdout.js';
|
|
5
|
-
export { createReviewSessionSummary } from './types.js';
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { access } from 'node:fs/promises';
|
|
3
|
-
import { isAbsolute, relative, resolve } from 'node:path';
|
|
4
|
-
function createComponentId(name, resolvedSourcePath) {
|
|
5
|
-
const sourceHash = createHash('sha256').update(`${name}:${resolvedSourcePath}`).digest('hex').slice(0, 12);
|
|
6
|
-
return `${name}-${sourceHash}`;
|
|
7
|
-
}
|
|
8
|
-
async function resolveComponentSourcePath(source, reviewRoot) {
|
|
9
|
-
// Absolute paths stored in the DB are used directly — no reviewRoot boundary check needed.
|
|
10
|
-
if (isAbsolute(source)) {
|
|
11
|
-
try {
|
|
12
|
-
await access(source);
|
|
13
|
-
return source;
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
throw new Error(`Unable to access component source at ${source}`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
const candidate = resolve(reviewRoot, source);
|
|
20
|
-
const relativeToRoot = relative(reviewRoot, candidate);
|
|
21
|
-
if (relativeToRoot.startsWith('..') || relativeToRoot === '..' || isAbsolute(relativeToRoot)) {
|
|
22
|
-
throw new Error(`Resolved component source is outside the review root: ${source}. Pass --project-root <path> to set the correct base.`);
|
|
23
|
-
}
|
|
24
|
-
try {
|
|
25
|
-
await access(candidate);
|
|
26
|
-
return candidate;
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
throw new Error(`Unable to access component source at ${candidate}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
export async function loadReviewInput(components, options = {}) {
|
|
33
|
-
const reviewRoot = resolve(options.reviewRoot ?? process.cwd());
|
|
34
|
-
const records = await Promise.all(components.map(async (component) => {
|
|
35
|
-
let resolvedSourcePath;
|
|
36
|
-
try {
|
|
37
|
-
resolvedSourcePath = await resolveComponentSourcePath(component.source, reviewRoot);
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
throw new Error(`Unable to access component source for ${component.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
id: createComponentId(component.name, resolvedSourcePath),
|
|
44
|
-
name: component.name,
|
|
45
|
-
resolvedSourcePath,
|
|
46
|
-
sourceCode: null,
|
|
47
|
-
originalProposal: component,
|
|
48
|
-
editedProposal: structuredClone(component),
|
|
49
|
-
status: 'needs-review',
|
|
50
|
-
};
|
|
51
|
-
}));
|
|
52
|
-
return { components: records };
|
|
53
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { access, appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { resolve } from 'node:path';
|
|
4
|
-
import { loadRawComponents } from '../../session/db.js';
|
|
5
|
-
export function getRefineArtifactsRoot() {
|
|
6
|
-
if (process.env.EDS_REVIEW_ARTIFACTS_DIR) {
|
|
7
|
-
return resolve(process.env.EDS_REVIEW_ARTIFACTS_DIR);
|
|
8
|
-
}
|
|
9
|
-
return resolve(homedir(), '.contentful', 'experience-design-system-cli', 'reviews');
|
|
10
|
-
}
|
|
11
|
-
export async function getRefineSessionPaths(sessionId, artifactsRoot) {
|
|
12
|
-
const sessionDir = resolve(artifactsRoot, sessionId);
|
|
13
|
-
return {
|
|
14
|
-
sessionDir,
|
|
15
|
-
eventsPath: resolve(sessionDir, 'events.jsonl'),
|
|
16
|
-
statePath: resolve(sessionDir, 'current-review-state.json'),
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
export async function saveReviewState(statePath, session) {
|
|
20
|
-
await writeFile(statePath, JSON.stringify(session, null, 2), 'utf8');
|
|
21
|
-
}
|
|
22
|
-
export async function appendReviewEvent(eventsPath, event) {
|
|
23
|
-
const record = {
|
|
24
|
-
type: event.type,
|
|
25
|
-
timestamp: new Date().toISOString(),
|
|
26
|
-
payload: event.payload,
|
|
27
|
-
};
|
|
28
|
-
await appendFile(eventsPath, JSON.stringify(record) + '\n', 'utf8');
|
|
29
|
-
}
|
|
30
|
-
export async function ensureRefineSession(sessionId, artifactsRoot, initialSnapshot) {
|
|
31
|
-
const paths = await getRefineSessionPaths(sessionId, artifactsRoot);
|
|
32
|
-
await mkdir(paths.sessionDir, { recursive: true });
|
|
33
|
-
try {
|
|
34
|
-
await access(paths.statePath);
|
|
35
|
-
const savedState = await readFile(paths.statePath, 'utf8');
|
|
36
|
-
return JSON.parse(savedState);
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
await writeFile(paths.statePath, JSON.stringify(initialSnapshot, null, 2), 'utf8');
|
|
40
|
-
await writeFile(paths.eventsPath, '', 'utf8');
|
|
41
|
-
return initialSnapshot;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Persist scope-gate decisions to `current-review-state.json` so downstream
|
|
46
|
-
* consumers (notably `loadAcceptedNames` in `generate components`) can filter
|
|
47
|
-
* out rejected components. The wizard's scope-gate doesn't drive the rich
|
|
48
|
-
* `analyze select` TUI, so fields that file consumes (resolvedSourcePath,
|
|
49
|
-
* sourceCode) are populated with safe placeholders. Only `name` and `status`
|
|
50
|
-
* matter for `loadAcceptedNames`.
|
|
51
|
-
*/
|
|
52
|
-
export async function writeScopeDecisionsSnapshot(db, sessionId, decisions) {
|
|
53
|
-
const acceptedSet = new Set(decisions.accepted);
|
|
54
|
-
const rawComponents = loadRawComponents(db, sessionId);
|
|
55
|
-
const records = rawComponents.map((c) => {
|
|
56
|
-
const status = acceptedSet.has(c.name) ? 'accepted' : 'rejected';
|
|
57
|
-
const proposal = {
|
|
58
|
-
name: c.name,
|
|
59
|
-
source: c.source,
|
|
60
|
-
framework: c.framework,
|
|
61
|
-
props: c.props,
|
|
62
|
-
slots: c.slots,
|
|
63
|
-
...(c.extractionConfidence !== undefined ? { extractionConfidence: c.extractionConfidence } : {}),
|
|
64
|
-
...(c.reviewReasons !== undefined ? { reviewReasons: c.reviewReasons } : {}),
|
|
65
|
-
...(c.needsReview !== undefined ? { needsReview: c.needsReview } : {}),
|
|
66
|
-
};
|
|
67
|
-
return {
|
|
68
|
-
id: c.component_id,
|
|
69
|
-
name: c.name,
|
|
70
|
-
resolvedSourcePath: '',
|
|
71
|
-
sourceCode: null,
|
|
72
|
-
originalProposal: proposal,
|
|
73
|
-
editedProposal: proposal,
|
|
74
|
-
status,
|
|
75
|
-
};
|
|
76
|
-
});
|
|
77
|
-
const paths = await getRefineSessionPaths(sessionId, getRefineArtifactsRoot());
|
|
78
|
-
await mkdir(paths.sessionDir, { recursive: true });
|
|
79
|
-
await saveReviewState(paths.statePath, { components: records });
|
|
80
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure mapper from a `ServerPreviewResponse` to a per-component-name
|
|
3
|
-
* `PreviewAnnotation` map. Consumed by the wizard's final-review sidebar
|
|
4
|
-
* (and any other surface that wants to render diff badges next to a
|
|
5
|
-
* component row).
|
|
6
|
-
*
|
|
7
|
-
* The `new` bucket of `ServerPreviewResponse.components` is `CDFComponentEntry[]`
|
|
8
|
-
* — those entries do NOT carry a `name` field on the wire (the name is the
|
|
9
|
-
* KEY in the parent manifest). So instead of trying to read names off entries,
|
|
10
|
-
* we derive the "new" set as:
|
|
11
|
-
*
|
|
12
|
-
* new = localNames \ (unchangedNames ∪ changedNames ∪ removedNames)
|
|
13
|
-
*
|
|
14
|
-
* Callers must pass the local manifest's component keys as `localNames` so
|
|
15
|
-
* we can perform the set difference. Removed components from the server that
|
|
16
|
-
* aren't in the local manifest are still annotated as `'removed'` (so the
|
|
17
|
-
* detail panel can list them).
|
|
18
|
-
*
|
|
19
|
-
* Precedence: `breaking` > `changed` > `removed` > `new`.
|
|
20
|
-
*
|
|
21
|
-
* Unchanged entries are omitted from the map — callers should treat
|
|
22
|
-
* `map.get(name) === undefined` as "no annotation".
|
|
23
|
-
*/
|
|
24
|
-
export function applyPreviewAnnotations(preview, localNames) {
|
|
25
|
-
const out = new Map();
|
|
26
|
-
const unchanged = new Set(preview.components.unchanged);
|
|
27
|
-
// Annotate changed first; collect their names so we can exclude from "new".
|
|
28
|
-
const changedNames = new Set();
|
|
29
|
-
for (const item of preview.components.changed) {
|
|
30
|
-
const name = item.current?.name;
|
|
31
|
-
if (typeof name !== 'string')
|
|
32
|
-
continue;
|
|
33
|
-
changedNames.add(name);
|
|
34
|
-
if (item.changeClassification?.classification === 'breaking') {
|
|
35
|
-
out.set(name, 'breaking');
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
out.set(name, 'changed');
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// Annotate removed.
|
|
42
|
-
const removedNames = new Set();
|
|
43
|
-
for (const entity of preview.components.removed) {
|
|
44
|
-
if (entity.name) {
|
|
45
|
-
removedNames.add(entity.name);
|
|
46
|
-
out.set(entity.name, 'removed');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// Derive `new` = localNames \ (unchanged ∪ changed ∪ removed).
|
|
50
|
-
for (const name of localNames) {
|
|
51
|
-
if (unchanged.has(name))
|
|
52
|
-
continue;
|
|
53
|
-
if (changedNames.has(name))
|
|
54
|
-
continue;
|
|
55
|
-
if (removedNames.has(name))
|
|
56
|
-
continue;
|
|
57
|
-
if (out.has(name))
|
|
58
|
-
continue;
|
|
59
|
-
out.set(name, 'new');
|
|
60
|
-
}
|
|
61
|
-
return out;
|
|
62
|
-
}
|