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