@contentful/experience-design-system-cli 2.23.2-dev-build-753ceef.0 → 2.23.2-dev-build-e996ffb.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -24
- package/dist/package.json +1 -2
- package/dist/src/analyze/command.js +2 -8
- package/dist/src/analyze/select/command.js +9 -13
- package/dist/src/analyze/select/tui/App.js +4 -1
- package/dist/src/analyze/select-agent/command.js +11 -14
- package/dist/src/apply/api-client.d.ts +5 -5
- package/dist/src/apply/api-client.js +8 -18
- package/dist/src/apply/command.d.ts +1 -1
- package/dist/src/apply/command.js +77 -81
- package/dist/src/apply/preview-utils.d.ts +5 -1
- package/dist/src/apply/preview-utils.js +5 -1
- package/dist/src/apply/tui/ServerApplyView.d.ts +6 -3
- package/dist/src/apply/tui/ServerApplyView.js +15 -6
- package/dist/src/apply/tui/ServerPreviewView.d.ts +2 -1
- package/dist/src/apply/tui/ServerPreviewView.js +3 -3
- package/dist/src/generate/command.js +9 -15
- package/dist/src/generate/edit/command.js +0 -1
- package/dist/src/import/command.js +6 -1
- package/dist/src/import/orchestrator.d.ts +1 -0
- package/dist/src/import/orchestrator.js +14 -18
- package/dist/src/import/tui/WizardApp.d.ts +2 -1
- package/dist/src/import/tui/WizardApp.js +8 -8
- package/dist/src/import/tui/final-review-host.d.ts +2 -1
- package/dist/src/import/tui/final-review-host.js +2 -2
- package/dist/src/import/tui/runLivePreview.d.ts +3 -0
- package/dist/src/import/tui/runLivePreview.js +4 -1
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +2 -1
- package/dist/src/import/tui/steps/GenerateReviewStep.js +3 -3
- package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +6 -2
- package/dist/src/import/tui/steps/WizardPreviewStep.js +12 -3
- package/dist/src/import/tui/useFinalizePreview.d.ts +2 -0
- package/dist/src/import/tui/useFinalizePreview.js +0 -0
- package/dist/src/import/tui/useLivePreview.d.ts +2 -0
- package/dist/src/import/tui/useLivePreview.js +1 -0
- package/dist/src/index.js +1 -6
- package/dist/src/lib/command-options.d.ts +1 -0
- package/dist/src/lib/command-options.js +3 -0
- package/dist/src/print/command.js +13 -16
- package/dist/src/program.js +1 -8
- package/dist/src/runs/modify-launcher.d.ts +2 -0
- package/dist/src/runs/modify-launcher.js +2 -0
- package/dist/src/runs/push-helpers.d.ts +1 -0
- package/dist/src/runs/push-helpers.js +3 -0
- package/dist/src/runs/replay-helpers.d.ts +4 -0
- package/dist/src/runs/replay-helpers.js +3 -0
- package/package.json +5 -6
- package/dist/src/analytics/apply.d.ts +0 -6
- package/dist/src/analytics/apply.js +0 -30
- package/dist/src/analytics/client.d.ts +0 -7
- package/dist/src/analytics/client.js +0 -68
- package/dist/src/analytics/constants.d.ts +0 -4
- package/dist/src/analytics/constants.js +0 -4
- package/dist/src/analytics/env.d.ts +0 -4
- package/dist/src/analytics/env.js +0 -14
- package/dist/src/analytics/exit.d.ts +0 -5
- package/dist/src/analytics/exit.js +0 -24
- package/dist/src/analytics/index.d.ts +0 -10
- package/dist/src/analytics/index.js +0 -9
- package/dist/src/analytics/normalize.d.ts +0 -3
- package/dist/src/analytics/normalize.js +0 -18
- package/dist/src/analytics/os.d.ts +0 -2
- package/dist/src/analytics/os.js +0 -13
- package/dist/src/analytics/session.d.ts +0 -3
- package/dist/src/analytics/session.js +0 -15
- package/dist/src/analytics/tracker.d.ts +0 -17
- package/dist/src/analytics/tracker.js +0 -126
- package/dist/src/analytics/types.d.ts +0 -28
- package/dist/src/analytics/types.js +0 -1
|
@@ -12,14 +12,13 @@ import { ServerPreviewApp, ServerPreviewConfirm, ServerApplyProgress, ServerAppl
|
|
|
12
12
|
import { SelectView, makeSelectKey } from './tui/SelectView.js';
|
|
13
13
|
import { buildPostPushUrl } from '../lib/contentful-urls.js';
|
|
14
14
|
import { resolveCompositionMode } from '../lib/composition-mode.js';
|
|
15
|
-
import { addArtifactInputOptions, addCompositionOptions, addContentfulTargetOptions, addSelectionOptions, } from '../lib/command-options.js';
|
|
15
|
+
import { addAllowDeletionsOption, addArtifactInputOptions, addCompositionOptions, addContentfulTargetOptions, addSelectionOptions, } from '../lib/command-options.js';
|
|
16
16
|
import { stripAllowedComponents } from '../import/strip-allowed-components.js';
|
|
17
17
|
import { readExperiencesCredentials } from '../credentials-store.js';
|
|
18
18
|
import { getInteractiveTerminalSupport, requireInteractiveTerminal } from '../lib/terminal-capabilities.js';
|
|
19
|
-
|
|
20
|
-
async function die(message, fields = {}) {
|
|
19
|
+
function die(message) {
|
|
21
20
|
process.stderr.write(`${message}\n`);
|
|
22
|
-
|
|
21
|
+
process.exit(1);
|
|
23
22
|
}
|
|
24
23
|
async function pathExists(p) {
|
|
25
24
|
return access(p)
|
|
@@ -28,7 +27,7 @@ async function pathExists(p) {
|
|
|
28
27
|
}
|
|
29
28
|
async function assertFileExists(flag, p) {
|
|
30
29
|
if (!(await pathExists(p)))
|
|
31
|
-
|
|
30
|
+
die(`Error: file not found: ${p} (from ${flag})`);
|
|
32
31
|
}
|
|
33
32
|
async function readJsonFile(flag, p) {
|
|
34
33
|
let text;
|
|
@@ -36,13 +35,13 @@ async function readJsonFile(flag, p) {
|
|
|
36
35
|
text = await readFile(p, 'utf8');
|
|
37
36
|
}
|
|
38
37
|
catch {
|
|
39
|
-
|
|
38
|
+
die(`Error: file not found: ${p} (from ${flag})`);
|
|
40
39
|
}
|
|
41
40
|
try {
|
|
42
41
|
return JSON.parse(text);
|
|
43
42
|
}
|
|
44
43
|
catch {
|
|
45
|
-
|
|
44
|
+
die(`Error: ${flag} is not valid JSON: ${p}`);
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
const IGNORE_TOKEN_DIRS = new Set(['node_modules', 'dist', 'build', '.next', '.nuxt', '.git']);
|
|
@@ -84,12 +83,12 @@ export async function readTokensFromPath(flag, p) {
|
|
|
84
83
|
s = await stat(p);
|
|
85
84
|
}
|
|
86
85
|
catch {
|
|
87
|
-
|
|
86
|
+
die(`Error: file not found: ${p} (from ${flag})`);
|
|
88
87
|
}
|
|
89
88
|
if (s.isDirectory()) {
|
|
90
89
|
const files = await collectJsonFiles(p);
|
|
91
90
|
if (files.length === 0)
|
|
92
|
-
|
|
91
|
+
die(`Error: no .json files found in directory: ${p} (from ${flag})`);
|
|
93
92
|
const merged = {};
|
|
94
93
|
for (const file of files.sort()) {
|
|
95
94
|
let text;
|
|
@@ -112,36 +111,36 @@ export async function readTokensFromPath(flag, p) {
|
|
|
112
111
|
}
|
|
113
112
|
const { valid, errors } = validateDTCG(merged);
|
|
114
113
|
if (!valid)
|
|
115
|
-
|
|
114
|
+
die(`Error: ${flag} contains invalid token types:\n${errors.map((e) => ` ${e.path}: ${e.message}`).join('\n')}`);
|
|
116
115
|
return flattenDTCG(merged, '');
|
|
117
116
|
}
|
|
118
117
|
const raw = await readJsonFile(flag, p);
|
|
119
118
|
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
120
|
-
|
|
119
|
+
die(`Error: ${flag} is not valid JSON: expected an object`);
|
|
121
120
|
}
|
|
122
121
|
const { valid, errors } = validateDTCG(raw);
|
|
123
122
|
if (!valid)
|
|
124
|
-
|
|
123
|
+
die(`Error: ${flag} contains invalid token types:\n${errors.map((e) => ` ${e.path}: ${e.message}`).join('\n')}`);
|
|
125
124
|
return flattenDTCG(raw, '');
|
|
126
125
|
}
|
|
127
126
|
async function resolveSharedInputs(opts) {
|
|
128
127
|
if (!opts.components && !opts.tokens && !opts.session) {
|
|
129
|
-
|
|
128
|
+
die('Error: at least one of --components, --tokens, or --session is required');
|
|
130
129
|
}
|
|
131
130
|
if (opts.session && opts.components) {
|
|
132
|
-
|
|
131
|
+
die('Error: --session and --components are mutually exclusive');
|
|
133
132
|
}
|
|
134
133
|
const spaceId = opts.spaceId ?? process.env.CONTENTFUL_SPACE_ID;
|
|
135
134
|
const environmentId = opts.environmentId ?? process.env.CONTENTFUL_ENVIRONMENT_ID;
|
|
136
135
|
if (!spaceId)
|
|
137
|
-
|
|
136
|
+
die('Error: --space-id is required (or set CONTENTFUL_SPACE_ID)');
|
|
138
137
|
if (!environmentId)
|
|
139
|
-
|
|
138
|
+
die('Error: --environment-id is required (or set CONTENTFUL_ENVIRONMENT_ID)');
|
|
140
139
|
opts.spaceId = spaceId;
|
|
141
140
|
opts.environmentId = environmentId;
|
|
142
141
|
const cmaToken = opts.cmaToken ?? process.env.CONTENTFUL_MANAGEMENT_TOKEN;
|
|
143
142
|
if (!cmaToken) {
|
|
144
|
-
|
|
143
|
+
die('Error: CMA token is required. Pass --cma-token or set CONTENTFUL_MANAGEMENT_TOKEN');
|
|
145
144
|
}
|
|
146
145
|
if (opts.components)
|
|
147
146
|
await assertFileExists('--components', opts.components);
|
|
@@ -155,14 +154,14 @@ async function resolveSharedInputs(opts) {
|
|
|
155
154
|
db.close();
|
|
156
155
|
}
|
|
157
156
|
if (components.length === 0) {
|
|
158
|
-
|
|
157
|
+
die(`Error: session '${opts.session}' has no generated components. Run generate components first.`);
|
|
159
158
|
}
|
|
160
159
|
}
|
|
161
160
|
else if (opts.components) {
|
|
162
161
|
const raw = await readJsonFile('--components', opts.components);
|
|
163
162
|
const result = validateCDF(raw);
|
|
164
163
|
if (!result.valid) {
|
|
165
|
-
|
|
164
|
+
die(`Error: --components failed schema validation: ${result.errors.map((e) => e.message).join(', ')}`);
|
|
166
165
|
}
|
|
167
166
|
components = result.components;
|
|
168
167
|
}
|
|
@@ -214,12 +213,12 @@ export function formatSlotCycleReport(cycles) {
|
|
|
214
213
|
}
|
|
215
214
|
return lines;
|
|
216
215
|
}
|
|
217
|
-
export
|
|
216
|
+
export function assertNoSlotCycles(components) {
|
|
218
217
|
const cycles = detectSlotCycles(components);
|
|
219
218
|
if (cycles.length === 0)
|
|
220
219
|
return;
|
|
221
220
|
process.stderr.write(formatSlotCycleReport(cycles).join('\n') + '\n');
|
|
222
|
-
|
|
221
|
+
process.exit(1);
|
|
223
222
|
}
|
|
224
223
|
export function extractComponentsFromManifest(manifest) {
|
|
225
224
|
const componentsManifest = manifest?.componentsManifest;
|
|
@@ -428,24 +427,18 @@ export function registerApplyCommand(program) {
|
|
|
428
427
|
}
|
|
429
428
|
catch (e) {
|
|
430
429
|
if (e instanceof ApiError)
|
|
431
|
-
|
|
430
|
+
die(`Error: ${formatApiError(e)}`);
|
|
432
431
|
throw e;
|
|
433
432
|
}
|
|
434
433
|
const { components, tokens, client } = inputs;
|
|
435
|
-
const spaceId = opts.spaceId;
|
|
436
|
-
const environmentId = opts.environmentId;
|
|
437
|
-
await bindAnalyticsSessionId(opts.session, {
|
|
438
|
-
space_key: spaceId,
|
|
439
|
-
environment_key: environmentId,
|
|
440
|
-
});
|
|
441
434
|
try {
|
|
442
435
|
await client.validateToken();
|
|
443
436
|
}
|
|
444
437
|
catch (e) {
|
|
445
438
|
if (e instanceof ApiError)
|
|
446
|
-
|
|
439
|
+
die(`Error: ${formatApiError(e)}`);
|
|
447
440
|
const cause = e instanceof Error && e.cause instanceof Error ? e.cause.message : '';
|
|
448
|
-
|
|
441
|
+
die(`Error: unable to connect to API host${cause ? `: ${cause}` : ''}`);
|
|
449
442
|
}
|
|
450
443
|
const manifest = buildManifest(components, tokens);
|
|
451
444
|
let preview;
|
|
@@ -454,27 +447,30 @@ export function registerApplyCommand(program) {
|
|
|
454
447
|
}
|
|
455
448
|
catch (e) {
|
|
456
449
|
if (e instanceof ApiError)
|
|
457
|
-
|
|
450
|
+
die(`Error: ${formatApiError(e)}`);
|
|
458
451
|
throw e;
|
|
459
452
|
}
|
|
460
|
-
|
|
453
|
+
const spaceId = opts.spaceId;
|
|
454
|
+
const environmentId = opts.environmentId;
|
|
461
455
|
if (getInteractiveTerminalSupport().supported) {
|
|
462
456
|
const { waitUntilExit } = render(createElement(ServerPreviewApp, {
|
|
463
457
|
preview,
|
|
464
458
|
spaceId,
|
|
465
459
|
environmentId,
|
|
460
|
+
allowDeletions: false,
|
|
466
461
|
}));
|
|
467
462
|
await waitUntilExit();
|
|
468
463
|
}
|
|
469
464
|
else {
|
|
470
465
|
process.stdout.write(JSON.stringify(buildPreviewOutput(preview, spaceId, environmentId), null, 2) + '\n');
|
|
471
|
-
|
|
466
|
+
process.exit(0);
|
|
472
467
|
}
|
|
473
468
|
});
|
|
474
469
|
const pushCmd = applyCmd.command('push').description('Write component types and design tokens to Contentful ExO');
|
|
475
470
|
addArtifactInputOptions(pushCmd);
|
|
476
471
|
addContentfulTargetOptions(pushCmd);
|
|
477
472
|
addCompositionOptions(pushCmd);
|
|
473
|
+
addAllowDeletionsOption(pushCmd);
|
|
478
474
|
pushCmd
|
|
479
475
|
.option('--yes', 'Skip interactive confirmation')
|
|
480
476
|
.option('--verbose', 'Show all entity progress including skipped/unchanged')
|
|
@@ -484,7 +480,7 @@ export function registerApplyCommand(program) {
|
|
|
484
480
|
const isTTY = getInteractiveTerminalSupport().supported;
|
|
485
481
|
if (!isTTY && !opts.yes) {
|
|
486
482
|
process.stderr.write('Error: apply push requires --yes in non-interactive mode\n');
|
|
487
|
-
|
|
483
|
+
process.exit(1);
|
|
488
484
|
}
|
|
489
485
|
let inputs;
|
|
490
486
|
try {
|
|
@@ -492,49 +488,45 @@ export function registerApplyCommand(program) {
|
|
|
492
488
|
}
|
|
493
489
|
catch (e) {
|
|
494
490
|
if (e instanceof ApiError)
|
|
495
|
-
|
|
491
|
+
die(`Error: ${formatApiError(e, opts.verbose)}`);
|
|
496
492
|
throw e;
|
|
497
493
|
}
|
|
498
494
|
const { components, tokens, client } = inputs;
|
|
499
|
-
|
|
500
|
-
const environmentId = opts.environmentId;
|
|
501
|
-
await bindAnalyticsSessionId(opts.session, {
|
|
502
|
-
space_key: spaceId,
|
|
503
|
-
environment_key: environmentId,
|
|
504
|
-
});
|
|
505
|
-
await assertNoSlotCycles(components);
|
|
495
|
+
assertNoSlotCycles(components);
|
|
506
496
|
try {
|
|
507
497
|
await client.validateToken();
|
|
508
498
|
}
|
|
509
499
|
catch (e) {
|
|
510
500
|
if (e instanceof ApiError)
|
|
511
|
-
|
|
501
|
+
die(`Error: ${formatApiError(e, opts.verbose)}`);
|
|
512
502
|
throw e;
|
|
513
503
|
}
|
|
514
504
|
const manifest = buildManifest(components, tokens);
|
|
515
505
|
let preview;
|
|
516
506
|
try {
|
|
517
|
-
preview = await client.previewImport(manifest);
|
|
507
|
+
preview = await client.previewImport(manifest, opts.allowDeletions === true);
|
|
518
508
|
}
|
|
519
509
|
catch (e) {
|
|
520
510
|
if (e instanceof ApiError)
|
|
521
|
-
|
|
511
|
+
die(`Error: ${formatApiError(e, opts.verbose)}`);
|
|
522
512
|
throw e;
|
|
523
513
|
}
|
|
524
|
-
|
|
514
|
+
const spaceId = opts.spaceId;
|
|
515
|
+
const environmentId = opts.environmentId;
|
|
525
516
|
if (opts.dryRun) {
|
|
526
517
|
if (isTTY) {
|
|
527
518
|
const { waitUntilExit } = render(createElement(ServerPreviewApp, {
|
|
528
519
|
preview,
|
|
529
520
|
spaceId,
|
|
530
521
|
environmentId,
|
|
522
|
+
allowDeletions: opts.allowDeletions === true,
|
|
531
523
|
}));
|
|
532
524
|
await waitUntilExit();
|
|
533
525
|
}
|
|
534
526
|
else {
|
|
535
527
|
process.stdout.write(JSON.stringify(buildPreviewOutput(preview, spaceId, environmentId), null, 2) + '\n');
|
|
536
528
|
}
|
|
537
|
-
|
|
529
|
+
process.exit(0);
|
|
538
530
|
}
|
|
539
531
|
if (isEmptyPreview(preview)) {
|
|
540
532
|
if (isTTY && !opts.yes) {
|
|
@@ -543,14 +535,14 @@ export function registerApplyCommand(program) {
|
|
|
543
535
|
else {
|
|
544
536
|
process.stdout.write(JSON.stringify(buildPreviewOutput(preview, spaceId, environmentId), null, 2) + '\n');
|
|
545
537
|
}
|
|
546
|
-
|
|
538
|
+
process.exit(0);
|
|
547
539
|
}
|
|
548
540
|
const breakingWithImpact = hasBreakingChangesWithImpact(preview);
|
|
549
541
|
if (!isTTY || opts.yes) {
|
|
550
542
|
if (breakingWithImpact && !opts.force) {
|
|
551
543
|
process.stderr.write('Error: breaking changes with downstream impact detected. Use --force to acknowledge.\n');
|
|
552
544
|
process.stdout.write(JSON.stringify(buildPreviewOutput(preview, spaceId, environmentId), null, 2) + '\n');
|
|
553
|
-
|
|
545
|
+
process.exit(1);
|
|
554
546
|
}
|
|
555
547
|
const verbose = opts.verbose ?? false;
|
|
556
548
|
if (verbose) {
|
|
@@ -558,11 +550,14 @@ export function registerApplyCommand(program) {
|
|
|
558
550
|
}
|
|
559
551
|
let operation;
|
|
560
552
|
try {
|
|
561
|
-
operation = await client.applyImport(manifest,
|
|
553
|
+
operation = await client.applyImport(manifest, {
|
|
554
|
+
acknowledgeBreakingChanges: breakingWithImpact || opts.force === true,
|
|
555
|
+
allowDeletions: opts.allowDeletions === true,
|
|
556
|
+
});
|
|
562
557
|
}
|
|
563
558
|
catch (e) {
|
|
564
559
|
if (e instanceof ApiError)
|
|
565
|
-
|
|
560
|
+
die(`Error: ${formatApiError(e, opts.verbose)}`);
|
|
566
561
|
throw e;
|
|
567
562
|
}
|
|
568
563
|
process.stderr.write(`Apply operation started: ${operation.sys.id}\n`);
|
|
@@ -571,18 +566,16 @@ export function registerApplyCommand(program) {
|
|
|
571
566
|
}
|
|
572
567
|
catch (e) {
|
|
573
568
|
if (e instanceof ApiError)
|
|
574
|
-
|
|
569
|
+
die(`Error: ${formatApiError(e, opts.verbose)}`);
|
|
575
570
|
throw e;
|
|
576
571
|
}
|
|
577
572
|
const summary = buildApplyOutput(operation, spaceId, environmentId, opts.host);
|
|
578
|
-
recordApplyOutcome(client, spaceId, environmentId, operation);
|
|
579
573
|
process.stdout.write(JSON.stringify(summary, null, 2) + '\n');
|
|
580
|
-
|
|
581
|
-
await exitWithAnalytics(exitCode);
|
|
574
|
+
process.exit(operation.sys.status === 'succeeded' ? 0 : 1);
|
|
582
575
|
return;
|
|
583
576
|
}
|
|
584
577
|
await new Promise((resolvePromise) => {
|
|
585
|
-
const runApply = async (acknowledge) => {
|
|
578
|
+
const runApply = async (acknowledge, applyDeletions) => {
|
|
586
579
|
instance.rerender(createElement(ServerApplyProgress, {
|
|
587
580
|
spaceId,
|
|
588
581
|
environmentId,
|
|
@@ -590,7 +583,10 @@ export function registerApplyCommand(program) {
|
|
|
590
583
|
}));
|
|
591
584
|
let operation;
|
|
592
585
|
try {
|
|
593
|
-
operation = await client.applyImport(manifest,
|
|
586
|
+
operation = await client.applyImport(manifest, {
|
|
587
|
+
acknowledgeBreakingChanges: acknowledge,
|
|
588
|
+
allowDeletions: applyDeletions,
|
|
589
|
+
});
|
|
594
590
|
}
|
|
595
591
|
catch (e) {
|
|
596
592
|
if (e instanceof ApiError) {
|
|
@@ -625,7 +621,6 @@ export function registerApplyCommand(program) {
|
|
|
625
621
|
}
|
|
626
622
|
throw e;
|
|
627
623
|
}
|
|
628
|
-
recordApplyOutcome(client, spaceId, environmentId, operation);
|
|
629
624
|
instance.rerender(createElement(ServerApplyDone, {
|
|
630
625
|
operation,
|
|
631
626
|
spaceId,
|
|
@@ -639,11 +634,12 @@ export function registerApplyCommand(program) {
|
|
|
639
634
|
spaceId,
|
|
640
635
|
environmentId,
|
|
641
636
|
breakingWithImpact,
|
|
642
|
-
|
|
643
|
-
|
|
637
|
+
allowDeletions: opts.allowDeletions === true,
|
|
638
|
+
onConfirm: (acknowledge, applyDeletions) => {
|
|
639
|
+
void runApply(acknowledge, applyDeletions);
|
|
644
640
|
},
|
|
645
641
|
onCancel: () => {
|
|
646
|
-
|
|
642
|
+
process.exit(0);
|
|
647
643
|
},
|
|
648
644
|
}));
|
|
649
645
|
void instance.waitUntilExit().then(() => resolvePromise());
|
|
@@ -654,6 +650,7 @@ export function registerApplyCommand(program) {
|
|
|
654
650
|
addContentfulTargetOptions(selectCmd);
|
|
655
651
|
addCompositionOptions(selectCmd);
|
|
656
652
|
addSelectionOptions(selectCmd);
|
|
653
|
+
addAllowDeletionsOption(selectCmd);
|
|
657
654
|
selectCmd.option('--force', 'Skip confirmation for breaking changes').action(async (opts) => {
|
|
658
655
|
const nonInteractive = opts.selectAll || (opts.select ?? []).length > 0 || (opts.deselect ?? []).length > 0;
|
|
659
656
|
if (!nonInteractive) {
|
|
@@ -667,46 +664,41 @@ export function registerApplyCommand(program) {
|
|
|
667
664
|
}
|
|
668
665
|
catch (e) {
|
|
669
666
|
if (e instanceof ApiError)
|
|
670
|
-
|
|
667
|
+
die(`Error: ${formatApiError(e)}`);
|
|
671
668
|
throw e;
|
|
672
669
|
}
|
|
673
670
|
const { components, tokens, client } = inputs;
|
|
674
|
-
|
|
671
|
+
assertNoSlotCycles(components);
|
|
675
672
|
try {
|
|
676
673
|
await client.validateToken();
|
|
677
674
|
}
|
|
678
675
|
catch (e) {
|
|
679
676
|
if (e instanceof ApiError)
|
|
680
|
-
|
|
677
|
+
die(`Error: ${formatApiError(e)}`);
|
|
681
678
|
throw e;
|
|
682
679
|
}
|
|
683
680
|
const fullManifest = buildManifest(components, tokens);
|
|
684
681
|
let preview;
|
|
685
682
|
try {
|
|
686
|
-
preview = await client.previewImport(fullManifest);
|
|
683
|
+
preview = await client.previewImport(fullManifest, opts.allowDeletions === true);
|
|
687
684
|
}
|
|
688
685
|
catch (e) {
|
|
689
686
|
if (e instanceof ApiError)
|
|
690
|
-
|
|
687
|
+
die(`Error: ${formatApiError(e)}`);
|
|
691
688
|
throw e;
|
|
692
689
|
}
|
|
693
690
|
const spaceId = opts.spaceId;
|
|
694
691
|
const environmentId = opts.environmentId;
|
|
695
|
-
await bindAnalyticsSessionId(opts.session, {
|
|
696
|
-
space_key: spaceId,
|
|
697
|
-
environment_key: environmentId,
|
|
698
|
-
});
|
|
699
|
-
recordContentfulContext(client, spaceId, environmentId);
|
|
700
692
|
const entities = getSelectableEntities(preview);
|
|
701
693
|
if (entities.length === 0) {
|
|
702
694
|
process.stderr.write('Nothing to change — design system is up to date.\n');
|
|
703
|
-
|
|
695
|
+
process.exit(0);
|
|
704
696
|
}
|
|
705
697
|
if (nonInteractive) {
|
|
706
698
|
const selectedKeys = resolveNonInteractiveSelection(entities, opts);
|
|
707
699
|
if (selectedKeys.size === 0) {
|
|
708
700
|
process.stderr.write('No entities matched selection criteria.\n');
|
|
709
|
-
|
|
701
|
+
process.exit(0);
|
|
710
702
|
}
|
|
711
703
|
const selectedComponentKeys = new Set();
|
|
712
704
|
const selectedTokenPaths = new Set();
|
|
@@ -722,15 +714,18 @@ export function registerApplyCommand(program) {
|
|
|
722
714
|
const hasBreaking = entities.some((e) => e.isBreaking && selectedKeys.has(makeSelectKey(e.kind, e.id)));
|
|
723
715
|
if (hasBreaking && !opts.force) {
|
|
724
716
|
process.stderr.write('Error: selection includes breaking changes. Use --force to acknowledge.\n');
|
|
725
|
-
|
|
717
|
+
process.exit(1);
|
|
726
718
|
}
|
|
727
719
|
let operation;
|
|
728
720
|
try {
|
|
729
|
-
operation = await client.applyImport(filteredManifest,
|
|
721
|
+
operation = await client.applyImport(filteredManifest, {
|
|
722
|
+
acknowledgeBreakingChanges: hasBreaking || opts.force === true,
|
|
723
|
+
allowDeletions: opts.allowDeletions === true,
|
|
724
|
+
});
|
|
730
725
|
}
|
|
731
726
|
catch (e) {
|
|
732
727
|
if (e instanceof ApiError)
|
|
733
|
-
|
|
728
|
+
die(`Error: ${formatApiError(e)}`);
|
|
734
729
|
throw e;
|
|
735
730
|
}
|
|
736
731
|
process.stderr.write(`Apply operation started: ${operation.sys.id}\n`);
|
|
@@ -739,13 +734,12 @@ export function registerApplyCommand(program) {
|
|
|
739
734
|
}
|
|
740
735
|
catch (e) {
|
|
741
736
|
if (e instanceof ApiError)
|
|
742
|
-
|
|
737
|
+
die(`Error: ${formatApiError(e)}`);
|
|
743
738
|
throw e;
|
|
744
739
|
}
|
|
745
740
|
const summary = buildApplyOutput(operation, spaceId, environmentId, opts.host);
|
|
746
|
-
recordApplyOutcome(client, spaceId, environmentId, operation);
|
|
747
741
|
process.stdout.write(JSON.stringify(summary, null, 2) + '\n');
|
|
748
|
-
|
|
742
|
+
process.exit(operation.sys.status === 'succeeded' ? 0 : 1);
|
|
749
743
|
return;
|
|
750
744
|
}
|
|
751
745
|
await new Promise((resolvePromise) => {
|
|
@@ -769,7 +763,10 @@ export function registerApplyCommand(program) {
|
|
|
769
763
|
}));
|
|
770
764
|
let operation;
|
|
771
765
|
try {
|
|
772
|
-
operation = await client.applyImport(filteredManifest,
|
|
766
|
+
operation = await client.applyImport(filteredManifest, {
|
|
767
|
+
acknowledgeBreakingChanges: hasBreaking,
|
|
768
|
+
allowDeletions: opts.allowDeletions === true,
|
|
769
|
+
});
|
|
773
770
|
}
|
|
774
771
|
catch (e) {
|
|
775
772
|
if (e instanceof ApiError) {
|
|
@@ -804,7 +801,6 @@ export function registerApplyCommand(program) {
|
|
|
804
801
|
}
|
|
805
802
|
throw e;
|
|
806
803
|
}
|
|
807
|
-
recordApplyOutcome(client, spaceId, environmentId, operation);
|
|
808
804
|
instance.rerender(createElement(ServerApplyDone, {
|
|
809
805
|
operation,
|
|
810
806
|
spaceId,
|
|
@@ -7,6 +7,10 @@ import type { ServerPreviewResponse } from '@contentful/experience-design-system
|
|
|
7
7
|
* would be a pure no-op (INTEG-4411 refined guard).
|
|
8
8
|
*
|
|
9
9
|
* A push that produces ANY entry in ANY bucket — including a rejection that
|
|
10
|
-
* removes a server-side component — is NOT empty.
|
|
10
|
+
* removes a server-side component — is NOT empty. Note the server hides
|
|
11
|
+
* removals entirely (empty removed[]) unless the preview was fetched with
|
|
12
|
+
* allowDeletions: true, so "empty" here also correctly describes a preview
|
|
13
|
+
* with pending-but-hidden deletions: pushing without --allow-deletions is a
|
|
14
|
+
* genuine no-op in that case too, since the apply gate hides them the same way.
|
|
11
15
|
*/
|
|
12
16
|
export declare function isEmptyPreview(preview: ServerPreviewResponse): boolean;
|
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
* would be a pure no-op (INTEG-4411 refined guard).
|
|
7
7
|
*
|
|
8
8
|
* A push that produces ANY entry in ANY bucket — including a rejection that
|
|
9
|
-
* removes a server-side component — is NOT empty.
|
|
9
|
+
* removes a server-side component — is NOT empty. Note the server hides
|
|
10
|
+
* removals entirely (empty removed[]) unless the preview was fetched with
|
|
11
|
+
* allowDeletions: true, so "empty" here also correctly describes a preview
|
|
12
|
+
* with pending-but-hidden deletions: pushing without --allow-deletions is a
|
|
13
|
+
* genuine no-op in that case too, since the apply gate hides them the same way.
|
|
10
14
|
*/
|
|
11
15
|
export function isEmptyPreview(preview) {
|
|
12
16
|
const { components, tokens, taxonomies } = preview;
|
|
@@ -5,16 +5,19 @@ interface ServerPreviewConfirmProps {
|
|
|
5
5
|
spaceId: string;
|
|
6
6
|
environmentId: string;
|
|
7
7
|
breakingWithImpact: boolean;
|
|
8
|
-
|
|
8
|
+
/** The value the preview was actually fetched with. */
|
|
9
|
+
allowDeletions: boolean;
|
|
10
|
+
onConfirm: (acknowledge: boolean, allowDeletions: boolean) => void;
|
|
9
11
|
onCancel: () => void;
|
|
10
12
|
}
|
|
11
|
-
export declare function ServerPreviewConfirm({ preview, spaceId, environmentId, breakingWithImpact, onConfirm, onCancel, }: ServerPreviewConfirmProps): React.ReactElement;
|
|
13
|
+
export declare function ServerPreviewConfirm({ preview, spaceId, environmentId, breakingWithImpact, allowDeletions: fetchedAllowDeletions, onConfirm, onCancel, }: ServerPreviewConfirmProps): React.ReactElement;
|
|
12
14
|
interface ServerPreviewAppProps {
|
|
13
15
|
preview: ServerPreviewResponse;
|
|
14
16
|
spaceId: string;
|
|
15
17
|
environmentId: string;
|
|
18
|
+
allowDeletions: boolean;
|
|
16
19
|
}
|
|
17
|
-
export declare function ServerPreviewApp({ preview, spaceId, environmentId }: ServerPreviewAppProps): React.ReactElement;
|
|
20
|
+
export declare function ServerPreviewApp({ preview, spaceId, environmentId, allowDeletions, }: ServerPreviewAppProps): React.ReactElement;
|
|
18
21
|
interface ServerApplyProgressProps {
|
|
19
22
|
spaceId: string;
|
|
20
23
|
environmentId: string;
|
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import { Box, Text, useInput } from 'ink';
|
|
3
4
|
import { ServerPreviewView } from './ServerPreviewView.js';
|
|
4
5
|
import { buildPostPushUrl } from '../../lib/contentful-urls.js';
|
|
5
6
|
import { formatEdsiError } from '../../lib/error-parser.js';
|
|
6
|
-
export function ServerPreviewConfirm({ preview, spaceId, environmentId, breakingWithImpact, onConfirm, onCancel, }) {
|
|
7
|
+
export function ServerPreviewConfirm({ preview, spaceId, environmentId, breakingWithImpact, allowDeletions: fetchedAllowDeletions, onConfirm, onCancel, }) {
|
|
8
|
+
const [allowDeletions, setAllowDeletions] = useState(fetchedAllowDeletions);
|
|
9
|
+
const removedCount = preview.components.removed.length + preview.tokens.removed.length;
|
|
7
10
|
useInput((input, key) => {
|
|
8
|
-
if (key.return)
|
|
9
|
-
onConfirm(breakingWithImpact);
|
|
11
|
+
if (key.return) {
|
|
12
|
+
onConfirm(breakingWithImpact, allowDeletions);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if ((input === 'x' || input === 'X') && fetchedAllowDeletions && removedCount > 0) {
|
|
16
|
+
setAllowDeletions((prev) => !prev);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
10
19
|
if (key.escape || input === 'q')
|
|
11
20
|
onCancel();
|
|
12
21
|
});
|
|
13
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ServerPreviewView, { preview: preview, spaceId: spaceId, environmentId: environmentId }), _jsxs(Box, { paddingX: 2, flexDirection: "column", children: [breakingWithImpact && (_jsxs(Text, { color: "red", bold: true, children: [' ', "\u26A0 Breaking changes will affect downstream entities. Press Enter to acknowledge and apply."] })), _jsxs(Text, { children: [' ', "Press ", _jsx(Text, { bold: true, children: "Enter" }), " to apply, ", _jsx(Text, { bold: true, children: "Esc" }), " to cancel"] })] })] }));
|
|
22
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ServerPreviewView, { preview: preview, spaceId: spaceId, environmentId: environmentId, allowDeletions: allowDeletions }), _jsxs(Box, { paddingX: 2, flexDirection: "column", children: [breakingWithImpact && (_jsxs(Text, { color: "red", bold: true, children: [' ', "\u26A0 Breaking changes will affect downstream entities. Press Enter to acknowledge and apply."] })), allowDeletions && removedCount > 0 && (_jsxs(Text, { color: "red", bold: true, children: [' ', "\u26A0 ", removedCount, " missing ", removedCount === 1 ? 'entity' : 'entities', " will be permanently deleted. Press Enter to confirm."] })), fetchedAllowDeletions && removedCount > 0 && (_jsxs(Text, { dimColor: true, children: [" [x] ", allowDeletions ? '[✓]' : '[ ]', " Allow deletions"] })), _jsxs(Text, { children: [' ', "Press ", _jsx(Text, { bold: true, children: "Enter" }), " to apply, ", _jsx(Text, { bold: true, children: "Esc" }), " to cancel"] })] })] }));
|
|
14
23
|
}
|
|
15
|
-
export function ServerPreviewApp({ preview, spaceId, environmentId }) {
|
|
24
|
+
export function ServerPreviewApp({ preview, spaceId, environmentId, allowDeletions, }) {
|
|
16
25
|
useInput((input, key) => {
|
|
17
26
|
if (key.escape || input === 'q')
|
|
18
27
|
process.exit(0);
|
|
19
28
|
});
|
|
20
|
-
return _jsx(ServerPreviewView, { preview: preview, spaceId: spaceId, environmentId: environmentId });
|
|
29
|
+
return (_jsx(ServerPreviewView, { preview: preview, spaceId: spaceId, environmentId: environmentId, allowDeletions: allowDeletions }));
|
|
21
30
|
}
|
|
22
31
|
export function ServerApplyProgress({ spaceId, environmentId, status, operationId, error, }) {
|
|
23
32
|
useInput((_input, key) => {
|
|
@@ -4,6 +4,7 @@ interface ServerPreviewViewProps {
|
|
|
4
4
|
preview: ServerPreviewResponse;
|
|
5
5
|
spaceId: string;
|
|
6
6
|
environmentId: string;
|
|
7
|
+
allowDeletions: boolean;
|
|
7
8
|
}
|
|
8
|
-
export declare function ServerPreviewView({ preview, spaceId, environmentId }: ServerPreviewViewProps): React.ReactElement;
|
|
9
|
+
export declare function ServerPreviewView({ preview, spaceId, environmentId, allowDeletions, }: ServerPreviewViewProps): React.ReactElement;
|
|
9
10
|
export {};
|
|
@@ -13,11 +13,11 @@ function DraftWarning({ hasDraft }) {
|
|
|
13
13
|
return null;
|
|
14
14
|
return _jsx(Text, { color: "yellow", children: " \u26A1 has pending draft changes" });
|
|
15
15
|
}
|
|
16
|
-
export function ServerPreviewView({ preview, spaceId, environmentId }) {
|
|
16
|
+
export function ServerPreviewView({ preview, spaceId, environmentId, allowDeletions, }) {
|
|
17
17
|
const { components, tokens } = preview;
|
|
18
18
|
const totalComponents = components.new.length + components.changed.length + components.unchanged.length + components.removed.length;
|
|
19
19
|
const totalTokens = tokens.new.length + tokens.changed.length + tokens.unchanged.length + tokens.removed.length;
|
|
20
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, children: ["Preview \u2014 ", environmentId, " @ ", spaceId] }), _jsx(Text, { children: " " }), totalComponents > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [" Component Types (", totalComponents, " total)"] }), _jsxs(Text, { color: "green", children: [" \u2746 ", components.new.length, " to create"] }), _jsxs(Text, { color: "yellow", children: [" ~ ", components.changed.length, " to update"] }), _jsxs(Text, { color:
|
|
20
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, children: ["Preview \u2014 ", environmentId, " @ ", spaceId] }), _jsx(Text, { children: " " }), totalComponents > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [" Component Types (", totalComponents, " total)"] }), _jsxs(Text, { color: "green", children: [" \u2746 ", components.new.length, " to create"] }), _jsxs(Text, { color: "yellow", children: [" ~ ", components.changed.length, " to update"] }), _jsxs(Text, { color: allowDeletions ? 'red' : 'yellow', children: [' ', allowDeletions ? '✗' : '⊘', " ", components.removed.length, " to ", allowDeletions ? 'delete' : 'skip'] }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", components.unchanged.length, " unchanged"] }), components.changed.map((item, i) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: "yellow", children: [" ~ ", item.current.name] }), _jsx(DraftWarning, { hasDraft: item.hasPendingDraftChanges })] }), _jsx(BreakingBadge, { item: item })] }, i))), _jsx(Text, { children: " " })] })), totalTokens > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [" Design Tokens (", totalTokens, " total)"] }), _jsxs(Text, { color: "green", children: [" \u2746 ", tokens.new.length, " to create"] }), _jsxs(Text, { color: "yellow", children: [" ~ ", tokens.changed.length, " to update"] }), _jsxs(Text, { color: allowDeletions ? 'red' : 'yellow', children: [' ', allowDeletions ? '✗' : '⊘', " ", tokens.removed.length, " to ", allowDeletions ? 'delete' : 'skip'] }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", tokens.unchanged.length, " unchanged"] }), tokens.changed
|
|
21
21
|
.filter((t) => t.hasPendingDraftChanges)
|
|
22
|
-
.map((item, i) => (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: "yellow", children: [" ~ ", item.current.name] }), _jsx(DraftWarning, { hasDraft: true })] }, i))), _jsx(Text, { children: " " })] })), _jsx(Text, { dimColor: true, children: " Press Q to exit." })] }));
|
|
22
|
+
.map((item, i) => (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: "yellow", children: [" ~ ", item.current.name] }), _jsx(DraftWarning, { hasDraft: true })] }, i))), _jsx(Text, { children: " " })] })), !allowDeletions && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "yellow", children: " \u2298 Deletions are hidden by default \u2014 rerun with --allow-deletions to see them." }) })), _jsx(Text, { dimColor: true, children: " Press Q to exit." })] }));
|
|
23
23
|
}
|