@contentful/experience-design-system-cli 2.2.1
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 +532 -0
- package/bin/cli.js +58 -0
- package/dist/package.json +56 -0
- package/dist/src/analyze/command.d.ts +3 -0
- package/dist/src/analyze/command.js +175 -0
- package/dist/src/analyze/extract/astro.d.ts +5 -0
- package/dist/src/analyze/extract/astro.js +280 -0
- package/dist/src/analyze/extract/pipeline.d.ts +6 -0
- package/dist/src/analyze/extract/pipeline.js +298 -0
- package/dist/src/analyze/extract/react.d.ts +2 -0
- package/dist/src/analyze/extract/react.js +1949 -0
- package/dist/src/analyze/extract/slot-detection.d.ts +35 -0
- package/dist/src/analyze/extract/slot-detection.js +101 -0
- package/dist/src/analyze/extract/stencil.d.ts +2 -0
- package/dist/src/analyze/extract/stencil.js +293 -0
- package/dist/src/analyze/extract/tsx-shared.d.ts +8 -0
- package/dist/src/analyze/extract/tsx-shared.js +263 -0
- package/dist/src/analyze/extract/vue-tsx.d.ts +2 -0
- package/dist/src/analyze/extract/vue-tsx.js +498 -0
- package/dist/src/analyze/extract/vue.d.ts +5 -0
- package/dist/src/analyze/extract/vue.js +647 -0
- package/dist/src/analyze/extract/web-components.d.ts +2 -0
- package/dist/src/analyze/extract/web-components.js +866 -0
- package/dist/src/analyze/pre-classify.d.ts +17 -0
- package/dist/src/analyze/pre-classify.js +144 -0
- package/dist/src/analyze/select/command.d.ts +2 -0
- package/dist/src/analyze/select/command.js +256 -0
- package/dist/src/analyze/select/index.d.ts +6 -0
- package/dist/src/analyze/select/index.js +5 -0
- package/dist/src/analyze/select/parser.d.ts +6 -0
- package/dist/src/analyze/select/parser.js +53 -0
- package/dist/src/analyze/select/persistence.d.ts +9 -0
- package/dist/src/analyze/select/persistence.js +42 -0
- package/dist/src/analyze/select/stdout.d.ts +7 -0
- package/dist/src/analyze/select/stdout.js +3 -0
- package/dist/src/analyze/select/tui/App.d.ts +8 -0
- package/dist/src/analyze/select/tui/App.js +491 -0
- package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +20 -0
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +43 -0
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/FieldEditor.js +531 -0
- package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +10 -0
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +15 -0
- package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +7 -0
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +11 -0
- package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/JsonEditor.js +154 -0
- package/dist/src/analyze/select/tui/components/JsonPanel.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/JsonPanel.js +62 -0
- package/dist/src/analyze/select/tui/components/PreviewSummaryBar.d.ts +8 -0
- package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +29 -0
- package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +8 -0
- package/dist/src/analyze/select/tui/components/QuitDialog.js +14 -0
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +15 -0
- package/dist/src/analyze/select/tui/components/Sidebar.js +48 -0
- package/dist/src/analyze/select/tui/components/SourcePanel.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/SourcePanel.js +52 -0
- package/dist/src/analyze/select/tui/components/StatusBar.d.ts +11 -0
- package/dist/src/analyze/select/tui/components/StatusBar.js +6 -0
- package/dist/src/analyze/select/tui/components/TopBar.d.ts +10 -0
- package/dist/src/analyze/select/tui/components/TopBar.js +5 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +24 -0
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +68 -0
- package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +24 -0
- package/dist/src/analyze/select/tui/hooks/useKeymap.js +67 -0
- package/dist/src/analyze/select/tui/hooks/useSession.d.ts +19 -0
- package/dist/src/analyze/select/tui/hooks/useSession.js +52 -0
- package/dist/src/analyze/select/tui/hooks/useUndo.d.ts +8 -0
- package/dist/src/analyze/select/tui/hooks/useUndo.js +26 -0
- package/dist/src/analyze/select/types.d.ts +46 -0
- package/dist/src/analyze/select/types.js +20 -0
- package/dist/src/analyze/select-agent/command.d.ts +2 -0
- package/dist/src/analyze/select-agent/command.js +208 -0
- package/dist/src/analyze/tui/AnalyzeView.d.ts +24 -0
- package/dist/src/analyze/tui/AnalyzeView.js +38 -0
- package/dist/src/apply/api-client.d.ts +35 -0
- package/dist/src/apply/api-client.js +143 -0
- package/dist/src/apply/command.d.ts +6 -0
- package/dist/src/apply/command.js +787 -0
- package/dist/src/apply/manifest.d.ts +1 -0
- package/dist/src/apply/manifest.js +1 -0
- package/dist/src/apply/tui/SelectView.d.ts +18 -0
- package/dist/src/apply/tui/SelectView.js +34 -0
- package/dist/src/apply/tui/ServerApplyView.d.ts +32 -0
- package/dist/src/apply/tui/ServerApplyView.js +42 -0
- package/dist/src/apply/tui/ServerPreviewView.d.ts +9 -0
- package/dist/src/apply/tui/ServerPreviewView.js +21 -0
- package/dist/src/credentials-store.d.ts +8 -0
- package/dist/src/credentials-store.js +30 -0
- package/dist/src/generate/agent-runner.d.ts +86 -0
- package/dist/src/generate/agent-runner.js +314 -0
- package/dist/src/generate/command.d.ts +2 -0
- package/dist/src/generate/command.js +545 -0
- package/dist/src/generate/edit/command.d.ts +2 -0
- package/dist/src/generate/edit/command.js +126 -0
- package/dist/src/generate/prompt-builder.d.ts +18 -0
- package/dist/src/generate/prompt-builder.js +202 -0
- package/dist/src/generate/tui/GenerateView.d.ts +12 -0
- package/dist/src/generate/tui/GenerateView.js +10 -0
- package/dist/src/import/command.d.ts +2 -0
- package/dist/src/import/command.js +96 -0
- package/dist/src/import/orchestrator.d.ts +37 -0
- package/dist/src/import/orchestrator.js +374 -0
- package/dist/src/import/path-utils.d.ts +15 -0
- package/dist/src/import/path-utils.js +30 -0
- package/dist/src/import/tui/WizardApp.d.ts +10 -0
- package/dist/src/import/tui/WizardApp.js +906 -0
- package/dist/src/import/tui/steps/CredentialsStep.d.ts +15 -0
- package/dist/src/import/tui/steps/CredentialsStep.js +79 -0
- package/dist/src/import/tui/steps/DoneStep.d.ts +20 -0
- package/dist/src/import/tui/steps/DoneStep.js +17 -0
- package/dist/src/import/tui/steps/ErrorStep.d.ts +8 -0
- package/dist/src/import/tui/steps/ErrorStep.js +11 -0
- package/dist/src/import/tui/steps/GateStep.d.ts +14 -0
- package/dist/src/import/tui/steps/GateStep.js +20 -0
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +8 -0
- package/dist/src/import/tui/steps/GenerateReviewStep.js +208 -0
- package/dist/src/import/tui/steps/PathValidationStep.d.ts +10 -0
- package/dist/src/import/tui/steps/PathValidationStep.js +151 -0
- package/dist/src/import/tui/steps/PreviewStep.d.ts +21 -0
- package/dist/src/import/tui/steps/PreviewStep.js +36 -0
- package/dist/src/import/tui/steps/RunningStep.d.ts +10 -0
- package/dist/src/import/tui/steps/RunningStep.js +20 -0
- package/dist/src/import/tui/steps/TokenInputStep.d.ts +8 -0
- package/dist/src/import/tui/steps/TokenInputStep.js +70 -0
- package/dist/src/import/tui/steps/WelcomeStep.d.ts +7 -0
- package/dist/src/import/tui/steps/WelcomeStep.js +33 -0
- package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +15 -0
- package/dist/src/import/tui/steps/WizardPreviewStep.js +121 -0
- package/dist/src/import/tui/steps/preview-diff.d.ts +10 -0
- package/dist/src/import/tui/steps/preview-diff.js +132 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/output/format.d.ts +23 -0
- package/dist/src/output/format.js +110 -0
- package/dist/src/print/command.d.ts +2 -0
- package/dist/src/print/command.js +199 -0
- package/dist/src/print/validate/tui/ValidateView.d.ts +15 -0
- package/dist/src/print/validate/tui/ValidateView.js +37 -0
- package/dist/src/print/validate/validators/cdf-validator.d.ts +2 -0
- package/dist/src/print/validate/validators/cdf-validator.js +104 -0
- package/dist/src/print/validate/validators/dtcg-validator.d.ts +2 -0
- package/dist/src/print/validate/validators/dtcg-validator.js +110 -0
- package/dist/src/print/validate/validators/format-errors.d.ts +12 -0
- package/dist/src/print/validate/validators/format-errors.js +18 -0
- package/dist/src/program.d.ts +2 -0
- package/dist/src/program.js +25 -0
- package/dist/src/session/command.d.ts +2 -0
- package/dist/src/session/command.js +261 -0
- package/dist/src/session/db.d.ts +111 -0
- package/dist/src/session/db.js +1114 -0
- package/dist/src/session/migration.d.ts +4 -0
- package/dist/src/session/migration.js +117 -0
- package/dist/src/session/session-id.d.ts +1 -0
- package/dist/src/session/session-id.js +212 -0
- package/dist/src/session/stats.d.ts +27 -0
- package/dist/src/session/stats.js +89 -0
- package/dist/src/setup/command.d.ts +2 -0
- package/dist/src/setup/command.js +765 -0
- package/dist/src/types.d.ts +48 -0
- package/dist/src/types.js +1 -0
- package/package.json +55 -0
- package/skills/generate-components.md +361 -0
- package/skills/generate-tokens.md +194 -0
- package/skills/select-components.md +180 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReviewSessionPaths, ReviewSessionSnapshot } from '../../types.js';
|
|
2
|
+
type UseSessionInput = {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
artifactsRoot: string;
|
|
5
|
+
reviewRoot?: string;
|
|
6
|
+
};
|
|
7
|
+
type UseSessionResult = {
|
|
8
|
+
session: ReviewSessionSnapshot | null;
|
|
9
|
+
paths: ReviewSessionPaths | null;
|
|
10
|
+
loading: boolean;
|
|
11
|
+
error: string | null;
|
|
12
|
+
saveState: (updatedSession: ReviewSessionSnapshot) => Promise<void>;
|
|
13
|
+
appendEvent: (event: {
|
|
14
|
+
type: string;
|
|
15
|
+
payload: Record<string, unknown>;
|
|
16
|
+
}) => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
export declare function useSession({ sessionId, artifactsRoot, reviewRoot }: UseSessionInput): UseSessionResult;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { loadReviewInput } from '../../parser.js';
|
|
3
|
+
import { appendReviewEvent, ensureRefineSession, getRefineSessionPaths, saveReviewState } from '../../persistence.js';
|
|
4
|
+
import { openPipelineDb, loadRawComponents } from '../../../../session/db.js';
|
|
5
|
+
export function useSession({ sessionId, artifactsRoot, reviewRoot }) {
|
|
6
|
+
const [state, setState] = useState({ session: null, paths: null, loading: true, error: null });
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
async function load() {
|
|
9
|
+
const db = openPipelineDb();
|
|
10
|
+
let rawComponents;
|
|
11
|
+
try {
|
|
12
|
+
rawComponents = loadRawComponents(db, sessionId);
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
db.close();
|
|
16
|
+
}
|
|
17
|
+
const initialSnapshot = await loadReviewInput(rawComponents, { reviewRoot });
|
|
18
|
+
const paths = await getRefineSessionPaths(sessionId, artifactsRoot);
|
|
19
|
+
const session = await ensureRefineSession(sessionId, artifactsRoot, initialSnapshot);
|
|
20
|
+
const isResume = session.components.some((c) => c.status !== 'needs-review');
|
|
21
|
+
await appendReviewEvent(paths.eventsPath, {
|
|
22
|
+
type: isResume ? 'session_resumed' : 'session_started',
|
|
23
|
+
payload: { componentCount: session.components.length },
|
|
24
|
+
});
|
|
25
|
+
setState({ session, paths, loading: false, error: null });
|
|
26
|
+
}
|
|
27
|
+
load().catch((err) => setState({
|
|
28
|
+
session: null,
|
|
29
|
+
paths: null,
|
|
30
|
+
loading: false,
|
|
31
|
+
error: String(err),
|
|
32
|
+
}));
|
|
33
|
+
}, []);
|
|
34
|
+
const saveState = async (updatedSession) => {
|
|
35
|
+
if (!state.paths)
|
|
36
|
+
return;
|
|
37
|
+
await saveReviewState(state.paths.statePath, updatedSession);
|
|
38
|
+
};
|
|
39
|
+
const appendEvent = async (event) => {
|
|
40
|
+
if (!state.paths)
|
|
41
|
+
return;
|
|
42
|
+
await appendReviewEvent(state.paths.eventsPath, event);
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
session: state.session,
|
|
46
|
+
paths: state.paths,
|
|
47
|
+
loading: state.loading,
|
|
48
|
+
error: state.error,
|
|
49
|
+
saveState,
|
|
50
|
+
appendEvent,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
export function useUndo(initial, maxSize = 50) {
|
|
3
|
+
const [stack, setStack] = useState([]);
|
|
4
|
+
const [current, setCurrent] = useState(initial);
|
|
5
|
+
return {
|
|
6
|
+
current,
|
|
7
|
+
push: (next) => {
|
|
8
|
+
setStack((prev) => {
|
|
9
|
+
const newStack = [...prev, current];
|
|
10
|
+
return newStack.length > maxSize ? newStack.slice(1) : newStack;
|
|
11
|
+
});
|
|
12
|
+
setCurrent(next);
|
|
13
|
+
},
|
|
14
|
+
undo: () => {
|
|
15
|
+
setStack((prev) => {
|
|
16
|
+
if (prev.length === 0)
|
|
17
|
+
return prev;
|
|
18
|
+
const newStack = prev.slice(0, -1);
|
|
19
|
+
setCurrent(prev[prev.length - 1]);
|
|
20
|
+
return newStack;
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
canUndo: stack.length > 0,
|
|
24
|
+
clear: () => setStack([]),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { RawComponentDefinition } from '../../types.js';
|
|
2
|
+
export type PreviewAnnotation = 'new' | 'changed' | 'removed' | 'breaking';
|
|
3
|
+
export type ReviewComponentStatus = 'needs-review' | 'reviewed' | 'accepted' | 'rejected';
|
|
4
|
+
export type ReviewComponentRecord = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
resolvedSourcePath: string;
|
|
8
|
+
sourceCode: string | null;
|
|
9
|
+
originalProposal: RawComponentDefinition;
|
|
10
|
+
editedProposal: RawComponentDefinition;
|
|
11
|
+
status: ReviewComponentStatus;
|
|
12
|
+
};
|
|
13
|
+
export type ReviewComponentDetail = {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
originalProposal: RawComponentDefinition;
|
|
17
|
+
editedProposal: RawComponentDefinition;
|
|
18
|
+
status: ReviewComponentStatus;
|
|
19
|
+
};
|
|
20
|
+
export type ReviewComponentSummary = {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
status: ReviewComponentStatus;
|
|
24
|
+
previewAnnotation?: PreviewAnnotation;
|
|
25
|
+
};
|
|
26
|
+
export type ReviewSessionSnapshot = {
|
|
27
|
+
components: ReviewComponentRecord[];
|
|
28
|
+
};
|
|
29
|
+
export type ReviewSessionDetail = {
|
|
30
|
+
components: ReviewComponentDetail[];
|
|
31
|
+
};
|
|
32
|
+
export type ReviewSessionSummary = {
|
|
33
|
+
components: ReviewComponentSummary[];
|
|
34
|
+
};
|
|
35
|
+
export type ReviewEvent = {
|
|
36
|
+
type: string;
|
|
37
|
+
timestamp: string;
|
|
38
|
+
payload: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
export type ReviewSessionPaths = {
|
|
41
|
+
sessionDir: string;
|
|
42
|
+
eventsPath: string;
|
|
43
|
+
statePath: string;
|
|
44
|
+
};
|
|
45
|
+
export declare function createReviewSessionSummary(session: ReviewSessionSnapshot): ReviewSessionSummary;
|
|
46
|
+
export declare function createReviewSessionDetail(session: ReviewSessionSnapshot): ReviewSessionDetail;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function createReviewSessionSummary(session) {
|
|
2
|
+
return {
|
|
3
|
+
components: session.components.map((component) => ({
|
|
4
|
+
id: component.id,
|
|
5
|
+
name: component.name,
|
|
6
|
+
status: component.status,
|
|
7
|
+
})),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function createReviewSessionDetail(session) {
|
|
11
|
+
return {
|
|
12
|
+
components: session.components.map((component) => ({
|
|
13
|
+
id: component.id,
|
|
14
|
+
name: component.name,
|
|
15
|
+
originalProposal: component.originalProposal,
|
|
16
|
+
editedProposal: component.editedProposal,
|
|
17
|
+
status: component.status,
|
|
18
|
+
})),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { openPipelineDb, loadRawComponents, createStep, updateStep } from '../../session/db.js';
|
|
2
|
+
import { getRefineArtifactsRoot, ensureRefineSession, getRefineSessionPaths, saveReviewState, } from '../select/persistence.js';
|
|
3
|
+
import { loadReviewInput } from '../select/parser.js';
|
|
4
|
+
import { buildPrompt } from '../../generate/prompt-builder.js';
|
|
5
|
+
import { parseSelectToolCallLines, runAgent } from '../../generate/agent-runner.js';
|
|
6
|
+
import { OutputFormatter, c } from '../../output/format.js';
|
|
7
|
+
const VALID_AGENTS = new Set(['claude', 'codex', 'opencode', 'cursor']);
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 3 * 60 * 1000);
|
|
9
|
+
const DEFAULT_CONCURRENCY = 5;
|
|
10
|
+
function resolveSessionId(sessionFlag) {
|
|
11
|
+
if (sessionFlag)
|
|
12
|
+
return sessionFlag;
|
|
13
|
+
const db = openPipelineDb();
|
|
14
|
+
try {
|
|
15
|
+
const row = db
|
|
16
|
+
.prepare(`SELECT s.id FROM sessions s
|
|
17
|
+
JOIN steps st ON st.session_id = s.id
|
|
18
|
+
WHERE st.command = 'analyze extract'
|
|
19
|
+
AND st.status = 'complete'
|
|
20
|
+
ORDER BY st.started_at DESC
|
|
21
|
+
LIMIT 1`)
|
|
22
|
+
.get();
|
|
23
|
+
if (!row) {
|
|
24
|
+
process.stderr.write('Error: no completed analyze extract session found. Run analyze extract first, or pass --session <id>.\n');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
return row.id;
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
db.close();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function buildComponentData(component) {
|
|
34
|
+
return {
|
|
35
|
+
name: component.name,
|
|
36
|
+
source: component.source,
|
|
37
|
+
framework: component.framework,
|
|
38
|
+
propCount: component.props.length,
|
|
39
|
+
slotCount: component.slots.length,
|
|
40
|
+
propNames: component.props.slice(0, 8).map((p) => p.name),
|
|
41
|
+
props: component.props,
|
|
42
|
+
slots: component.slots,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function selectOneComponent(agent, model, component, index, total, verbose) {
|
|
46
|
+
const prompt = await buildPrompt({
|
|
47
|
+
skill: 'select',
|
|
48
|
+
mode: 'autonomous',
|
|
49
|
+
rawComponentsInline: JSON.stringify([buildComponentData(component)], null, 2),
|
|
50
|
+
outDir: process.cwd(),
|
|
51
|
+
});
|
|
52
|
+
const pos = c.dim(`[${index + 1}/${total}]`);
|
|
53
|
+
let outputBuf = '';
|
|
54
|
+
const formatter = new OutputFormatter(verbose, (s) => {
|
|
55
|
+
outputBuf += s;
|
|
56
|
+
});
|
|
57
|
+
const result = await runAgent({
|
|
58
|
+
agent,
|
|
59
|
+
model,
|
|
60
|
+
prompt,
|
|
61
|
+
interactive: false,
|
|
62
|
+
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
63
|
+
onOutput: (chunk) => formatter.push(chunk),
|
|
64
|
+
});
|
|
65
|
+
formatter.flush();
|
|
66
|
+
process.stderr.write(` ${pos} ${c.bold(component.name)}\n${outputBuf}`);
|
|
67
|
+
if (result.timedOut) {
|
|
68
|
+
return { componentName: component.name, decision: null, failed: true, error: 'timed out' };
|
|
69
|
+
}
|
|
70
|
+
if (result.exitCode !== 0) {
|
|
71
|
+
return {
|
|
72
|
+
componentName: component.name,
|
|
73
|
+
decision: null,
|
|
74
|
+
failed: true,
|
|
75
|
+
error: `agent exited with code ${result.exitCode}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const { calls, warnings } = parseSelectToolCallLines(result.stdout);
|
|
79
|
+
if (warnings.length > 0) {
|
|
80
|
+
for (const w of warnings)
|
|
81
|
+
process.stderr.write(` ${c.yellow('⚠')} ${component.name}: ${w}\n`);
|
|
82
|
+
}
|
|
83
|
+
const call = calls.find((call) => call.name === component.name) ?? calls[0];
|
|
84
|
+
if (!call) {
|
|
85
|
+
return {
|
|
86
|
+
componentName: component.name,
|
|
87
|
+
decision: null,
|
|
88
|
+
failed: true,
|
|
89
|
+
error: 'agent produced no tool call for this component',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const decision = call.tool === 'select_component' ? 'accepted' : 'rejected';
|
|
93
|
+
return { componentName: component.name, decision, reason: call.reason, failed: false };
|
|
94
|
+
}
|
|
95
|
+
async function selectAllComponents(agent, model, components, verbose) {
|
|
96
|
+
const concurrency = Number(process.env.EDS_GENERATE_CONCURRENCY ?? DEFAULT_CONCURRENCY);
|
|
97
|
+
process.stderr.write(`Validating ${c.bold(String(components.length))} component${components.length === 1 ? '' : 's'}` +
|
|
98
|
+
c.dim(` (concurrency: ${concurrency})`) +
|
|
99
|
+
'\n');
|
|
100
|
+
const results = new Array(components.length);
|
|
101
|
+
let next = 0;
|
|
102
|
+
async function worker() {
|
|
103
|
+
while (next < components.length) {
|
|
104
|
+
const i = next++;
|
|
105
|
+
results[i] = await selectOneComponent(agent, model, components[i], i, components.length, verbose);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, components.length) }, worker));
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
export function registerAnalyzeSelectAgentCommand(program) {
|
|
112
|
+
program
|
|
113
|
+
.command('select-agent')
|
|
114
|
+
.description('Use an AI agent to select components for Contentful Experience Orchestration')
|
|
115
|
+
.option('--session <id>', 'Session ID from analyze extract (defaults to most recent)')
|
|
116
|
+
.option('--project-root <path>', 'Project root for resolving component source files')
|
|
117
|
+
.requiredOption('--agent <name>', 'Agent to use: claude, codex, opencode, cursor')
|
|
118
|
+
.option('--model <name>', 'Model to use (defaults to a small/fast model per agent)')
|
|
119
|
+
.option('--verbose', 'Show full agent output including reasoning text')
|
|
120
|
+
.option('--dry-run', 'Print the prompt for the first component without invoking the agent')
|
|
121
|
+
.action(async (opts) => {
|
|
122
|
+
if (!VALID_AGENTS.has(opts.agent)) {
|
|
123
|
+
process.stderr.write(`Error: unknown agent '${opts.agent}'. Accepted values: claude, codex, opencode, cursor\n`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const agent = opts.agent;
|
|
128
|
+
const sessionId = resolveSessionId(opts.session);
|
|
129
|
+
const db = openPipelineDb();
|
|
130
|
+
let rawComponents;
|
|
131
|
+
try {
|
|
132
|
+
rawComponents = loadRawComponents(db, sessionId);
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
db.close();
|
|
136
|
+
}
|
|
137
|
+
if (rawComponents.length === 0) {
|
|
138
|
+
process.stderr.write(`Error: session '${sessionId}' has no raw components. Run analyze extract first.\n`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (opts.dryRun) {
|
|
143
|
+
const first = rawComponents[0];
|
|
144
|
+
const prompt = await buildPrompt({
|
|
145
|
+
skill: 'select',
|
|
146
|
+
mode: 'autonomous',
|
|
147
|
+
rawComponentsInline: JSON.stringify([buildComponentData(first)], null, 2),
|
|
148
|
+
outDir: process.cwd(),
|
|
149
|
+
});
|
|
150
|
+
process.stdout.write(prompt + '\n');
|
|
151
|
+
process.exit(0);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const selectResults = await selectAllComponents(agent, opts.model, rawComponents, opts.verbose ?? false);
|
|
155
|
+
// Build decision map from results
|
|
156
|
+
const decisions = new Map();
|
|
157
|
+
for (const r of selectResults) {
|
|
158
|
+
if (!r.failed && r.decision) {
|
|
159
|
+
decisions.set(r.componentName, r.decision);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Load existing snapshot and apply decisions
|
|
163
|
+
const artifactsRoot = getRefineArtifactsRoot();
|
|
164
|
+
let snapshot;
|
|
165
|
+
try {
|
|
166
|
+
snapshot = await loadReviewInput(rawComponents, {
|
|
167
|
+
reviewRoot: opts.projectRoot,
|
|
168
|
+
});
|
|
169
|
+
snapshot = await ensureRefineSession(sessionId, artifactsRoot, snapshot);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
process.stderr.write(`Error: unable to initialize select session.\n${error instanceof Error ? error.message : String(error)}\n`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const paths = await getRefineSessionPaths(sessionId, artifactsRoot);
|
|
177
|
+
const updated = {
|
|
178
|
+
...snapshot,
|
|
179
|
+
components: snapshot.components.map((comp) => {
|
|
180
|
+
const decision = decisions.get(comp.name);
|
|
181
|
+
if (!decision)
|
|
182
|
+
return comp;
|
|
183
|
+
return { ...comp, status: decision };
|
|
184
|
+
}),
|
|
185
|
+
};
|
|
186
|
+
await saveReviewState(paths.statePath, updated);
|
|
187
|
+
const accepted = updated.components.filter((comp) => comp.status === 'accepted');
|
|
188
|
+
const rejected = updated.components.filter((comp) => comp.status === 'rejected');
|
|
189
|
+
const failed = selectResults.filter((r) => r.failed);
|
|
190
|
+
if (failed.length > 0) {
|
|
191
|
+
process.stderr.write(c.red(`Failed (${failed.length}/${selectResults.length}):`) + '\n');
|
|
192
|
+
for (const f of failed) {
|
|
193
|
+
process.stderr.write(` ${c.red('✗')} ${f.componentName} ${c.dim(f.error ?? 'unknown error')}\n`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Write step to DB
|
|
197
|
+
const stepDb = openPipelineDb();
|
|
198
|
+
const stepId = createStep(stepDb, sessionId, 'analyze select', { sessionId });
|
|
199
|
+
try {
|
|
200
|
+
const status = failed.length === selectResults.length ? 'failed' : 'complete';
|
|
201
|
+
updateStep(stepDb, stepId, status, { sessionId });
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
stepDb.close();
|
|
205
|
+
}
|
|
206
|
+
process.stderr.write(`Accepted: ${accepted.length} Rejected: ${rejected.length}\n`);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type AnalyzeViewResult = {
|
|
3
|
+
sourceDirectory: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
fileCount: number;
|
|
6
|
+
components: Array<{
|
|
7
|
+
name: string;
|
|
8
|
+
framework: 'react' | 'next' | 'vue' | 'astro' | 'web-component' | 'stencil';
|
|
9
|
+
propCount: number;
|
|
10
|
+
slotCount: number;
|
|
11
|
+
warnings: string[];
|
|
12
|
+
}>;
|
|
13
|
+
totalWarnings: number;
|
|
14
|
+
zeroPropComponents?: Array<{
|
|
15
|
+
name: string;
|
|
16
|
+
source: string;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
type AnalyzeViewProps = {
|
|
20
|
+
result: AnalyzeViewResult;
|
|
21
|
+
onExit: () => void;
|
|
22
|
+
};
|
|
23
|
+
export declare function AnalyzeView({ result, onExit }: AnalyzeViewProps): React.ReactElement;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { TopBar } from '../select/tui/components/TopBar.js';
|
|
5
|
+
import { useImmediateInput } from '../select/tui/hooks/useImmediateInput.js';
|
|
6
|
+
function truncateName(name, maxLen = 30) {
|
|
7
|
+
if (name.length <= maxLen)
|
|
8
|
+
return name;
|
|
9
|
+
return name.slice(0, maxLen - 1) + '…';
|
|
10
|
+
}
|
|
11
|
+
export function AnalyzeView({ result, onExit }) {
|
|
12
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
13
|
+
useImmediateInput((input, key) => {
|
|
14
|
+
if (input === 'q' || key.return) {
|
|
15
|
+
onExit();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (key.upArrow || input === 'k') {
|
|
19
|
+
setScrollOffset((o) => Math.max(0, o - 1));
|
|
20
|
+
}
|
|
21
|
+
else if (key.downArrow || input === 'j') {
|
|
22
|
+
setScrollOffset((o) => o + 1);
|
|
23
|
+
}
|
|
24
|
+
else if (input === 'g') {
|
|
25
|
+
setScrollOffset(0);
|
|
26
|
+
}
|
|
27
|
+
else if (input === 'G') {
|
|
28
|
+
setScrollOffset(Math.max(0, result.components.length + 10));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TopBar, { subcommand: "analyze", hints: [
|
|
32
|
+
{ key: '?', label: 'help' },
|
|
33
|
+
{ key: 'q', label: 'quit' },
|
|
34
|
+
] }), _jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Text, { children: 'Scanned ' + result.fileCount + ' source files in ' + result.sourceDirectory }), _jsx(Text, { children: 'Extracted ' + result.components.length + ' components' }), _jsx(Text, { dimColor: true, children: 'Session: ' + result.sessionId }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { bold: true, children: "Components" }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { children: " " }), result.components.slice(scrollOffset).map((component) => (_jsxs(Box, { children: [component.warnings.length > 0 && _jsx(Text, { color: "yellow", children: "\u26A0 " }), component.warnings.length === 0 && _jsx(Text, { children: " " }), _jsx(Text, { children: truncateName(component.name).padEnd(20) }), _jsx(Text, { dimColor: true, children: component.framework.padEnd(10) }), _jsx(Text, { children: (component.propCount + ' props').padEnd(10) }), _jsx(Text, { children: component.slotCount + ' ' + (component.slotCount === 1 ? 'slot' : 'slots') }), component.warnings.length > 0 && (_jsx(Text, { color: "yellow", children: ' ' + component.warnings.length + ' warning' + (component.warnings.length === 1 ? '' : 's') }))] }, component.name))), result.zeroPropComponents && result.zeroPropComponents.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { bold: true, color: "yellow", children: 'Zero-prop components (' + result.zeroPropComponents.length + ')' }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { color: "yellow", children: ' These may be Storybook stories, context providers, or SSR utilities.' }), _jsx(Text, { color: "yellow", children: ' Review them in `analyze select` before generating.' }), _jsx(Text, { children: " " }), result.zeroPropComponents.map((c) => (_jsx(Text, { color: "yellow", children: ' ' + c.name + ' (' + c.source + ')' }, c.name)))] })), result.totalWarnings > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { bold: true, color: "yellow", children: 'Warnings (' + result.totalWarnings + ')' }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { children: " " }), result.components
|
|
35
|
+
.filter((c) => c.warnings.length > 0)
|
|
36
|
+
.flatMap((c) => c.warnings.map((w) => ({ component: c.name, warning: w })))
|
|
37
|
+
.map((w, i) => (_jsx(Text, { color: "yellow", children: ' ⚠ ' + w.component + ': ' + w.warning }, i)))] })), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: 'Run: analyze select --session ' + result.sessionId }), _jsx(Text, { children: " " })] }), _jsx(Box, { borderStyle: "single", paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter or q to exit" }) })] }));
|
|
38
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ManifestPayload, ServerPreviewResponse, ApplyOperationResponse } from '@contentful/experience-design-system-types';
|
|
2
|
+
export declare const DEFAULT_HOST = "https://api.contentful.com";
|
|
3
|
+
export interface ApiClientOptions {
|
|
4
|
+
host?: string;
|
|
5
|
+
cmaToken: string;
|
|
6
|
+
spaceId: string;
|
|
7
|
+
environmentId: string;
|
|
8
|
+
organizationId?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class ApiError extends Error {
|
|
11
|
+
readonly status: number;
|
|
12
|
+
readonly body: string;
|
|
13
|
+
constructor(message: string, status: number, body: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class ImportApiClient {
|
|
16
|
+
private host;
|
|
17
|
+
private token;
|
|
18
|
+
private spaceId;
|
|
19
|
+
private environmentId;
|
|
20
|
+
private organizationId;
|
|
21
|
+
constructor(opts: ApiClientOptions);
|
|
22
|
+
setOrganizationId(orgId: string | null): void;
|
|
23
|
+
private base;
|
|
24
|
+
private headers;
|
|
25
|
+
resolveOrganizationId(): Promise<string | null>;
|
|
26
|
+
validateEnvironment(): Promise<void>;
|
|
27
|
+
previewImport(manifest: ManifestPayload): Promise<ServerPreviewResponse>;
|
|
28
|
+
applyImport(manifest: ManifestPayload, acknowledgeBreakingChanges: boolean): Promise<ApplyOperationResponse>;
|
|
29
|
+
pollOperation(operationId: string, opts?: {
|
|
30
|
+
intervalMs?: number;
|
|
31
|
+
maxIntervalMs?: number;
|
|
32
|
+
maxAttempts?: number;
|
|
33
|
+
onProgress?: (op: ApplyOperationResponse) => void;
|
|
34
|
+
}): Promise<ApplyOperationResponse>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export const DEFAULT_HOST = 'https://api.contentful.com';
|
|
2
|
+
export class ApiError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
constructor(message, status, body) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.body = body;
|
|
9
|
+
if (body) {
|
|
10
|
+
// Append a trimmed version of the response body so callers that only
|
|
11
|
+
// log e.message don't silently swallow the server's error detail.
|
|
12
|
+
const trimmed = body.length > 1000 ? body.slice(0, 1000) + '…' : body;
|
|
13
|
+
this.message = `${message}\n${trimmed}`;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function request(url, options) {
|
|
18
|
+
const headers = {
|
|
19
|
+
Authorization: `Bearer ${options.token}`,
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
};
|
|
22
|
+
if (options.orgId)
|
|
23
|
+
headers['x-contentful-organization-id'] = options.orgId;
|
|
24
|
+
const init = {
|
|
25
|
+
method: options.method ?? 'GET',
|
|
26
|
+
headers,
|
|
27
|
+
};
|
|
28
|
+
if (options.body)
|
|
29
|
+
init.body = options.body;
|
|
30
|
+
return fetch(url, init);
|
|
31
|
+
}
|
|
32
|
+
export class ImportApiClient {
|
|
33
|
+
host;
|
|
34
|
+
token;
|
|
35
|
+
spaceId;
|
|
36
|
+
environmentId;
|
|
37
|
+
organizationId;
|
|
38
|
+
constructor(opts) {
|
|
39
|
+
this.host = opts.host ?? DEFAULT_HOST;
|
|
40
|
+
this.token = opts.cmaToken;
|
|
41
|
+
this.spaceId = opts.spaceId;
|
|
42
|
+
this.environmentId = opts.environmentId;
|
|
43
|
+
this.organizationId = opts.organizationId;
|
|
44
|
+
}
|
|
45
|
+
setOrganizationId(orgId) {
|
|
46
|
+
if (orgId)
|
|
47
|
+
this.organizationId = orgId;
|
|
48
|
+
}
|
|
49
|
+
base() {
|
|
50
|
+
return `${this.host}/spaces/${this.spaceId}/environments/${this.environmentId}`;
|
|
51
|
+
}
|
|
52
|
+
headers() {
|
|
53
|
+
const h = {
|
|
54
|
+
Authorization: `Bearer ${this.token}`,
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
};
|
|
57
|
+
if (this.organizationId)
|
|
58
|
+
h['x-contentful-organization-id'] = this.organizationId;
|
|
59
|
+
return h;
|
|
60
|
+
}
|
|
61
|
+
async resolveOrganizationId() {
|
|
62
|
+
const url = `${this.host}/spaces/${this.spaceId}`;
|
|
63
|
+
const res = await request(url, { token: this.token });
|
|
64
|
+
if (res.status === 401 || res.status === 403) {
|
|
65
|
+
throw new ApiError(`space '${this.spaceId}' not found or CMA token lacks access`, res.status, await res.text());
|
|
66
|
+
}
|
|
67
|
+
if (res.status === 404) {
|
|
68
|
+
// Custom host (e.g. staging) may not expose the spaces API or the space may not
|
|
69
|
+
// exist there. Skip the org ID — the design systems API may not require it.
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
throw new ApiError(`unexpected error fetching space: ${res.status}`, res.status, await res.text());
|
|
74
|
+
}
|
|
75
|
+
const json = (await res.json());
|
|
76
|
+
return json.sys.organization.sys.id;
|
|
77
|
+
}
|
|
78
|
+
async validateEnvironment() {
|
|
79
|
+
const url = `${this.host}/spaces/${this.spaceId}/environments/${this.environmentId}`;
|
|
80
|
+
const res = await request(url, { token: this.token });
|
|
81
|
+
if (res.status === 404) {
|
|
82
|
+
throw new ApiError(`environment '${this.environmentId}' not found in space '${this.spaceId}'`, 404, await res.text());
|
|
83
|
+
}
|
|
84
|
+
if (res.status === 401 || res.status === 403) {
|
|
85
|
+
throw new ApiError(`space '${this.spaceId}' not found or CMA token lacks access`, res.status, await res.text());
|
|
86
|
+
}
|
|
87
|
+
if (!res.ok) {
|
|
88
|
+
throw new ApiError(`unexpected error validating environment: ${res.status}`, res.status, await res.text());
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async previewImport(manifest) {
|
|
92
|
+
const url = `${this.base()}/design_systems/imports/preview`;
|
|
93
|
+
const res = await fetch(url, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: this.headers(),
|
|
96
|
+
body: JSON.stringify(manifest),
|
|
97
|
+
});
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
throw new ApiError(`preview failed: ${res.status}`, res.status, await res.text());
|
|
100
|
+
}
|
|
101
|
+
return (await res.json());
|
|
102
|
+
}
|
|
103
|
+
async applyImport(manifest, acknowledgeBreakingChanges) {
|
|
104
|
+
const url = `${this.base()}/design_systems/imports/apply`;
|
|
105
|
+
const res = await fetch(url, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: this.headers(),
|
|
108
|
+
body: JSON.stringify({ ...manifest, acknowledgeBreakingChanges }),
|
|
109
|
+
});
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
throw new ApiError(`apply failed: ${res.status}`, res.status, await res.text());
|
|
112
|
+
}
|
|
113
|
+
return (await res.json());
|
|
114
|
+
}
|
|
115
|
+
async pollOperation(operationId, opts = {}) {
|
|
116
|
+
const intervalMs = opts.intervalMs ?? 2000;
|
|
117
|
+
const maxIntervalMs = opts.maxIntervalMs ?? Math.round(intervalMs * 2.5);
|
|
118
|
+
const maxAttempts = opts.maxAttempts ?? 150;
|
|
119
|
+
const terminalStatuses = new Set(['succeeded', 'partial', 'failed']);
|
|
120
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
121
|
+
const url = `${this.base()}/design_systems/imports/apply/${encodeURIComponent(operationId)}`;
|
|
122
|
+
const res = await fetch(url, {
|
|
123
|
+
method: 'GET',
|
|
124
|
+
headers: this.headers(),
|
|
125
|
+
});
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
throw new ApiError(`poll failed: ${res.status}`, res.status, await res.text());
|
|
128
|
+
}
|
|
129
|
+
const op = (await res.json());
|
|
130
|
+
opts.onProgress?.(op);
|
|
131
|
+
if (terminalStatuses.has(op.sys.status)) {
|
|
132
|
+
return op;
|
|
133
|
+
}
|
|
134
|
+
if (attempt < maxAttempts - 1) {
|
|
135
|
+
const progress = maxAttempts > 1 ? attempt / (maxAttempts - 1) : 0;
|
|
136
|
+
const baseDelay = intervalMs + (maxIntervalMs - intervalMs) * progress;
|
|
137
|
+
const jitter = Math.random() * baseDelay * 0.15;
|
|
138
|
+
await new Promise((resolve) => setTimeout(resolve, baseDelay + jitter));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
throw new Error(`Operation ${operationId} timed out after ${maxAttempts} attempts`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { DTCGTokenEntry } from '@contentful/experience-design-system-types';
|
|
3
|
+
import type { ServerPreviewResponse } from '@contentful/experience-design-system-types';
|
|
4
|
+
export declare function readTokensFromPath(flag: string, p: string): Promise<DTCGTokenEntry[]>;
|
|
5
|
+
export declare function hasBreakingChangesWithImpact(preview: ServerPreviewResponse): boolean;
|
|
6
|
+
export declare function registerApplyCommand(program: Command): void;
|