@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,845 +0,0 @@
|
|
|
1
|
-
import { createElement, useState } from 'react';
|
|
2
|
-
import { render, useInput } from 'ink';
|
|
3
|
-
import { access, readFile, readdir, stat } from 'node:fs/promises';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { validateCDF, flattenDTCG, validateDTCG, buildManifest, buildFilteredManifest, } from '@contentful/experience-design-system-types';
|
|
6
|
-
import { ApiError, ImportApiClient } from './api-client.js';
|
|
7
|
-
import { formatApiError, formatEdsiError } from '../lib/error-parser.js';
|
|
8
|
-
import { openPipelineDb, loadCDFComponents } from '../session/db.js';
|
|
9
|
-
import { findSlotCycles, suggestCycleBreakEdge, formatCyclePath } from '../analyze/cycle-detection.js';
|
|
10
|
-
import { isEmptyPreview } from './preview-utils.js';
|
|
11
|
-
import { ServerPreviewApp, ServerPreviewConfirm, ServerApplyProgress, ServerApplyDone } from './tui/ServerApplyView.js';
|
|
12
|
-
import { SelectView, makeSelectKey } from './tui/SelectView.js';
|
|
13
|
-
import { buildPostPushUrl } from '../lib/contentful-urls.js';
|
|
14
|
-
import { resolveCompositionMode } from '../lib/composition-mode.js';
|
|
15
|
-
import { addAllowDeletionsOption, addArtifactInputOptions, addCompositionOptions, addContentfulTargetOptions, addSelectionOptions, } from '../lib/command-options.js';
|
|
16
|
-
import { stripAllowedComponents } from '../import/strip-allowed-components.js';
|
|
17
|
-
import { readExperiencesCredentials } from '../credentials-store.js';
|
|
18
|
-
import { getInteractiveTerminalSupport, requireInteractiveTerminal } from '../lib/terminal-capabilities.js';
|
|
19
|
-
import { bindAnalyticsSessionId, exitWithAnalytics, failureFromApiError, recordApplyOutcome, recordContentfulContext, } from '../analytics/index.js';
|
|
20
|
-
async function die(message, fields = {}) {
|
|
21
|
-
process.stderr.write(`${message}\n`);
|
|
22
|
-
return exitWithAnalytics(1, fields);
|
|
23
|
-
}
|
|
24
|
-
async function pathExists(p) {
|
|
25
|
-
return access(p)
|
|
26
|
-
.then(() => true)
|
|
27
|
-
.catch(() => false);
|
|
28
|
-
}
|
|
29
|
-
async function assertFileExists(flag, p) {
|
|
30
|
-
if (!(await pathExists(p)))
|
|
31
|
-
return await die(`Error: file not found: ${p} (from ${flag})`);
|
|
32
|
-
}
|
|
33
|
-
async function readJsonFile(flag, p) {
|
|
34
|
-
let text;
|
|
35
|
-
try {
|
|
36
|
-
text = await readFile(p, 'utf8');
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return await die(`Error: file not found: ${p} (from ${flag})`);
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
return JSON.parse(text);
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return await die(`Error: ${flag} is not valid JSON: ${p}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const IGNORE_TOKEN_DIRS = new Set(['node_modules', 'dist', 'build', '.next', '.nuxt', '.git']);
|
|
49
|
-
async function collectJsonFiles(dir) {
|
|
50
|
-
const results = [];
|
|
51
|
-
async function walk(current) {
|
|
52
|
-
let entries;
|
|
53
|
-
try {
|
|
54
|
-
entries = await readdir(current);
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
await Promise.all(entries.map(async (entry) => {
|
|
60
|
-
if (IGNORE_TOKEN_DIRS.has(entry))
|
|
61
|
-
return;
|
|
62
|
-
const full = join(current, entry);
|
|
63
|
-
let s;
|
|
64
|
-
try {
|
|
65
|
-
s = await stat(full);
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (s.isDirectory()) {
|
|
71
|
-
await walk(full);
|
|
72
|
-
}
|
|
73
|
-
else if (entry.endsWith('.json')) {
|
|
74
|
-
results.push(full);
|
|
75
|
-
}
|
|
76
|
-
}));
|
|
77
|
-
}
|
|
78
|
-
await walk(dir);
|
|
79
|
-
return results;
|
|
80
|
-
}
|
|
81
|
-
export async function readTokensFromPath(flag, p) {
|
|
82
|
-
let s;
|
|
83
|
-
try {
|
|
84
|
-
s = await stat(p);
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
return await die(`Error: file not found: ${p} (from ${flag})`);
|
|
88
|
-
}
|
|
89
|
-
if (s.isDirectory()) {
|
|
90
|
-
const files = await collectJsonFiles(p);
|
|
91
|
-
if (files.length === 0)
|
|
92
|
-
return await die(`Error: no .json files found in directory: ${p} (from ${flag})`);
|
|
93
|
-
const merged = {};
|
|
94
|
-
for (const file of files.sort()) {
|
|
95
|
-
let text;
|
|
96
|
-
try {
|
|
97
|
-
text = await readFile(file, 'utf8');
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
let parsed;
|
|
103
|
-
try {
|
|
104
|
-
parsed = JSON.parse(text);
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
110
|
-
Object.assign(merged, parsed);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const { valid, errors } = validateDTCG(merged);
|
|
114
|
-
if (!valid)
|
|
115
|
-
return await die(`Error: ${flag} contains invalid token types:\n${errors.map((e) => ` ${e.path}: ${e.message}`).join('\n')}`);
|
|
116
|
-
return flattenDTCG(merged, '');
|
|
117
|
-
}
|
|
118
|
-
const raw = await readJsonFile(flag, p);
|
|
119
|
-
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
120
|
-
return await die(`Error: ${flag} is not valid JSON: expected an object`);
|
|
121
|
-
}
|
|
122
|
-
const { valid, errors } = validateDTCG(raw);
|
|
123
|
-
if (!valid)
|
|
124
|
-
return await die(`Error: ${flag} contains invalid token types:\n${errors.map((e) => ` ${e.path}: ${e.message}`).join('\n')}`);
|
|
125
|
-
return flattenDTCG(raw, '');
|
|
126
|
-
}
|
|
127
|
-
async function resolveSharedInputs(opts) {
|
|
128
|
-
if (!opts.components && !opts.tokens && !opts.session) {
|
|
129
|
-
return await die('Error: at least one of --components, --tokens, or --session is required');
|
|
130
|
-
}
|
|
131
|
-
if (opts.session && opts.components) {
|
|
132
|
-
return await die('Error: --session and --components are mutually exclusive');
|
|
133
|
-
}
|
|
134
|
-
const spaceId = opts.spaceId ?? process.env.CONTENTFUL_SPACE_ID;
|
|
135
|
-
const environmentId = opts.environmentId ?? process.env.CONTENTFUL_ENVIRONMENT_ID;
|
|
136
|
-
if (!spaceId)
|
|
137
|
-
return await die('Error: --space-id is required (or set CONTENTFUL_SPACE_ID)');
|
|
138
|
-
if (!environmentId)
|
|
139
|
-
return await die('Error: --environment-id is required (or set CONTENTFUL_ENVIRONMENT_ID)');
|
|
140
|
-
opts.spaceId = spaceId;
|
|
141
|
-
opts.environmentId = environmentId;
|
|
142
|
-
const cmaToken = opts.cmaToken ?? process.env.CONTENTFUL_MANAGEMENT_TOKEN;
|
|
143
|
-
if (!cmaToken) {
|
|
144
|
-
return await die('Error: CMA token is required. Pass --cma-token or set CONTENTFUL_MANAGEMENT_TOKEN');
|
|
145
|
-
}
|
|
146
|
-
if (opts.components)
|
|
147
|
-
await assertFileExists('--components', opts.components);
|
|
148
|
-
let components = [];
|
|
149
|
-
if (opts.session) {
|
|
150
|
-
const db = openPipelineDb();
|
|
151
|
-
try {
|
|
152
|
-
components = loadCDFComponents(db, opts.session);
|
|
153
|
-
}
|
|
154
|
-
finally {
|
|
155
|
-
db.close();
|
|
156
|
-
}
|
|
157
|
-
if (components.length === 0) {
|
|
158
|
-
return await die(`Error: session '${opts.session}' has no generated components. Run generate components first.`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
else if (opts.components) {
|
|
162
|
-
const raw = await readJsonFile('--components', opts.components);
|
|
163
|
-
const result = validateCDF(raw);
|
|
164
|
-
if (!result.valid) {
|
|
165
|
-
return await die(`Error: --components failed schema validation: ${result.errors.map((e) => e.message).join(', ')}`);
|
|
166
|
-
}
|
|
167
|
-
components = result.components;
|
|
168
|
-
}
|
|
169
|
-
// Atomic mode (spec T8/T12): strip embedded-component composition at the
|
|
170
|
-
// single serialization boundary, regardless of load path. Normalizing here
|
|
171
|
-
// (rather than only in loadCDFComponents) also covers hand-authored
|
|
172
|
-
// `--components` files. Starving `$allowedComponents` at this one point
|
|
173
|
-
// means slot-cycle detection downstream structurally returns zero.
|
|
174
|
-
let configMode;
|
|
175
|
-
try {
|
|
176
|
-
configMode = (await readExperiencesCredentials()).compositionMode;
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
// Missing credentials.json → resolver falls through to default (atomic).
|
|
180
|
-
}
|
|
181
|
-
if (resolveCompositionMode(opts, configMode) === 'atomic') {
|
|
182
|
-
components = stripAllowedComponents(components);
|
|
183
|
-
}
|
|
184
|
-
let tokens = [];
|
|
185
|
-
if (opts.tokens) {
|
|
186
|
-
tokens = await readTokensFromPath('--tokens', opts.tokens);
|
|
187
|
-
}
|
|
188
|
-
const client = new ImportApiClient({
|
|
189
|
-
host: opts.host,
|
|
190
|
-
cmaToken,
|
|
191
|
-
spaceId,
|
|
192
|
-
environmentId,
|
|
193
|
-
});
|
|
194
|
-
return { components, tokens, client };
|
|
195
|
-
}
|
|
196
|
-
export function detectSlotCycles(components) {
|
|
197
|
-
const cycleInput = components.map(({ key, entry }) => ({
|
|
198
|
-
name: key,
|
|
199
|
-
slots: Object.entries(entry.$slots ?? {}).map(([slotName, slotDef]) => ({
|
|
200
|
-
name: slotName,
|
|
201
|
-
allowedComponents: slotDef.$allowedComponents ?? [],
|
|
202
|
-
})),
|
|
203
|
-
}));
|
|
204
|
-
return findSlotCycles(cycleInput);
|
|
205
|
-
}
|
|
206
|
-
export function formatSlotCycleReport(cycles) {
|
|
207
|
-
const lines = [];
|
|
208
|
-
lines.push(`Error: manifest:components/slot-cycles — ${cycles.length} slot dependency cycle(s) detected. Push refused.`);
|
|
209
|
-
for (let i = 0; i < cycles.length; i += 1) {
|
|
210
|
-
const cycle = cycles[i];
|
|
211
|
-
lines.push(` Cycle ${i + 1}: ${formatCyclePath(cycle)}`);
|
|
212
|
-
const suggested = suggestCycleBreakEdge(cycle, cycles);
|
|
213
|
-
lines.push(` Fix: remove '${suggested.toComponent}' from ${suggested.fromComponent}.$slots.${suggested.slotName}.$allowedComponents`);
|
|
214
|
-
}
|
|
215
|
-
return lines;
|
|
216
|
-
}
|
|
217
|
-
export async function assertNoSlotCycles(components) {
|
|
218
|
-
const cycles = detectSlotCycles(components);
|
|
219
|
-
if (cycles.length === 0)
|
|
220
|
-
return;
|
|
221
|
-
process.stderr.write(formatSlotCycleReport(cycles).join('\n') + '\n');
|
|
222
|
-
await exitWithAnalytics(1);
|
|
223
|
-
}
|
|
224
|
-
export function extractComponentsFromManifest(manifest) {
|
|
225
|
-
const componentsManifest = manifest?.componentsManifest;
|
|
226
|
-
if (!componentsManifest)
|
|
227
|
-
return [];
|
|
228
|
-
const out = [];
|
|
229
|
-
for (const [key, value] of Object.entries(componentsManifest)) {
|
|
230
|
-
if (key === '$schema')
|
|
231
|
-
continue;
|
|
232
|
-
if (!value || typeof value !== 'object')
|
|
233
|
-
continue;
|
|
234
|
-
out.push({ key, entry: value });
|
|
235
|
-
}
|
|
236
|
-
return out;
|
|
237
|
-
}
|
|
238
|
-
export function hasBreakingChangesWithImpact(preview) {
|
|
239
|
-
const allChanged = [...preview.components.changed, ...preview.tokens.changed];
|
|
240
|
-
return allChanged.some((c) => c.changeClassification?.classification === 'breaking' &&
|
|
241
|
-
c.impact &&
|
|
242
|
-
(c.impact.affectedFragments > 0 || c.impact.affectedExperiences > 0));
|
|
243
|
-
}
|
|
244
|
-
function buildPreviewOutput(preview, spaceId, environmentId) {
|
|
245
|
-
return {
|
|
246
|
-
spaceId,
|
|
247
|
-
environmentId,
|
|
248
|
-
components: {
|
|
249
|
-
new: preview.components.new.length,
|
|
250
|
-
changed: preview.components.changed.length,
|
|
251
|
-
unchanged: preview.components.unchanged.length,
|
|
252
|
-
removed: preview.components.removed.length,
|
|
253
|
-
breaking: preview.components.changed.filter((c) => c.changeClassification?.classification === 'breaking').length,
|
|
254
|
-
draftOverwrites: preview.components.changed.filter((c) => c.hasPendingDraftChanges).length,
|
|
255
|
-
},
|
|
256
|
-
tokens: {
|
|
257
|
-
new: preview.tokens.new.length,
|
|
258
|
-
changed: preview.tokens.changed.length,
|
|
259
|
-
unchanged: preview.tokens.unchanged.length,
|
|
260
|
-
removed: preview.tokens.removed.length,
|
|
261
|
-
draftOverwrites: preview.tokens.changed.filter((c) => c.hasPendingDraftChanges).length,
|
|
262
|
-
},
|
|
263
|
-
taxonomies: {
|
|
264
|
-
new: preview.taxonomies.new.length,
|
|
265
|
-
changed: preview.taxonomies.changed.length,
|
|
266
|
-
unchanged: preview.taxonomies.unchanged.length,
|
|
267
|
-
removed: preview.taxonomies.removed.length,
|
|
268
|
-
},
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
function buildApplyOutput(operation, spaceId, environmentId, host) {
|
|
272
|
-
const items = operation.items ?? [];
|
|
273
|
-
const componentItems = items.filter((i) => i.entityType === 'ComponentType');
|
|
274
|
-
const tokenItems = items.filter((i) => i.entityType === 'DesignToken');
|
|
275
|
-
function countByAction(subset) {
|
|
276
|
-
return {
|
|
277
|
-
created: subset.filter((i) => i.action === 'create' && i.status === 'succeeded').length,
|
|
278
|
-
updated: subset.filter((i) => i.action === 'update' && i.status === 'succeeded').length,
|
|
279
|
-
failed: subset.filter((i) => i.status === 'failed').length,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
return {
|
|
283
|
-
status: operation.sys.status,
|
|
284
|
-
operationId: operation.sys.id,
|
|
285
|
-
spaceId,
|
|
286
|
-
environmentId,
|
|
287
|
-
summary: operation.summary,
|
|
288
|
-
componentTypes: countByAction(componentItems),
|
|
289
|
-
designTokens: countByAction(tokenItems),
|
|
290
|
-
viewUrl: buildPostPushUrl({ host: host ?? 'api.contentful.com', spaceId, environmentId }),
|
|
291
|
-
tokensUrl: buildPostPushUrl({ host: host ?? 'api.contentful.com', spaceId, environmentId, view: 'design_tokens' }),
|
|
292
|
-
failures: items
|
|
293
|
-
.filter((item) => item.status === 'failed')
|
|
294
|
-
.map((item) => ({
|
|
295
|
-
entityType: item.entityType,
|
|
296
|
-
entityId: item.id,
|
|
297
|
-
error: formatEdsiError(item.error),
|
|
298
|
-
})),
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
function getSelectableEntities(preview) {
|
|
302
|
-
const entities = [];
|
|
303
|
-
for (const token of preview.tokens.new) {
|
|
304
|
-
entities.push({
|
|
305
|
-
id: token.path ?? token.id ?? '',
|
|
306
|
-
kind: 'token',
|
|
307
|
-
status: 'new',
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
for (const item of preview.tokens.changed) {
|
|
311
|
-
entities.push({ id: item.current.id, kind: 'token', status: 'changed' });
|
|
312
|
-
}
|
|
313
|
-
for (const comp of preview.components.new) {
|
|
314
|
-
entities.push({
|
|
315
|
-
id: comp.key ?? comp.id ?? '',
|
|
316
|
-
kind: 'component',
|
|
317
|
-
status: 'new',
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
for (const item of preview.components.changed) {
|
|
321
|
-
entities.push({
|
|
322
|
-
id: item.current.id,
|
|
323
|
-
kind: 'component',
|
|
324
|
-
status: 'changed',
|
|
325
|
-
isBreaking: item.changeClassification?.classification === 'breaking',
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
return entities;
|
|
329
|
-
}
|
|
330
|
-
function resolveNonInteractiveSelection(entities, opts) {
|
|
331
|
-
const allKeys = new Set(entities.map((e) => makeSelectKey(e.kind, e.id)));
|
|
332
|
-
if (opts.selectAll) {
|
|
333
|
-
if ((opts.select ?? []).length > 0 || (opts.deselect ?? []).length > 0) {
|
|
334
|
-
process.stderr.write('Warning: --select-all overrides --select and --deselect\n');
|
|
335
|
-
}
|
|
336
|
-
return allKeys;
|
|
337
|
-
}
|
|
338
|
-
const hasSelectPatterns = (opts.select ?? []).length > 0;
|
|
339
|
-
const selected = new Set();
|
|
340
|
-
if (!hasSelectPatterns) {
|
|
341
|
-
for (const key of allKeys)
|
|
342
|
-
selected.add(key);
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
for (const pattern of opts.select ?? []) {
|
|
346
|
-
for (const key of allKeys) {
|
|
347
|
-
if (key.includes(pattern))
|
|
348
|
-
selected.add(key);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
for (const pattern of opts.deselect ?? []) {
|
|
353
|
-
for (const key of [...selected]) {
|
|
354
|
-
if (key.includes(pattern))
|
|
355
|
-
selected.delete(key);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return selected;
|
|
359
|
-
}
|
|
360
|
-
function SelectApp({ entities, spaceId, environmentId, onApply }) {
|
|
361
|
-
const allKeys = new Set(entities.map((e) => makeSelectKey(e.kind, e.id)));
|
|
362
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
363
|
-
const [selected, setSelected] = useState(() => new Set(allKeys));
|
|
364
|
-
const [importing, setImporting] = useState(false);
|
|
365
|
-
useInput((input, key) => {
|
|
366
|
-
if (importing)
|
|
367
|
-
return;
|
|
368
|
-
if (key.upArrow) {
|
|
369
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
if (key.downArrow) {
|
|
373
|
-
setSelectedIndex((prev) => Math.min(entities.length - 1, prev + 1));
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
if (input === ' ') {
|
|
377
|
-
const entity = entities[selectedIndex];
|
|
378
|
-
if (!entity)
|
|
379
|
-
return;
|
|
380
|
-
const k = makeSelectKey(entity.kind, entity.id);
|
|
381
|
-
setSelected((prev) => {
|
|
382
|
-
const next = new Set(prev);
|
|
383
|
-
if (next.has(k))
|
|
384
|
-
next.delete(k);
|
|
385
|
-
else
|
|
386
|
-
next.add(k);
|
|
387
|
-
return next;
|
|
388
|
-
});
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
if (input === 'a' || input === 'A') {
|
|
392
|
-
setSelected(new Set(allKeys));
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
if (input === 'n' || input === 'N') {
|
|
396
|
-
setSelected(new Set());
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
if ((input === 'i' || input === 'I') && selected.size > 0) {
|
|
400
|
-
setImporting(true);
|
|
401
|
-
onApply(selected);
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
if (input === 'q' || input === 'Q') {
|
|
405
|
-
process.exit(0);
|
|
406
|
-
}
|
|
407
|
-
});
|
|
408
|
-
return createElement(SelectView, {
|
|
409
|
-
entities,
|
|
410
|
-
spaceId,
|
|
411
|
-
environmentId,
|
|
412
|
-
selectedIndex,
|
|
413
|
-
selected,
|
|
414
|
-
importing,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
export function registerApplyCommand(program) {
|
|
418
|
-
const applyCmd = program
|
|
419
|
-
.command('apply')
|
|
420
|
-
.description('Preview, select, or push design system entities to Contentful ExO');
|
|
421
|
-
const previewCmd = applyCmd.command('preview').description('Show a read-only diff of what apply push would do');
|
|
422
|
-
addArtifactInputOptions(previewCmd);
|
|
423
|
-
addContentfulTargetOptions(previewCmd);
|
|
424
|
-
addCompositionOptions(previewCmd);
|
|
425
|
-
previewCmd.action(async (opts) => {
|
|
426
|
-
let inputs;
|
|
427
|
-
try {
|
|
428
|
-
inputs = await resolveSharedInputs(opts);
|
|
429
|
-
}
|
|
430
|
-
catch (e) {
|
|
431
|
-
if (e instanceof ApiError)
|
|
432
|
-
return await die(`Error: ${formatApiError(e)}`, failureFromApiError(e));
|
|
433
|
-
throw e;
|
|
434
|
-
}
|
|
435
|
-
const { components, tokens, client } = inputs;
|
|
436
|
-
const spaceId = opts.spaceId;
|
|
437
|
-
const environmentId = opts.environmentId;
|
|
438
|
-
await bindAnalyticsSessionId(opts.session, {
|
|
439
|
-
space_key: spaceId,
|
|
440
|
-
environment_key: environmentId,
|
|
441
|
-
});
|
|
442
|
-
try {
|
|
443
|
-
await client.validateToken();
|
|
444
|
-
}
|
|
445
|
-
catch (e) {
|
|
446
|
-
if (e instanceof ApiError)
|
|
447
|
-
return await die(`Error: ${formatApiError(e)}`, failureFromApiError(e));
|
|
448
|
-
const cause = e instanceof Error && e.cause instanceof Error ? e.cause.message : '';
|
|
449
|
-
return await die(`Error: unable to connect to API host${cause ? `: ${cause}` : ''}`);
|
|
450
|
-
}
|
|
451
|
-
const manifest = buildManifest(components, tokens);
|
|
452
|
-
let preview;
|
|
453
|
-
try {
|
|
454
|
-
preview = await client.previewImport(manifest);
|
|
455
|
-
}
|
|
456
|
-
catch (e) {
|
|
457
|
-
if (e instanceof ApiError)
|
|
458
|
-
return await die(`Error: ${formatApiError(e)}`, failureFromApiError(e));
|
|
459
|
-
throw e;
|
|
460
|
-
}
|
|
461
|
-
recordContentfulContext(client, spaceId, environmentId);
|
|
462
|
-
if (getInteractiveTerminalSupport().supported) {
|
|
463
|
-
const { waitUntilExit } = render(createElement(ServerPreviewApp, {
|
|
464
|
-
preview,
|
|
465
|
-
spaceId,
|
|
466
|
-
environmentId,
|
|
467
|
-
allowDeletions: false,
|
|
468
|
-
}));
|
|
469
|
-
await waitUntilExit();
|
|
470
|
-
}
|
|
471
|
-
else {
|
|
472
|
-
process.stdout.write(JSON.stringify(buildPreviewOutput(preview, spaceId, environmentId), null, 2) + '\n');
|
|
473
|
-
await exitWithAnalytics(0);
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
const pushCmd = applyCmd.command('push').description('Write component types and design tokens to Contentful ExO');
|
|
477
|
-
addArtifactInputOptions(pushCmd);
|
|
478
|
-
addContentfulTargetOptions(pushCmd);
|
|
479
|
-
addCompositionOptions(pushCmd);
|
|
480
|
-
addAllowDeletionsOption(pushCmd);
|
|
481
|
-
pushCmd
|
|
482
|
-
.option('--yes', 'Skip interactive confirmation')
|
|
483
|
-
.option('--verbose', 'Show all entity progress including skipped/unchanged')
|
|
484
|
-
.option('--force', 'Skip confirmation for breaking changes (for CI)')
|
|
485
|
-
.option('--dry-run', 'Run preview only without applying')
|
|
486
|
-
.action(async (opts) => {
|
|
487
|
-
const isTTY = getInteractiveTerminalSupport().supported;
|
|
488
|
-
if (!isTTY && !opts.yes) {
|
|
489
|
-
process.stderr.write('Error: apply push requires --yes in non-interactive mode\n');
|
|
490
|
-
await exitWithAnalytics(1);
|
|
491
|
-
}
|
|
492
|
-
let inputs;
|
|
493
|
-
try {
|
|
494
|
-
inputs = await resolveSharedInputs(opts);
|
|
495
|
-
}
|
|
496
|
-
catch (e) {
|
|
497
|
-
if (e instanceof ApiError)
|
|
498
|
-
return await die(`Error: ${formatApiError(e, opts.verbose)}`, failureFromApiError(e));
|
|
499
|
-
throw e;
|
|
500
|
-
}
|
|
501
|
-
const { components, tokens, client } = inputs;
|
|
502
|
-
const spaceId = opts.spaceId;
|
|
503
|
-
const environmentId = opts.environmentId;
|
|
504
|
-
await bindAnalyticsSessionId(opts.session, {
|
|
505
|
-
space_key: spaceId,
|
|
506
|
-
environment_key: environmentId,
|
|
507
|
-
});
|
|
508
|
-
await assertNoSlotCycles(components);
|
|
509
|
-
try {
|
|
510
|
-
await client.validateToken();
|
|
511
|
-
}
|
|
512
|
-
catch (e) {
|
|
513
|
-
if (e instanceof ApiError)
|
|
514
|
-
return await die(`Error: ${formatApiError(e, opts.verbose)}`, failureFromApiError(e));
|
|
515
|
-
throw e;
|
|
516
|
-
}
|
|
517
|
-
const manifest = buildManifest(components, tokens);
|
|
518
|
-
let preview;
|
|
519
|
-
try {
|
|
520
|
-
preview = await client.previewImport(manifest, opts.allowDeletions === true);
|
|
521
|
-
}
|
|
522
|
-
catch (e) {
|
|
523
|
-
if (e instanceof ApiError)
|
|
524
|
-
return await die(`Error: ${formatApiError(e, opts.verbose)}`, failureFromApiError(e));
|
|
525
|
-
throw e;
|
|
526
|
-
}
|
|
527
|
-
recordContentfulContext(client, spaceId, environmentId);
|
|
528
|
-
if (opts.dryRun) {
|
|
529
|
-
if (isTTY) {
|
|
530
|
-
const { waitUntilExit } = render(createElement(ServerPreviewApp, {
|
|
531
|
-
preview,
|
|
532
|
-
spaceId,
|
|
533
|
-
environmentId,
|
|
534
|
-
allowDeletions: opts.allowDeletions === true,
|
|
535
|
-
}));
|
|
536
|
-
await waitUntilExit();
|
|
537
|
-
}
|
|
538
|
-
else {
|
|
539
|
-
process.stdout.write(JSON.stringify(buildPreviewOutput(preview, spaceId, environmentId), null, 2) + '\n');
|
|
540
|
-
}
|
|
541
|
-
await exitWithAnalytics(0);
|
|
542
|
-
}
|
|
543
|
-
if (isEmptyPreview(preview)) {
|
|
544
|
-
if (isTTY && !opts.yes) {
|
|
545
|
-
process.stderr.write('Nothing to change — design system is up to date.\n');
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
548
|
-
process.stdout.write(JSON.stringify(buildPreviewOutput(preview, spaceId, environmentId), null, 2) + '\n');
|
|
549
|
-
}
|
|
550
|
-
await exitWithAnalytics(0);
|
|
551
|
-
}
|
|
552
|
-
const breakingWithImpact = hasBreakingChangesWithImpact(preview);
|
|
553
|
-
if (!isTTY || opts.yes) {
|
|
554
|
-
if (breakingWithImpact && !opts.force) {
|
|
555
|
-
process.stderr.write('Error: breaking changes with downstream impact detected. Use --force to acknowledge.\n');
|
|
556
|
-
process.stdout.write(JSON.stringify(buildPreviewOutput(preview, spaceId, environmentId), null, 2) + '\n');
|
|
557
|
-
await exitWithAnalytics(1);
|
|
558
|
-
}
|
|
559
|
-
const verbose = opts.verbose ?? false;
|
|
560
|
-
if (verbose) {
|
|
561
|
-
process.stderr.write(JSON.stringify(buildPreviewOutput(preview, spaceId, environmentId), null, 2) + '\n');
|
|
562
|
-
}
|
|
563
|
-
let operation;
|
|
564
|
-
try {
|
|
565
|
-
operation = await client.applyImport(manifest, {
|
|
566
|
-
acknowledgeBreakingChanges: breakingWithImpact || opts.force === true,
|
|
567
|
-
allowDeletions: opts.allowDeletions === true,
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
catch (e) {
|
|
571
|
-
if (e instanceof ApiError)
|
|
572
|
-
return await die(`Error: ${formatApiError(e, opts.verbose)}`, failureFromApiError(e));
|
|
573
|
-
throw e;
|
|
574
|
-
}
|
|
575
|
-
process.stderr.write(`Apply operation started: ${operation.sys.id}\n`);
|
|
576
|
-
try {
|
|
577
|
-
operation = await client.pollOperation(operation.sys.id);
|
|
578
|
-
}
|
|
579
|
-
catch (e) {
|
|
580
|
-
if (e instanceof ApiError)
|
|
581
|
-
return await die(`Error: ${formatApiError(e, opts.verbose)}`, failureFromApiError(e));
|
|
582
|
-
throw e;
|
|
583
|
-
}
|
|
584
|
-
const summary = buildApplyOutput(operation, spaceId, environmentId, opts.host);
|
|
585
|
-
recordApplyOutcome(client, spaceId, environmentId, operation);
|
|
586
|
-
process.stdout.write(JSON.stringify(summary, null, 2) + '\n');
|
|
587
|
-
const exitCode = operation.sys.status === 'succeeded' ? 0 : 1;
|
|
588
|
-
await exitWithAnalytics(exitCode);
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
await new Promise((resolvePromise) => {
|
|
592
|
-
const runApply = async (acknowledge, applyDeletions) => {
|
|
593
|
-
instance.rerender(createElement(ServerApplyProgress, {
|
|
594
|
-
spaceId,
|
|
595
|
-
environmentId,
|
|
596
|
-
status: 'applying',
|
|
597
|
-
}));
|
|
598
|
-
let operation;
|
|
599
|
-
try {
|
|
600
|
-
operation = await client.applyImport(manifest, {
|
|
601
|
-
acknowledgeBreakingChanges: acknowledge,
|
|
602
|
-
allowDeletions: applyDeletions,
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
catch (e) {
|
|
606
|
-
if (e instanceof ApiError) {
|
|
607
|
-
instance.rerender(createElement(ServerApplyProgress, {
|
|
608
|
-
spaceId,
|
|
609
|
-
environmentId,
|
|
610
|
-
status: 'error',
|
|
611
|
-
error: formatApiError(e, opts.verbose),
|
|
612
|
-
}));
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
throw e;
|
|
616
|
-
}
|
|
617
|
-
instance.rerender(createElement(ServerApplyProgress, {
|
|
618
|
-
spaceId,
|
|
619
|
-
environmentId,
|
|
620
|
-
status: 'polling',
|
|
621
|
-
operationId: operation.sys.id,
|
|
622
|
-
}));
|
|
623
|
-
try {
|
|
624
|
-
operation = await client.pollOperation(operation.sys.id);
|
|
625
|
-
}
|
|
626
|
-
catch (e) {
|
|
627
|
-
if (e instanceof ApiError) {
|
|
628
|
-
instance.rerender(createElement(ServerApplyProgress, {
|
|
629
|
-
spaceId,
|
|
630
|
-
environmentId,
|
|
631
|
-
status: 'error',
|
|
632
|
-
error: formatApiError(e, opts.verbose),
|
|
633
|
-
}));
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
throw e;
|
|
637
|
-
}
|
|
638
|
-
recordApplyOutcome(client, spaceId, environmentId, operation);
|
|
639
|
-
instance.rerender(createElement(ServerApplyDone, {
|
|
640
|
-
operation,
|
|
641
|
-
spaceId,
|
|
642
|
-
environmentId,
|
|
643
|
-
host: opts.host,
|
|
644
|
-
}));
|
|
645
|
-
resolvePromise();
|
|
646
|
-
};
|
|
647
|
-
const instance = render(createElement(ServerPreviewConfirm, {
|
|
648
|
-
preview,
|
|
649
|
-
spaceId,
|
|
650
|
-
environmentId,
|
|
651
|
-
breakingWithImpact,
|
|
652
|
-
allowDeletions: opts.allowDeletions === true,
|
|
653
|
-
onConfirm: (acknowledge, applyDeletions) => {
|
|
654
|
-
void runApply(acknowledge, applyDeletions);
|
|
655
|
-
},
|
|
656
|
-
onCancel: () => {
|
|
657
|
-
void exitWithAnalytics(0);
|
|
658
|
-
},
|
|
659
|
-
}));
|
|
660
|
-
void instance.waitUntilExit().then(() => resolvePromise());
|
|
661
|
-
});
|
|
662
|
-
});
|
|
663
|
-
const selectCmd = applyCmd.command('select').description('Select a subset of entities and push to Contentful ExO');
|
|
664
|
-
addArtifactInputOptions(selectCmd);
|
|
665
|
-
addContentfulTargetOptions(selectCmd);
|
|
666
|
-
addCompositionOptions(selectCmd);
|
|
667
|
-
addSelectionOptions(selectCmd);
|
|
668
|
-
addAllowDeletionsOption(selectCmd);
|
|
669
|
-
selectCmd.option('--force', 'Skip confirmation for breaking changes').action(async (opts) => {
|
|
670
|
-
const nonInteractive = opts.selectAll || (opts.select ?? []).length > 0 || (opts.deselect ?? []).length > 0;
|
|
671
|
-
if (!nonInteractive) {
|
|
672
|
-
requireInteractiveTerminal({
|
|
673
|
-
alternative: 'pass `--select-all`, `--select`, or `--deselect`',
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
let inputs;
|
|
677
|
-
try {
|
|
678
|
-
inputs = await resolveSharedInputs(opts);
|
|
679
|
-
}
|
|
680
|
-
catch (e) {
|
|
681
|
-
if (e instanceof ApiError)
|
|
682
|
-
return await die(`Error: ${formatApiError(e)}`, failureFromApiError(e));
|
|
683
|
-
throw e;
|
|
684
|
-
}
|
|
685
|
-
const { components, tokens, client } = inputs;
|
|
686
|
-
await assertNoSlotCycles(components);
|
|
687
|
-
try {
|
|
688
|
-
await client.validateToken();
|
|
689
|
-
}
|
|
690
|
-
catch (e) {
|
|
691
|
-
if (e instanceof ApiError)
|
|
692
|
-
return await die(`Error: ${formatApiError(e)}`, failureFromApiError(e));
|
|
693
|
-
throw e;
|
|
694
|
-
}
|
|
695
|
-
const fullManifest = buildManifest(components, tokens);
|
|
696
|
-
let preview;
|
|
697
|
-
try {
|
|
698
|
-
preview = await client.previewImport(fullManifest, opts.allowDeletions === true);
|
|
699
|
-
}
|
|
700
|
-
catch (e) {
|
|
701
|
-
if (e instanceof ApiError)
|
|
702
|
-
return await die(`Error: ${formatApiError(e)}`, failureFromApiError(e));
|
|
703
|
-
throw e;
|
|
704
|
-
}
|
|
705
|
-
const spaceId = opts.spaceId;
|
|
706
|
-
const environmentId = opts.environmentId;
|
|
707
|
-
await bindAnalyticsSessionId(opts.session, {
|
|
708
|
-
space_key: spaceId,
|
|
709
|
-
environment_key: environmentId,
|
|
710
|
-
});
|
|
711
|
-
recordContentfulContext(client, spaceId, environmentId);
|
|
712
|
-
const entities = getSelectableEntities(preview);
|
|
713
|
-
if (entities.length === 0) {
|
|
714
|
-
process.stderr.write('Nothing to change — design system is up to date.\n');
|
|
715
|
-
await exitWithAnalytics(0);
|
|
716
|
-
}
|
|
717
|
-
if (nonInteractive) {
|
|
718
|
-
const selectedKeys = resolveNonInteractiveSelection(entities, opts);
|
|
719
|
-
if (selectedKeys.size === 0) {
|
|
720
|
-
process.stderr.write('No entities matched selection criteria.\n');
|
|
721
|
-
await exitWithAnalytics(0);
|
|
722
|
-
}
|
|
723
|
-
const selectedComponentKeys = new Set();
|
|
724
|
-
const selectedTokenPaths = new Set();
|
|
725
|
-
for (const key of selectedKeys) {
|
|
726
|
-
const [kind, ...idParts] = key.split(':');
|
|
727
|
-
const id = idParts.join(':');
|
|
728
|
-
if (kind === 'component')
|
|
729
|
-
selectedComponentKeys.add(id);
|
|
730
|
-
else if (kind === 'token')
|
|
731
|
-
selectedTokenPaths.add(id);
|
|
732
|
-
}
|
|
733
|
-
const filteredManifest = buildFilteredManifest(fullManifest, selectedComponentKeys, selectedTokenPaths);
|
|
734
|
-
const hasBreaking = entities.some((e) => e.isBreaking && selectedKeys.has(makeSelectKey(e.kind, e.id)));
|
|
735
|
-
if (hasBreaking && !opts.force) {
|
|
736
|
-
process.stderr.write('Error: selection includes breaking changes. Use --force to acknowledge.\n');
|
|
737
|
-
await exitWithAnalytics(1);
|
|
738
|
-
}
|
|
739
|
-
let operation;
|
|
740
|
-
try {
|
|
741
|
-
operation = await client.applyImport(filteredManifest, {
|
|
742
|
-
acknowledgeBreakingChanges: hasBreaking || opts.force === true,
|
|
743
|
-
allowDeletions: opts.allowDeletions === true,
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
catch (e) {
|
|
747
|
-
if (e instanceof ApiError)
|
|
748
|
-
return await die(`Error: ${formatApiError(e)}`, failureFromApiError(e));
|
|
749
|
-
throw e;
|
|
750
|
-
}
|
|
751
|
-
process.stderr.write(`Apply operation started: ${operation.sys.id}\n`);
|
|
752
|
-
try {
|
|
753
|
-
operation = await client.pollOperation(operation.sys.id);
|
|
754
|
-
}
|
|
755
|
-
catch (e) {
|
|
756
|
-
if (e instanceof ApiError)
|
|
757
|
-
return await die(`Error: ${formatApiError(e)}`, failureFromApiError(e));
|
|
758
|
-
throw e;
|
|
759
|
-
}
|
|
760
|
-
const summary = buildApplyOutput(operation, spaceId, environmentId, opts.host);
|
|
761
|
-
recordApplyOutcome(client, spaceId, environmentId, operation);
|
|
762
|
-
process.stdout.write(JSON.stringify(summary, null, 2) + '\n');
|
|
763
|
-
await exitWithAnalytics(operation.sys.status === 'succeeded' ? 0 : 1);
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
await new Promise((resolvePromise) => {
|
|
767
|
-
const runSelectApply = async (selectedKeys) => {
|
|
768
|
-
const selectedComponentKeys = new Set();
|
|
769
|
-
const selectedTokenPaths = new Set();
|
|
770
|
-
for (const key of selectedKeys) {
|
|
771
|
-
const [kind, ...idParts] = key.split(':');
|
|
772
|
-
const id = idParts.join(':');
|
|
773
|
-
if (kind === 'component')
|
|
774
|
-
selectedComponentKeys.add(id);
|
|
775
|
-
else if (kind === 'token')
|
|
776
|
-
selectedTokenPaths.add(id);
|
|
777
|
-
}
|
|
778
|
-
const filteredManifest = buildFilteredManifest(fullManifest, selectedComponentKeys, selectedTokenPaths);
|
|
779
|
-
const hasBreaking = entities.some((e) => e.isBreaking && selectedKeys.has(makeSelectKey(e.kind, e.id)));
|
|
780
|
-
instance.rerender(createElement(ServerApplyProgress, {
|
|
781
|
-
spaceId,
|
|
782
|
-
environmentId,
|
|
783
|
-
status: 'applying',
|
|
784
|
-
}));
|
|
785
|
-
let operation;
|
|
786
|
-
try {
|
|
787
|
-
operation = await client.applyImport(filteredManifest, {
|
|
788
|
-
acknowledgeBreakingChanges: hasBreaking,
|
|
789
|
-
allowDeletions: opts.allowDeletions === true,
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
catch (e) {
|
|
793
|
-
if (e instanceof ApiError) {
|
|
794
|
-
instance.rerender(createElement(ServerApplyProgress, {
|
|
795
|
-
spaceId,
|
|
796
|
-
environmentId,
|
|
797
|
-
status: 'error',
|
|
798
|
-
error: formatApiError(e),
|
|
799
|
-
}));
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
throw e;
|
|
803
|
-
}
|
|
804
|
-
instance.rerender(createElement(ServerApplyProgress, {
|
|
805
|
-
spaceId,
|
|
806
|
-
environmentId,
|
|
807
|
-
status: 'polling',
|
|
808
|
-
operationId: operation.sys.id,
|
|
809
|
-
}));
|
|
810
|
-
try {
|
|
811
|
-
operation = await client.pollOperation(operation.sys.id);
|
|
812
|
-
}
|
|
813
|
-
catch (e) {
|
|
814
|
-
if (e instanceof ApiError) {
|
|
815
|
-
instance.rerender(createElement(ServerApplyProgress, {
|
|
816
|
-
spaceId,
|
|
817
|
-
environmentId,
|
|
818
|
-
status: 'error',
|
|
819
|
-
error: formatApiError(e),
|
|
820
|
-
}));
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
throw e;
|
|
824
|
-
}
|
|
825
|
-
recordApplyOutcome(client, spaceId, environmentId, operation);
|
|
826
|
-
instance.rerender(createElement(ServerApplyDone, {
|
|
827
|
-
operation,
|
|
828
|
-
spaceId,
|
|
829
|
-
environmentId,
|
|
830
|
-
host: opts.host,
|
|
831
|
-
}));
|
|
832
|
-
resolvePromise();
|
|
833
|
-
};
|
|
834
|
-
const instance = render(createElement(SelectApp, {
|
|
835
|
-
entities,
|
|
836
|
-
spaceId,
|
|
837
|
-
environmentId,
|
|
838
|
-
onApply: (selectedKeys) => {
|
|
839
|
-
void runSelectApply(selectedKeys);
|
|
840
|
-
},
|
|
841
|
-
}));
|
|
842
|
-
void instance.waitUntilExit().then(() => resolvePromise());
|
|
843
|
-
});
|
|
844
|
-
});
|
|
845
|
-
}
|