@contentful/experience-design-system-cli 2.2.1 → 2.5.2-dev-build-eebccad.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 +3 -1
- package/dist/package.json +2 -1
- package/dist/src/analyze/select/tui/App.js +0 -2
- package/dist/src/apply/api-client.d.ts +1 -5
- package/dist/src/apply/api-client.js +12 -37
- package/dist/src/apply/command.js +3 -9
- package/dist/src/apply/tui/ServerPreviewView.js +1 -1
- package/dist/src/credentials-store.d.ts +4 -4
- package/dist/src/credentials-store.js +4 -4
- package/dist/src/import/command.js +2 -2
- package/dist/src/import/tui/WizardApp.js +36 -28
- package/dist/src/import/tui/steps/CredentialsStep.js +2 -2
- package/dist/src/import/tui/steps/ErrorStep.d.ts +2 -1
- package/dist/src/import/tui/steps/ErrorStep.js +6 -2
- package/dist/src/session/db.d.ts +1 -1
- package/dist/src/setup/command.js +31 -31
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @contentful/experience-design-system-cli
|
|
2
2
|
|
|
3
|
-
CLI for extracting, reviewing, generating, validating, and pushing Contentful Experience Design System component definitions.
|
|
3
|
+
CLI for extracting, reviewing, generating, validating, and pushing Contentful Experience Design System component definitions into [Experience Orchestration (ExO)](https://www.contentful.com/help/experience-orchestration/).
|
|
4
|
+
|
|
5
|
+
**Binary names:** `experiences` (primary), `exo` (alias), `experience-design-system-cli` (long-form). All three point to the same executable.
|
|
4
6
|
|
|
5
7
|
## Pipeline Overview
|
|
6
8
|
|
package/dist/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.5.2-dev-build-eebccad.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"experience-design-system-cli": "./bin/cli.js",
|
|
8
|
+
"experiences": "./bin/cli.js",
|
|
8
9
|
"exo": "./bin/cli.js"
|
|
9
10
|
},
|
|
10
11
|
"main": "./dist/src/index.js",
|
|
@@ -78,8 +78,6 @@ export function App({ sessionId, artifactsRoot, reviewRoot }) {
|
|
|
78
78
|
if (!manifest.componentsManifest)
|
|
79
79
|
manifest.componentsManifest = {};
|
|
80
80
|
const client = new ImportApiClient({ cmaToken, spaceId, environmentId });
|
|
81
|
-
const orgId = await client.resolveOrganizationId();
|
|
82
|
-
client.setOrganizationId(orgId);
|
|
83
81
|
const preview = await client.previewImport(manifest);
|
|
84
82
|
const annotations = {};
|
|
85
83
|
for (const item of preview.components.new) {
|
|
@@ -5,7 +5,6 @@ export interface ApiClientOptions {
|
|
|
5
5
|
cmaToken: string;
|
|
6
6
|
spaceId: string;
|
|
7
7
|
environmentId: string;
|
|
8
|
-
organizationId?: string;
|
|
9
8
|
}
|
|
10
9
|
export declare class ApiError extends Error {
|
|
11
10
|
readonly status: number;
|
|
@@ -17,13 +16,10 @@ export declare class ImportApiClient {
|
|
|
17
16
|
private token;
|
|
18
17
|
private spaceId;
|
|
19
18
|
private environmentId;
|
|
20
|
-
private organizationId;
|
|
21
19
|
constructor(opts: ApiClientOptions);
|
|
22
|
-
setOrganizationId(orgId: string | null): void;
|
|
23
20
|
private base;
|
|
24
21
|
private headers;
|
|
25
|
-
|
|
26
|
-
validateEnvironment(): Promise<void>;
|
|
22
|
+
validateToken(): Promise<void>;
|
|
27
23
|
previewImport(manifest: ManifestPayload): Promise<ServerPreviewResponse>;
|
|
28
24
|
applyImport(manifest: ManifestPayload, acknowledgeBreakingChanges: boolean): Promise<ApplyOperationResponse>;
|
|
29
25
|
pollOperation(operationId: string, opts?: {
|
|
@@ -19,8 +19,6 @@ async function request(url, options) {
|
|
|
19
19
|
Authorization: `Bearer ${options.token}`,
|
|
20
20
|
'Content-Type': 'application/json',
|
|
21
21
|
};
|
|
22
|
-
if (options.orgId)
|
|
23
|
-
headers['x-contentful-organization-id'] = options.orgId;
|
|
24
22
|
const init = {
|
|
25
23
|
method: options.method ?? 'GET',
|
|
26
24
|
headers,
|
|
@@ -34,58 +32,35 @@ export class ImportApiClient {
|
|
|
34
32
|
token;
|
|
35
33
|
spaceId;
|
|
36
34
|
environmentId;
|
|
37
|
-
organizationId;
|
|
38
35
|
constructor(opts) {
|
|
39
36
|
this.host = opts.host ?? DEFAULT_HOST;
|
|
40
37
|
this.token = opts.cmaToken;
|
|
41
38
|
this.spaceId = opts.spaceId;
|
|
42
39
|
this.environmentId = opts.environmentId;
|
|
43
|
-
this.organizationId = opts.organizationId;
|
|
44
|
-
}
|
|
45
|
-
setOrganizationId(orgId) {
|
|
46
|
-
if (orgId)
|
|
47
|
-
this.organizationId = orgId;
|
|
48
40
|
}
|
|
49
41
|
base() {
|
|
50
42
|
return `${this.host}/spaces/${this.spaceId}/environments/${this.environmentId}`;
|
|
51
43
|
}
|
|
52
44
|
headers() {
|
|
53
|
-
|
|
45
|
+
return {
|
|
54
46
|
Authorization: `Bearer ${this.token}`,
|
|
55
47
|
'Content-Type': 'application/json',
|
|
56
48
|
};
|
|
57
|
-
if (this.organizationId)
|
|
58
|
-
h['x-contentful-organization-id'] = this.organizationId;
|
|
59
|
-
return h;
|
|
60
|
-
}
|
|
61
|
-
async resolveOrganizationId() {
|
|
62
|
-
const url = `${this.host}/spaces/${this.spaceId}`;
|
|
63
|
-
const res = await request(url, { token: this.token });
|
|
64
|
-
if (res.status === 401 || res.status === 403) {
|
|
65
|
-
throw new ApiError(`space '${this.spaceId}' not found or CMA token lacks access`, res.status, await res.text());
|
|
66
|
-
}
|
|
67
|
-
if (res.status === 404) {
|
|
68
|
-
// Custom host (e.g. staging) may not expose the spaces API or the space may not
|
|
69
|
-
// exist there. Skip the org ID — the design systems API may not require it.
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
if (!res.ok) {
|
|
73
|
-
throw new ApiError(`unexpected error fetching space: ${res.status}`, res.status, await res.text());
|
|
74
|
-
}
|
|
75
|
-
const json = (await res.json());
|
|
76
|
-
return json.sys.organization.sys.id;
|
|
77
49
|
}
|
|
78
|
-
async
|
|
79
|
-
|
|
50
|
+
async validateToken() {
|
|
51
|
+
// /users/me is the canonical "is this token valid" endpoint — it doesn't enforce
|
|
52
|
+
// space-membership rules that don't apply to the design-systems API path, so it
|
|
53
|
+
// doesn't false-positive 401 for tokens that are entitled to call import endpoints
|
|
54
|
+
// but lack the role assignments public CMA's SpacesController/EnvironmentsController
|
|
55
|
+
// require. Per-space/per-org authorization is enforced by the design-systems API itself
|
|
56
|
+
// on the actual preview/apply call.
|
|
57
|
+
const url = `${this.host}/users/me`;
|
|
80
58
|
const res = await request(url, { token: this.token });
|
|
81
|
-
if (res.status ===
|
|
82
|
-
throw new ApiError(
|
|
83
|
-
}
|
|
84
|
-
if (res.status === 401 || res.status === 403) {
|
|
85
|
-
throw new ApiError(`space '${this.spaceId}' not found or CMA token lacks access`, res.status, await res.text());
|
|
59
|
+
if (res.status === 401) {
|
|
60
|
+
throw new ApiError('CMA token is invalid or revoked', res.status, await res.text());
|
|
86
61
|
}
|
|
87
62
|
if (!res.ok) {
|
|
88
|
-
throw new ApiError(`unexpected error validating
|
|
63
|
+
throw new ApiError(`unexpected error validating token: ${res.status}`, res.status, await res.text());
|
|
89
64
|
}
|
|
90
65
|
}
|
|
91
66
|
async previewImport(manifest) {
|
|
@@ -392,9 +392,7 @@ export function registerApplyCommand(program) {
|
|
|
392
392
|
}
|
|
393
393
|
const { components, tokens, client } = inputs;
|
|
394
394
|
try {
|
|
395
|
-
|
|
396
|
-
client.setOrganizationId(orgId);
|
|
397
|
-
await client.validateEnvironment();
|
|
395
|
+
await client.validateToken();
|
|
398
396
|
}
|
|
399
397
|
catch (e) {
|
|
400
398
|
if (e instanceof ApiError)
|
|
@@ -459,9 +457,7 @@ export function registerApplyCommand(program) {
|
|
|
459
457
|
}
|
|
460
458
|
const { components, tokens, client } = inputs;
|
|
461
459
|
try {
|
|
462
|
-
|
|
463
|
-
client.setOrganizationId(orgId);
|
|
464
|
-
await client.validateEnvironment();
|
|
460
|
+
await client.validateToken();
|
|
465
461
|
}
|
|
466
462
|
catch (e) {
|
|
467
463
|
if (e instanceof ApiError)
|
|
@@ -638,9 +634,7 @@ export function registerApplyCommand(program) {
|
|
|
638
634
|
}
|
|
639
635
|
const { components, tokens, client } = inputs;
|
|
640
636
|
try {
|
|
641
|
-
|
|
642
|
-
client.setOrganizationId(orgId);
|
|
643
|
-
await client.validateEnvironment();
|
|
637
|
+
await client.validateToken();
|
|
644
638
|
}
|
|
645
639
|
catch (e) {
|
|
646
640
|
if (e instanceof ApiError)
|
|
@@ -15,7 +15,7 @@ export function ServerPreviewView({ preview, spaceId, environmentId }) {
|
|
|
15
15
|
const { components, tokens } = preview;
|
|
16
16
|
const totalComponents = components.new.length + components.changed.length + components.unchanged.length + components.removed.length;
|
|
17
17
|
const totalTokens = tokens.new.length + tokens.changed.length + tokens.unchanged.length + tokens.removed.length;
|
|
18
|
-
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: [" \
|
|
18
|
+
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
|
|
19
19
|
.filter((t) => t.hasPendingDraftChanges)
|
|
20
20
|
.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." })] }));
|
|
21
21
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type ExperiencesCredentials = {
|
|
2
2
|
spaceId: string;
|
|
3
3
|
environmentId: string;
|
|
4
4
|
cmaToken: string;
|
|
5
5
|
};
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
6
|
+
export declare function readExperiencesCredentials(): Promise<ExperiencesCredentials>;
|
|
7
|
+
export declare function writeExperiencesCredentials(creds: ExperiencesCredentials): Promise<void>;
|
|
8
|
+
export declare function experiencesCredentialsPath(): string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
const CREDENTIALS_DIR = join(homedir(), '.config', '
|
|
4
|
+
const CREDENTIALS_DIR = join(homedir(), '.config', 'experiences');
|
|
5
5
|
const CREDENTIALS_PATH = join(CREDENTIALS_DIR, 'credentials.json');
|
|
6
|
-
export async function
|
|
6
|
+
export async function readExperiencesCredentials() {
|
|
7
7
|
try {
|
|
8
8
|
const raw = await readFile(CREDENTIALS_PATH, 'utf8');
|
|
9
9
|
const parsed = JSON.parse(raw);
|
|
@@ -21,10 +21,10 @@ export async function readExoCredentials() {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
export async function
|
|
24
|
+
export async function writeExperiencesCredentials(creds) {
|
|
25
25
|
await mkdir(CREDENTIALS_DIR, { recursive: true });
|
|
26
26
|
await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2) + '\n', { mode: 0o600 });
|
|
27
27
|
}
|
|
28
|
-
export function
|
|
28
|
+
export function experiencesCredentialsPath() {
|
|
29
29
|
return CREDENTIALS_PATH;
|
|
30
30
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve, join } from 'node:path';
|
|
2
2
|
import { runPipeline } from './orchestrator.js';
|
|
3
|
-
import {
|
|
3
|
+
import { readExperiencesCredentials } from '../credentials-store.js';
|
|
4
4
|
export function registerImportCommand(program) {
|
|
5
5
|
program
|
|
6
6
|
.command('import')
|
|
@@ -40,7 +40,7 @@ export function registerImportCommand(program) {
|
|
|
40
40
|
const { render } = await import('ink');
|
|
41
41
|
const { createElement } = await import('react');
|
|
42
42
|
const { WizardApp } = await import('./tui/WizardApp.js');
|
|
43
|
-
const creds = await
|
|
43
|
+
const creds = await readExperiencesCredentials();
|
|
44
44
|
const { waitUntilExit } = render(createElement(WizardApp, {
|
|
45
45
|
initialSpaceId: creds.spaceId,
|
|
46
46
|
initialEnvironmentId: creds.environmentId || 'master',
|
|
@@ -34,7 +34,7 @@ function runCli(args) {
|
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
-
const WIZARD_LOG = join(tmpdir(), '
|
|
37
|
+
const WIZARD_LOG = join(tmpdir(), 'experiences-import-wizard.log');
|
|
38
38
|
function logStep(entry) {
|
|
39
39
|
const line = JSON.stringify({ ts: new Date().toISOString(), ...entry }) + '\n';
|
|
40
40
|
appendFileSync(WIZARD_LOG, line);
|
|
@@ -45,7 +45,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
45
45
|
const terminalWidth = stdout?.columns ?? 80;
|
|
46
46
|
const logInit = useRef(false);
|
|
47
47
|
if (!logInit.current) {
|
|
48
|
-
writeFileSync(WIZARD_LOG, `---
|
|
48
|
+
writeFileSync(WIZARD_LOG, `--- experiences import session ${new Date().toISOString()} ---\n`);
|
|
49
49
|
logInit.current = true;
|
|
50
50
|
}
|
|
51
51
|
const credentialsRef = useRef(null);
|
|
@@ -85,6 +85,8 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
85
85
|
},
|
|
86
86
|
errorStep: '',
|
|
87
87
|
errorMessage: '',
|
|
88
|
+
errorAllowCredentialRetry: false,
|
|
89
|
+
authCheckStepNumber: 1,
|
|
88
90
|
});
|
|
89
91
|
useEffect(() => {
|
|
90
92
|
sessionRef.current = {
|
|
@@ -139,15 +141,16 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
139
141
|
logStep({ update: sanitized });
|
|
140
142
|
setState((prev) => ({ ...prev, ...partial }));
|
|
141
143
|
};
|
|
142
|
-
// ── Agent auth pre-flight
|
|
144
|
+
// ── Agent auth pre-flight ───────────────────────────────────────────────────────
|
|
143
145
|
const runAgentAuthCheck = async (nextStep) => {
|
|
144
|
-
|
|
146
|
+
const authCheckStepNumber = nextStep === 'generating-tokens' ? 1 : state.tokensPath ? 4 : 3;
|
|
147
|
+
update({ step: 'checking-claude-auth', authCheckStepNumber });
|
|
145
148
|
const status = await checkAgentAuth(state.agent);
|
|
146
149
|
if (status === 'not-found') {
|
|
147
150
|
update({
|
|
148
151
|
step: 'error',
|
|
149
152
|
errorStep: `${state.agent} auth check`,
|
|
150
|
-
errorMessage: `The \`${state.agent}\` CLI was not found on your PATH.\n\nInstall it, then re-run \`
|
|
153
|
+
errorMessage: `The \`${state.agent}\` CLI was not found on your PATH.\n\nInstall it, then re-run \`experiences import\`.`,
|
|
151
154
|
});
|
|
152
155
|
return false;
|
|
153
156
|
}
|
|
@@ -156,7 +159,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
156
159
|
step: 'error',
|
|
157
160
|
errorStep: `${state.agent} auth check`,
|
|
158
161
|
errorMessage: `${state.agent} is not authenticated.\n\n` +
|
|
159
|
-
`Run \`${state.agent}\` in your terminal to log in, then re-run \`
|
|
162
|
+
`Run \`${state.agent}\` in your terminal to log in, then re-run \`experiences import\`.\n\n` +
|
|
160
163
|
'If you are using AWS Bedrock, run:\n' +
|
|
161
164
|
' aws sso login --profile <your-profile>',
|
|
162
165
|
});
|
|
@@ -165,7 +168,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
165
168
|
update({ step: nextStep });
|
|
166
169
|
return true;
|
|
167
170
|
};
|
|
168
|
-
// ── Step runners
|
|
171
|
+
// ── Step runners ────────────────────────────────────────────────────────
|
|
169
172
|
const runGenerateTokens = async (rawTokensPath, outDir) => {
|
|
170
173
|
const result = await new Promise((res) => {
|
|
171
174
|
const child = spawn('node', [
|
|
@@ -438,7 +441,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
438
441
|
update({ step: 'validating-credentials' });
|
|
439
442
|
try {
|
|
440
443
|
const client = new ImportApiClient({ cmaToken, spaceId, environmentId, host: apiHost });
|
|
441
|
-
await client.
|
|
444
|
+
await client.validateToken();
|
|
442
445
|
const { extractSessionId, tokensPath } = sessionRef.current;
|
|
443
446
|
void runPreview(extractSessionId, tokensPath, spaceId, environmentId, cmaToken);
|
|
444
447
|
}
|
|
@@ -448,15 +451,18 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
448
451
|
return;
|
|
449
452
|
}
|
|
450
453
|
const msg = e instanceof Error ? e.message : 'Credential check failed';
|
|
451
|
-
update({
|
|
454
|
+
update({
|
|
455
|
+
step: 'error',
|
|
456
|
+
errorStep: 'validating-credentials',
|
|
457
|
+
errorMessage: msg,
|
|
458
|
+
errorAllowCredentialRetry: false,
|
|
459
|
+
});
|
|
452
460
|
}
|
|
453
461
|
};
|
|
454
462
|
const runPreview = async (extractSessionId, tokensPath, spaceId, environmentId, cmaToken) => {
|
|
455
463
|
update({ step: 'previewing' });
|
|
456
464
|
try {
|
|
457
465
|
const client = new ImportApiClient({ cmaToken, spaceId, environmentId, host: apiHost });
|
|
458
|
-
const orgId = await client.resolveOrganizationId();
|
|
459
|
-
client.setOrganizationId(orgId);
|
|
460
466
|
let components = [];
|
|
461
467
|
if (extractSessionId) {
|
|
462
468
|
const db = openPipelineDb();
|
|
@@ -520,21 +526,25 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
520
526
|
// Space-level config errors (e.g. "Design system public CMA is disabled") cannot be
|
|
521
527
|
// fixed by re-entering credentials — send to error screen.
|
|
522
528
|
if (bodyMsg && /disabled/i.test(bodyMsg)) {
|
|
523
|
-
update({
|
|
529
|
+
update({
|
|
530
|
+
step: 'error',
|
|
531
|
+
errorStep: 'apply preview',
|
|
532
|
+
errorMessage: `Preview failed: ${bodyMsg}`,
|
|
533
|
+
errorAllowCredentialRetry: false,
|
|
534
|
+
});
|
|
524
535
|
return;
|
|
525
536
|
}
|
|
526
537
|
update({ step: 'credentials', credentialsError: e.message });
|
|
527
538
|
return;
|
|
528
539
|
}
|
|
529
540
|
if (e.status === 404) {
|
|
530
|
-
// 404
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
// - previewImport: design systems endpoint doesn't exist for this space/environment
|
|
534
|
-
// Neither is a credentials problem — show a clear error instead of looping.
|
|
541
|
+
// 404 from previewImport means the design systems endpoint doesn't exist for this
|
|
542
|
+
// space/environment (typically wrong --host or wrong space/env). Not a credentials
|
|
543
|
+
// problem — show a clear error instead of looping.
|
|
535
544
|
update({
|
|
536
545
|
step: 'error',
|
|
537
546
|
errorStep: 'apply preview',
|
|
547
|
+
errorAllowCredentialRetry: true,
|
|
538
548
|
errorMessage: `Not found (404). Check that the space ID, environment ID, and host are correct.\n\n` +
|
|
539
549
|
` Space: ${spaceId}\n` +
|
|
540
550
|
` Environment: ${environmentId}\n` +
|
|
@@ -543,11 +553,11 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
543
553
|
});
|
|
544
554
|
return;
|
|
545
555
|
}
|
|
546
|
-
update({ step: 'error', errorStep: 'apply preview', errorMessage: e.message });
|
|
556
|
+
update({ step: 'error', errorStep: 'apply preview', errorMessage: e.message, errorAllowCredentialRetry: true });
|
|
547
557
|
return;
|
|
548
558
|
}
|
|
549
559
|
const msg = e instanceof Error ? e.message : 'Preview failed';
|
|
550
|
-
update({ step: 'error', errorStep: 'apply preview', errorMessage: msg });
|
|
560
|
+
update({ step: 'error', errorStep: 'apply preview', errorMessage: msg, errorAllowCredentialRetry: true });
|
|
551
561
|
}
|
|
552
562
|
};
|
|
553
563
|
const runPush = async (manifest, spaceId, environmentId, cmaToken, acknowledgeBreakingChanges, preview) => {
|
|
@@ -570,8 +580,6 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
570
580
|
update({ step: 'pushing' });
|
|
571
581
|
try {
|
|
572
582
|
const client = new ImportApiClient({ cmaToken, spaceId, environmentId, host: apiHost });
|
|
573
|
-
const orgId = await client.resolveOrganizationId();
|
|
574
|
-
client.setOrganizationId(orgId);
|
|
575
583
|
let operation = await client.applyImport(manifest, acknowledgeBreakingChanges);
|
|
576
584
|
try {
|
|
577
585
|
logStep({
|
|
@@ -650,7 +658,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
650
658
|
}
|
|
651
659
|
catch (e) {
|
|
652
660
|
const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : 'Push failed';
|
|
653
|
-
update({ step: 'error', errorStep: 'apply push', errorMessage: msg });
|
|
661
|
+
update({ step: 'error', errorStep: 'apply push', errorMessage: msg, errorAllowCredentialRetry: true });
|
|
654
662
|
}
|
|
655
663
|
};
|
|
656
664
|
const runPrintFiles = async (extractSessionId, outDir) => {
|
|
@@ -667,7 +675,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
667
675
|
// tokensPath is already on disk from generate-tokens step; just record it
|
|
668
676
|
update({ step: 'print-gate', componentsPath });
|
|
669
677
|
};
|
|
670
|
-
// ── Effect: kick off automatic steps
|
|
678
|
+
// ── Effect: kick off automatic steps ───────────────────────────────────────────────
|
|
671
679
|
const tokenReuseChecked = useRef(false);
|
|
672
680
|
useEffect(() => {
|
|
673
681
|
if (state.step === 'generating-tokens') {
|
|
@@ -694,7 +702,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
694
702
|
})();
|
|
695
703
|
}
|
|
696
704
|
}, [state.step]); // intentional: only re-run when step changes
|
|
697
|
-
// ── Render
|
|
705
|
+
// ── Render ────────────────────────────────────────────────────────────────────────────
|
|
698
706
|
const noQuitSteps = [
|
|
699
707
|
'checking-claude-auth',
|
|
700
708
|
'validating-credentials',
|
|
@@ -734,7 +742,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
734
742
|
}
|
|
735
743
|
}, onQuit: () => process.exit(0) }));
|
|
736
744
|
case 'checking-claude-auth':
|
|
737
|
-
return (_jsx(RunningStep, { stepNumber:
|
|
745
|
+
return (_jsx(RunningStep, { stepNumber: state.authCheckStepNumber, totalSteps: totalSteps, title: `Checking ${state.agent}`, description: `Verifying ${state.agent} is installed and authenticated...` }));
|
|
738
746
|
case 'generating-tokens':
|
|
739
747
|
return (_jsx(RunningStep, { stepNumber: 1, totalSteps: totalSteps, title: "Generating token definitions", description: `${state.agent} is mapping your design tokens to DTCG format and writing tokens.json. This may take a few minutes.` }));
|
|
740
748
|
case 'path-validation':
|
|
@@ -836,7 +844,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
836
844
|
void runPreview(extractSessionId, tokensPath, state.spaceId, state.environmentId, state.cmaToken);
|
|
837
845
|
}, onQuit: () => process.exit(0) }));
|
|
838
846
|
case 'validating-credentials':
|
|
839
|
-
return (_jsx(RunningStep, { stepNumber: totalSteps, totalSteps: totalSteps, title: "Validating credentials", description: "Checking that your space ID and CMA token are valid..." }));
|
|
847
|
+
return (_jsx(RunningStep, { stepNumber: totalSteps - 1, totalSteps: totalSteps, title: "Validating credentials", description: "Checking that your space ID and CMA token are valid..." }));
|
|
840
848
|
case 'previewing':
|
|
841
849
|
return (_jsx(RunningStep, { stepNumber: totalSteps, totalSteps: totalSteps, title: "Computing diff", description: "Computing diff against your Contentful space..." }));
|
|
842
850
|
case 'preview-gate':
|
|
@@ -857,13 +865,13 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
857
865
|
hasTokens && state.tokensPath ? `tokens.json → ${state.tokensPath}` : null,
|
|
858
866
|
]
|
|
859
867
|
.filter(Boolean)
|
|
860
|
-
.join('\n'), context: "Your files are saved to disk. Run `
|
|
868
|
+
.join('\n'), context: "Your files are saved to disk. Run `experiences import` again when you're ready to push to Contentful.", continueLabel: "Exit", showSkip: false, onContinue: () => process.exit(0), onQuit: () => process.exit(0) }));
|
|
861
869
|
case 'done': {
|
|
862
870
|
const totalFailed = state.pushResult.componentTypes.failed + state.pushResult.designTokens.failed;
|
|
863
871
|
return (_jsx(DoneStep, { componentTypes: state.pushResult.componentTypes, designTokens: state.pushResult.designTokens, summary: state.pushResult.summary, spaceId: state.spaceId, environmentId: state.environmentId, onExit: () => process.exit(totalFailed > 0 ? 1 : 0) }));
|
|
864
872
|
}
|
|
865
873
|
case 'error':
|
|
866
|
-
return _jsx(ErrorStep, { stepName: state.errorStep, message: state.errorMessage, onExit: () => process.exit(1) });
|
|
874
|
+
return (_jsx(ErrorStep, { stepName: state.errorStep, message: state.errorMessage, onExit: () => process.exit(1), onRetryCredentials: state.errorAllowCredentialRetry ? () => update({ step: 'credentials', credentialsError: '' }) : undefined }));
|
|
867
875
|
}
|
|
868
876
|
})();
|
|
869
877
|
return (_jsxs(Box, { flexDirection: "column", width: terminalWidth, children: [_jsx(TopBar, { subcommand: "import", hints: hints }), stepContent] }));
|
|
@@ -74,6 +74,6 @@ export function CredentialsStep({ summary, error: externalError, initialSpaceId
|
|
|
74
74
|
}
|
|
75
75
|
const displayError = inlineError ?? externalError ?? null;
|
|
76
76
|
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [summary && _jsxs(Text, { color: "green", children: ["\u2713 ", summary] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: initialSpaceId && initialCmaToken
|
|
77
|
-
? 'Credentials pre-filled from
|
|
78
|
-
: 'Enter your Contentful credentials to continue.' }) }), !(initialSpaceId && initialCmaToken) && (_jsx(Text, { dimColor: true, children: "Tip: run
|
|
77
|
+
? 'Credentials pre-filled from experiences setup. Press Enter to continue or edit any field to update.'
|
|
78
|
+
: 'Enter your Contentful credentials to continue.' }) }), !(initialSpaceId && initialCmaToken) && (_jsx(Text, { dimColor: true, children: "Tip: run experiences setup to save these to ~/.config/experiences/credentials.json so they pre-fill here automatically." })), _jsxs(Box, { flexDirection: "column", gap: 0, marginTop: 1, children: [renderField('Space ID', spaceId, 'spaceId'), renderField('Environment', environmentId, 'environmentId'), renderField('CMA Token', cmaToken, 'cmaToken', true)] }), displayError && _jsxs(Text, { color: "red", children: ["\u2717 ", displayError] }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Next field / Submit" }), _jsx(Text, { dimColor: true, children: "[Tab] Switch field" }), _jsx(Text, { dimColor: true, children: "[q] Quit" })] })] }));
|
|
79
79
|
}
|
|
@@ -3,6 +3,7 @@ type ErrorStepProps = {
|
|
|
3
3
|
stepName: string;
|
|
4
4
|
message: string;
|
|
5
5
|
onExit: () => void;
|
|
6
|
+
onRetryCredentials?: () => void;
|
|
6
7
|
};
|
|
7
|
-
export declare function ErrorStep({ stepName, message, onExit }: ErrorStepProps): React.ReactElement;
|
|
8
|
+
export declare function ErrorStep({ stepName, message, onExit, onRetryCredentials }: ErrorStepProps): React.ReactElement;
|
|
8
9
|
export {};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
|
|
4
|
-
export function ErrorStep({ stepName, message, onExit }) {
|
|
4
|
+
export function ErrorStep({ stepName, message, onExit, onRetryCredentials }) {
|
|
5
5
|
useImmediateInput((input, key) => {
|
|
6
|
+
if (input === 'r' && onRetryCredentials) {
|
|
7
|
+
onRetryCredentials();
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
6
10
|
if (key.return || input === 'q' || key.escape) {
|
|
7
11
|
onExit();
|
|
8
12
|
}
|
|
9
13
|
});
|
|
10
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, color: "red", children: ["\u2717 ", stepName, " failed"] }), _jsx(Text, { color: "red", children: message }),
|
|
14
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, color: "red", children: ["\u2717 ", stepName, " failed"] }), _jsx(Text, { color: "red", children: message }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter / q] Exit" }), onRetryCredentials && _jsx(Text, { dimColor: true, children: "[r] Re-enter credentials" })] })] }));
|
|
11
15
|
}
|
package/dist/src/session/db.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export declare function seedCDFFromPreviewResponse(db: DatabaseSync, sessionId:
|
|
|
83
83
|
*/
|
|
84
84
|
export declare function seedDefaultsFromChangedItems(db: DatabaseSync, sessionId: string, changedItems: Array<{
|
|
85
85
|
current: ComponentTypeSummary;
|
|
86
|
-
proposed:
|
|
86
|
+
proposed: object;
|
|
87
87
|
}>): number;
|
|
88
88
|
/**
|
|
89
89
|
* Ensures all props on generated components have a cdf_type.
|
|
@@ -5,7 +5,7 @@ import { homedir } from 'node:os';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { createInterface } from 'node:readline';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
|
-
import {
|
|
8
|
+
import { readExperiencesCredentials, writeExperiencesCredentials, experiencesCredentialsPath, } from '../credentials-store.js';
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
10
|
const REQUIRED_NODE_MAJOR = 24;
|
|
11
11
|
// ── Output helpers ────────────────────────────────────────────────────────────
|
|
@@ -169,7 +169,7 @@ async function setupNode() {
|
|
|
169
169
|
info(`nvm detected. Will run: nvm install ${REQUIRED_NODE_MAJOR} && nvm use ${REQUIRED_NODE_MAJOR}`);
|
|
170
170
|
const go = await confirm(`Install and switch to Node ${REQUIRED_NODE_MAJOR} via nvm?`);
|
|
171
171
|
if (!go) {
|
|
172
|
-
warn(`Skipped. Re-run
|
|
172
|
+
warn(`Skipped. Re-run experiences setup after switching to Node ${REQUIRED_NODE_MAJOR}.`);
|
|
173
173
|
return false;
|
|
174
174
|
}
|
|
175
175
|
// nvm is a shell function so we source it and run in a subshell
|
|
@@ -184,14 +184,14 @@ async function setupNode() {
|
|
|
184
184
|
info(`Run manually: nvm install ${REQUIRED_NODE_MAJOR} && nvm use ${REQUIRED_NODE_MAJOR}`);
|
|
185
185
|
return false;
|
|
186
186
|
}
|
|
187
|
-
ok(`Node ${REQUIRED_NODE_MAJOR} installed via nvm. Re-run
|
|
187
|
+
ok(`Node ${REQUIRED_NODE_MAJOR} installed via nvm. Re-run experiences setup in a fresh shell to pick it up.`);
|
|
188
188
|
return false; // Need fresh shell to get the new node on PATH
|
|
189
189
|
}
|
|
190
190
|
if (hasFnm) {
|
|
191
191
|
info(`fnm detected. Will run: fnm install ${REQUIRED_NODE_MAJOR} && fnm use ${REQUIRED_NODE_MAJOR}`);
|
|
192
192
|
const go = await confirm(`Install and switch to Node ${REQUIRED_NODE_MAJOR} via fnm?`);
|
|
193
193
|
if (!go) {
|
|
194
|
-
warn(`Skipped. Re-run
|
|
194
|
+
warn(`Skipped. Re-run experiences setup after switching to Node ${REQUIRED_NODE_MAJOR}.`);
|
|
195
195
|
return false;
|
|
196
196
|
}
|
|
197
197
|
const result = await runSpawn('fnm', ['install', String(REQUIRED_NODE_MAJOR)]);
|
|
@@ -212,7 +212,7 @@ async function setupNode() {
|
|
|
212
212
|
info(`Run manually: fnm default ${REQUIRED_NODE_MAJOR}`);
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
|
-
ok(`Node ${REQUIRED_NODE_MAJOR} installed via fnm. Re-run
|
|
215
|
+
ok(`Node ${REQUIRED_NODE_MAJOR} installed via fnm. Re-run experiences setup in a fresh shell.`);
|
|
216
216
|
return false;
|
|
217
217
|
}
|
|
218
218
|
// No version manager found — offer to install nvm
|
|
@@ -229,7 +229,7 @@ async function setupNode() {
|
|
|
229
229
|
info('Install manually: https://github.com/nvm-sh/nvm#installing-and-updating');
|
|
230
230
|
return false;
|
|
231
231
|
}
|
|
232
|
-
ok('nvm installed. Open a new shell, then re-run
|
|
232
|
+
ok('nvm installed. Open a new shell, then re-run experiences setup.');
|
|
233
233
|
return false;
|
|
234
234
|
}
|
|
235
235
|
info(`Install Node ${REQUIRED_NODE_MAJOR} manually from https://nodejs.org`);
|
|
@@ -308,7 +308,7 @@ async function setupBuild(repoRoot) {
|
|
|
308
308
|
// ── Step 4: agent CLI ─────────────────────────────────────────────────────────
|
|
309
309
|
async function setupAgent() {
|
|
310
310
|
section('Step 4: Coding agent (claude, codex, opencode, or cursor)', '[required]');
|
|
311
|
-
info('
|
|
311
|
+
info('experiences import uses a coding agent to generate component definitions.');
|
|
312
312
|
info('');
|
|
313
313
|
const agents = [
|
|
314
314
|
{ name: 'Claude Code', binary: 'claude', installHint: 'npm install -g @anthropic-ai/claude-code && claude login' },
|
|
@@ -375,15 +375,15 @@ async function setupAgent() {
|
|
|
375
375
|
info('Run `opencode auth` to configure your provider.');
|
|
376
376
|
return true;
|
|
377
377
|
}
|
|
378
|
-
warn('Skipped. Install a coding agent before running
|
|
378
|
+
warn('Skipped. Install a coding agent before running experiences import.');
|
|
379
379
|
return false;
|
|
380
380
|
}
|
|
381
381
|
// ── Step 5: Contentful credentials ───────────────────────────────────────────
|
|
382
382
|
async function setupContentfulCredentials() {
|
|
383
383
|
section('Step 5: Contentful credentials', '[optional]');
|
|
384
|
-
info(`Saved to ${
|
|
384
|
+
info(`Saved to ${experiencesCredentialsPath()} — loaded automatically by experiences import.`);
|
|
385
385
|
info('');
|
|
386
|
-
const stored = await
|
|
386
|
+
const stored = await readExperiencesCredentials();
|
|
387
387
|
const currentSpace = stored.spaceId;
|
|
388
388
|
const currentEnv = stored.environmentId;
|
|
389
389
|
const currentToken = stored.cmaToken;
|
|
@@ -417,7 +417,7 @@ async function setupContentfulCredentials() {
|
|
|
417
417
|
ok('Credentials already configured — no changes made');
|
|
418
418
|
}
|
|
419
419
|
else {
|
|
420
|
-
warn('Skipped.
|
|
420
|
+
warn('Skipped. experiences import will prompt for credentials interactively.');
|
|
421
421
|
}
|
|
422
422
|
return true;
|
|
423
423
|
}
|
|
@@ -434,15 +434,15 @@ async function setupContentfulCredentials() {
|
|
|
434
434
|
warn('Space ID and CMA token are required. Skipped.');
|
|
435
435
|
return false;
|
|
436
436
|
}
|
|
437
|
-
await
|
|
438
|
-
ok(`Credentials saved to ${
|
|
439
|
-
info('Run
|
|
437
|
+
await writeExperiencesCredentials({ spaceId, environmentId, cmaToken });
|
|
438
|
+
ok(`Credentials saved to ${experiencesCredentialsPath()}`);
|
|
439
|
+
info('Run experiences import — the credentials step will be pre-filled automatically.');
|
|
440
440
|
return true;
|
|
441
441
|
}
|
|
442
442
|
// ── Step 6: Optional quality-of-life ─────────────────────────────────────────
|
|
443
443
|
async function setupQoL(profilePath) {
|
|
444
444
|
section('Step 6: Optional extras', '[optional]');
|
|
445
|
-
info('These are not required for
|
|
445
|
+
info('These are not required for experiences import but improve the experience.');
|
|
446
446
|
info('');
|
|
447
447
|
// 6a: EDS_EXTRACT_CONCURRENCY
|
|
448
448
|
const hasConcurrency = await profileContains(profilePath, 'EDS_EXTRACT_CONCURRENCY');
|
|
@@ -451,7 +451,7 @@ async function setupQoL(profilePath) {
|
|
|
451
451
|
info('Default is 4. Set higher (e.g. 8) on fast machines to speed up large codebases.');
|
|
452
452
|
const setConcurrency = await confirm('Add EDS_EXTRACT_CONCURRENCY=8 to your profile?', false);
|
|
453
453
|
if (setConcurrency) {
|
|
454
|
-
await appendToProfile(profilePath, '#
|
|
454
|
+
await appendToProfile(profilePath, '# experiences performance\nexport EDS_EXTRACT_CONCURRENCY=8');
|
|
455
455
|
ok(`EDS_EXTRACT_CONCURRENCY=8 written to ${profilePath}`);
|
|
456
456
|
}
|
|
457
457
|
else {
|
|
@@ -595,7 +595,7 @@ async function checkAgent() {
|
|
|
595
595
|
}
|
|
596
596
|
}
|
|
597
597
|
warn('No coding agent found on PATH');
|
|
598
|
-
info('The coding agent is required for the generate steps in
|
|
598
|
+
info('The coding agent is required for the generate steps in experiences import.');
|
|
599
599
|
info('Install one of:');
|
|
600
600
|
info(' • Claude Code: npm install -g @anthropic-ai/claude-code');
|
|
601
601
|
info(' • OpenAI Codex: npm install -g @openai/codex');
|
|
@@ -606,11 +606,11 @@ async function checkAgent() {
|
|
|
606
606
|
export function registerSetupCommand(program) {
|
|
607
607
|
program
|
|
608
608
|
.command('doctor')
|
|
609
|
-
.description('Check prerequisites so
|
|
609
|
+
.description('Check prerequisites so experiences import runs without errors')
|
|
610
610
|
.option('--skip-build', 'Skip the pnpm install + build step (useful if already built)')
|
|
611
611
|
.option('--skip-agent', 'Skip the coding agent check')
|
|
612
612
|
.action(async (opts) => {
|
|
613
|
-
process.stderr.write('\x1b[
|
|
613
|
+
process.stderr.write('\x1b[1mexperiences doctor\x1b[0m — checking your environment\n');
|
|
614
614
|
const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
615
615
|
const results = [];
|
|
616
616
|
const nodeOk = await checkNode();
|
|
@@ -651,30 +651,30 @@ export function registerSetupCommand(program) {
|
|
|
651
651
|
}
|
|
652
652
|
}
|
|
653
653
|
if (requiredFailed.length === 0 && failed.length === 0) {
|
|
654
|
-
process.stderr.write('\n\x1b[32m\x1b[1m✓ All checks passed. You are ready to run:
|
|
654
|
+
process.stderr.write('\n\x1b[32m\x1b[1m✓ All checks passed. You are ready to run: experiences import\x1b[0m\n\n');
|
|
655
655
|
process.exit(0);
|
|
656
656
|
}
|
|
657
657
|
else if (requiredFailed.length === 0) {
|
|
658
658
|
process.stderr.write('\n\x1b[33m\x1b[1m⚠ Required checks passed, but optional checks failed.\x1b[0m\n');
|
|
659
|
-
process.stderr.write(' You can run \x1b[
|
|
659
|
+
process.stderr.write(' You can run \x1b[1mexperiences import\x1b[0m but the generate steps may fail without a coding agent.\n\n');
|
|
660
660
|
process.exit(0);
|
|
661
661
|
}
|
|
662
662
|
else {
|
|
663
663
|
process.stderr.write(`\n\x1b[31m\x1b[1m✗ ${requiredFailed.length} required check${requiredFailed.length === 1 ? '' : 's'} failed.\x1b[0m\n`);
|
|
664
|
-
process.stderr.write(' Fix the issues above, then re-run \x1b[
|
|
664
|
+
process.stderr.write(' Fix the issues above, then re-run \x1b[1mexperiences doctor\x1b[0m.\n\n');
|
|
665
665
|
process.exit(1);
|
|
666
666
|
}
|
|
667
667
|
});
|
|
668
668
|
program
|
|
669
669
|
.command('setup')
|
|
670
|
-
.description('Interactive setup wizard: installs prerequisites and configures credentials for
|
|
670
|
+
.description('Interactive setup wizard: installs prerequisites and configures credentials for experiences import')
|
|
671
671
|
.option('--skip-build', 'Skip the pnpm install + build step')
|
|
672
672
|
.option('--skip-agent', 'Skip the coding agent check')
|
|
673
673
|
.option('--skip-credentials', 'Skip the Contentful credentials step')
|
|
674
674
|
.option('--skip-optional', 'Skip optional quality-of-life extras')
|
|
675
675
|
.action(async (opts) => {
|
|
676
|
-
process.stdout.write('\n\x1b[
|
|
677
|
-
process.stdout.write('Sets up everything you need to run \x1b[
|
|
676
|
+
process.stdout.write('\n\x1b[1mexperiences setup\x1b[0m — interactive setup wizard\n');
|
|
677
|
+
process.stdout.write('Sets up everything you need to run \x1b[1mexperiences import\x1b[0m.\n');
|
|
678
678
|
process.stdout.write('Required steps are marked \x1b[31m[required]\x1b[0m, optional ones \x1b[2m[optional]\x1b[0m.\n');
|
|
679
679
|
const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
680
680
|
const repoRoot = join(pkgRoot, '..', '..');
|
|
@@ -684,7 +684,7 @@ export function registerSetupCommand(program) {
|
|
|
684
684
|
const nodeOk = await setupNode();
|
|
685
685
|
results.push({ name: 'Node.js 24+', passed: nodeOk, required: true });
|
|
686
686
|
if (!nodeOk) {
|
|
687
|
-
process.stdout.write('\n\x1b[33mNode.js setup requires a shell restart. Re-run
|
|
687
|
+
process.stdout.write('\n\x1b[33mNode.js setup requires a shell restart. Re-run experiences setup afterwards.\x1b[0m\n\n');
|
|
688
688
|
process.exit(0);
|
|
689
689
|
}
|
|
690
690
|
// Step 2: pnpm
|
|
@@ -736,19 +736,19 @@ export function registerSetupCommand(program) {
|
|
|
736
736
|
}
|
|
737
737
|
process.stdout.write('\n');
|
|
738
738
|
if (requiredFailed.length === 0) {
|
|
739
|
-
process.stdout.write('\x1b[32m\x1b[1m✓ Setup complete. You can now run:
|
|
739
|
+
process.stdout.write('\x1b[32m\x1b[1m✓ Setup complete. You can now run: experiences import\x1b[0m\n');
|
|
740
740
|
if (optionalFailed.length > 0) {
|
|
741
741
|
process.stdout.write(" (Some optional steps were skipped — that's fine.)\n");
|
|
742
742
|
}
|
|
743
743
|
}
|
|
744
744
|
else {
|
|
745
745
|
process.stdout.write(`\x1b[33m\x1b[1m⚠ ${requiredFailed.length} required step${requiredFailed.length === 1 ? '' : 's'} incomplete.\x1b[0m\n`);
|
|
746
|
-
process.stdout.write(' Complete the steps above, then re-run \x1b[
|
|
746
|
+
process.stdout.write(' Complete the steps above, then re-run \x1b[1mexperiences setup\x1b[0m.\n');
|
|
747
747
|
}
|
|
748
|
-
// ── Offer
|
|
748
|
+
// ── Offer experiences doctor ───────────────────────────────────────────────────
|
|
749
749
|
process.stdout.write('\n');
|
|
750
750
|
const runDoctor = process.stdout.isTTY &&
|
|
751
|
-
(await confirm('Run
|
|
751
|
+
(await confirm('Run experiences doctor now to verify your environment?', requiredFailed.length === 0));
|
|
752
752
|
if (runDoctor) {
|
|
753
753
|
process.stdout.write('\n');
|
|
754
754
|
const cliBin = process.argv[1] ?? fileURLToPath(import.meta.url);
|
|
@@ -759,7 +759,7 @@ export function registerSetupCommand(program) {
|
|
|
759
759
|
process.stderr.write(doctorResult.stderr);
|
|
760
760
|
process.exit(doctorResult.exitCode);
|
|
761
761
|
}
|
|
762
|
-
process.stdout.write('\nRun \x1b[
|
|
762
|
+
process.stdout.write('\nRun \x1b[1mexperiences doctor\x1b[0m at any time to re-check your environment.\n\n');
|
|
763
763
|
process.exit(requiredFailed.length === 0 ? 0 : 1);
|
|
764
764
|
});
|
|
765
765
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.5.2-dev-build-eebccad.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"experience-design-system-cli": "./bin/cli.js",
|
|
8
|
+
"experiences": "./bin/cli.js",
|
|
8
9
|
"exo": "./bin/cli.js"
|
|
9
10
|
},
|
|
10
11
|
"main": "./dist/src/index.js",
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"react-dom": "^18.3.1",
|
|
31
32
|
"ts-morph": "^27.0.2",
|
|
32
33
|
"typescript": "^5.9.3",
|
|
33
|
-
"@contentful/experience-design-system-types": "2.2.
|
|
34
|
+
"@contentful/experience-design-system-types": "2.5.2-dev-build-eebccad.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@tsconfig/node24": "^24.0.3",
|