@contentful/experience-design-system-cli 2.5.3-dev-build-b2e98f1.0 → 2.5.3-dev-build-985b472.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 -2
- package/dist/package.json +5 -1
- package/dist/src/apply/api-client.js +3 -2
- package/dist/src/credentials-store.d.ts +1 -0
- package/dist/src/credentials-store.js +11 -1
- package/dist/src/host-utils.d.ts +4 -0
- package/dist/src/host-utils.js +26 -0
- package/dist/src/import/command.js +2 -0
- package/dist/src/import/tui/WizardApp.d.ts +2 -1
- package/dist/src/import/tui/WizardApp.js +53 -18
- package/dist/src/import/tui/steps/CredentialsStep.d.ts +5 -4
- package/dist/src/import/tui/steps/CredentialsStep.js +23 -9
- package/dist/src/index.js +6 -1
- package/dist/src/setup/command.js +33 -4
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,8 +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 Experiences
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## CLI Overview
|
|
6
6
|
|
|
7
7
|
The commands form a pipeline. Run them in order, or use `import` to orchestrate the whole thing at once:
|
|
8
8
|
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.5.3-dev-build-
|
|
3
|
+
"version": "2.5.3-dev-build-985b472.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"node": "./dist/src/index.js"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://npm.pkg.github.com/"
|
|
24
|
+
},
|
|
21
25
|
"files": [
|
|
22
26
|
"bin/",
|
|
23
27
|
"dist/",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { DEFAULT_API_HOST, toApiHost } from '../host-utils.js';
|
|
2
|
+
export const DEFAULT_HOST = DEFAULT_API_HOST;
|
|
2
3
|
export class ApiError extends Error {
|
|
3
4
|
status;
|
|
4
5
|
body;
|
|
@@ -33,7 +34,7 @@ export class ImportApiClient {
|
|
|
33
34
|
spaceId;
|
|
34
35
|
environmentId;
|
|
35
36
|
constructor(opts) {
|
|
36
|
-
this.host = opts.host
|
|
37
|
+
this.host = toApiHost(opts.host);
|
|
37
38
|
this.token = opts.cmaToken;
|
|
38
39
|
this.spaceId = opts.spaceId;
|
|
39
40
|
this.environmentId = opts.environmentId;
|
|
@@ -2,6 +2,7 @@ export type ExperiencesCredentials = {
|
|
|
2
2
|
spaceId: string;
|
|
3
3
|
environmentId: string;
|
|
4
4
|
cmaToken: string;
|
|
5
|
+
host?: string;
|
|
5
6
|
};
|
|
6
7
|
export declare function readExperiencesCredentials(): Promise<ExperiencesCredentials>;
|
|
7
8
|
export declare function writeExperiencesCredentials(creds: ExperiencesCredentials): Promise<void>;
|
|
@@ -1,29 +1,39 @@
|
|
|
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
|
+
import { toConfiguredHost } from './host-utils.js';
|
|
4
5
|
const CREDENTIALS_DIR = join(homedir(), '.config', 'experiences');
|
|
5
6
|
const CREDENTIALS_PATH = join(CREDENTIALS_DIR, 'credentials.json');
|
|
6
7
|
export async function readExperiencesCredentials() {
|
|
7
8
|
try {
|
|
8
9
|
const raw = await readFile(CREDENTIALS_PATH, 'utf8');
|
|
9
10
|
const parsed = JSON.parse(raw);
|
|
11
|
+
const host = toConfiguredHost(process.env['EDS_HOST'] ?? parsed.host);
|
|
10
12
|
return {
|
|
11
13
|
spaceId: process.env['CONTENTFUL_SPACE_ID'] ?? parsed.spaceId ?? '',
|
|
12
14
|
environmentId: process.env['CONTENTFUL_ENVIRONMENT_ID'] ?? parsed.environmentId ?? '',
|
|
13
15
|
cmaToken: process.env['CONTENTFUL_MANAGEMENT_TOKEN'] ?? parsed.cmaToken ?? '',
|
|
16
|
+
...(host ? { host } : {}),
|
|
14
17
|
};
|
|
15
18
|
}
|
|
16
19
|
catch {
|
|
20
|
+
const host = toConfiguredHost(process.env['EDS_HOST']);
|
|
17
21
|
return {
|
|
18
22
|
spaceId: process.env['CONTENTFUL_SPACE_ID'] ?? '',
|
|
19
23
|
environmentId: process.env['CONTENTFUL_ENVIRONMENT_ID'] ?? '',
|
|
20
24
|
cmaToken: process.env['CONTENTFUL_MANAGEMENT_TOKEN'] ?? '',
|
|
25
|
+
...(host ? { host } : {}),
|
|
21
26
|
};
|
|
22
27
|
}
|
|
23
28
|
}
|
|
24
29
|
export async function writeExperiencesCredentials(creds) {
|
|
30
|
+
const { host: _host, ...rest } = creds;
|
|
31
|
+
const host = toConfiguredHost(creds.host);
|
|
25
32
|
await mkdir(CREDENTIALS_DIR, { recursive: true });
|
|
26
|
-
await writeFile(CREDENTIALS_PATH, JSON.stringify(
|
|
33
|
+
await writeFile(CREDENTIALS_PATH, JSON.stringify({
|
|
34
|
+
...rest,
|
|
35
|
+
...(host ? { host } : {}),
|
|
36
|
+
}, null, 2) + '\n', { mode: 0o600 });
|
|
27
37
|
}
|
|
28
38
|
export function experiencesCredentialsPath() {
|
|
29
39
|
return CREDENTIALS_PATH;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const DEFAULT_API_HOST = "https://api.contentful.com";
|
|
2
|
+
export declare const DEFAULT_CONFIGURED_HOST = "api.contentful.com";
|
|
3
|
+
export declare function toConfiguredHost(host?: string): string | undefined;
|
|
4
|
+
export declare function toApiHost(host?: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const DEFAULT_API_HOST = 'https://api.contentful.com';
|
|
2
|
+
export const DEFAULT_CONFIGURED_HOST = 'api.contentful.com';
|
|
3
|
+
function trimTrailingSlashes(value) {
|
|
4
|
+
return value.replace(/\/+$/, '');
|
|
5
|
+
}
|
|
6
|
+
function normalizeHostInput(host) {
|
|
7
|
+
const value = host?.trim();
|
|
8
|
+
if (!value)
|
|
9
|
+
return undefined;
|
|
10
|
+
return trimTrailingSlashes(value);
|
|
11
|
+
}
|
|
12
|
+
export function toConfiguredHost(host) {
|
|
13
|
+
const normalized = normalizeHostInput(host);
|
|
14
|
+
if (!normalized)
|
|
15
|
+
return undefined;
|
|
16
|
+
return normalized.replace(/^https:\/\//i, '');
|
|
17
|
+
}
|
|
18
|
+
export function toApiHost(host) {
|
|
19
|
+
const normalized = normalizeHostInput(host);
|
|
20
|
+
if (!normalized)
|
|
21
|
+
return DEFAULT_API_HOST;
|
|
22
|
+
if (/^[a-z][a-z\d+\-.]*:\/\//i.test(normalized)) {
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
return `https://${normalized}`;
|
|
26
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolve, join } from 'node:path';
|
|
2
2
|
import { runPipeline } from './orchestrator.js';
|
|
3
3
|
import { readExperiencesCredentials } from '../credentials-store.js';
|
|
4
|
+
import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../host-utils.js';
|
|
4
5
|
export function registerImportCommand(program) {
|
|
5
6
|
program
|
|
6
7
|
.command('import')
|
|
@@ -45,6 +46,7 @@ export function registerImportCommand(program) {
|
|
|
45
46
|
initialSpaceId: creds.spaceId,
|
|
46
47
|
initialEnvironmentId: creds.environmentId || 'master',
|
|
47
48
|
initialCmaToken: creds.cmaToken,
|
|
49
|
+
initialHost: toConfiguredHost(opts.host ?? creds.host) ?? DEFAULT_CONFIGURED_HOST,
|
|
48
50
|
initialAgent: opts.agent !== 'claude' ? opts.agent : undefined,
|
|
49
51
|
initialProjectPath: opts.project !== '.' ? resolve(opts.project) : undefined,
|
|
50
52
|
host: opts.host,
|
|
@@ -3,8 +3,9 @@ export type WizardAppProps = {
|
|
|
3
3
|
initialSpaceId?: string;
|
|
4
4
|
initialEnvironmentId?: string;
|
|
5
5
|
initialCmaToken?: string;
|
|
6
|
+
initialHost?: string;
|
|
6
7
|
initialAgent?: string;
|
|
7
8
|
initialProjectPath?: string;
|
|
8
9
|
host?: string;
|
|
9
10
|
};
|
|
10
|
-
export declare function WizardApp({ initialSpaceId, initialEnvironmentId, initialCmaToken, initialAgent, initialProjectPath, host, }?: WizardAppProps): React.ReactElement;
|
|
11
|
+
export declare function WizardApp({ initialSpaceId, initialEnvironmentId, initialCmaToken, initialHost, initialAgent, initialProjectPath, host, }?: WizardAppProps): React.ReactElement;
|
|
@@ -24,6 +24,8 @@ import { buildManifest } from '@contentful/experience-design-system-types';
|
|
|
24
24
|
import { openPipelineDb, loadCDFComponents, seedCDFFromPreviewResponse, seedDefaultsFromChangedItems, backfillUnclassifiedProps, } from '../../session/db.js';
|
|
25
25
|
import { checkAgentAuth } from '../../generate/agent-runner.js';
|
|
26
26
|
import { normalizePath } from '../path-utils.js';
|
|
27
|
+
import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../../host-utils.js';
|
|
28
|
+
import { writeExperiencesCredentials } from '../../credentials-store.js';
|
|
27
29
|
function findCliPath() {
|
|
28
30
|
return join(fileURLToPath(import.meta.url), '..', '..', '..', '..', '..', 'bin', 'cli.js');
|
|
29
31
|
}
|
|
@@ -39,8 +41,9 @@ function logStep(entry) {
|
|
|
39
41
|
const line = JSON.stringify({ ts: new Date().toISOString(), ...entry }) + '\n';
|
|
40
42
|
appendFileSync(WIZARD_LOG, line);
|
|
41
43
|
}
|
|
42
|
-
export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialAgent, initialProjectPath, host, } = {}) {
|
|
43
|
-
const
|
|
44
|
+
export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialProjectPath, host, } = {}) {
|
|
45
|
+
const defaultConfiguredHost = toConfiguredHost(host || process.env['EDS_HOST']) ?? DEFAULT_CONFIGURED_HOST;
|
|
46
|
+
const resolveWizardHost = (hostValue) => hostValue || defaultConfiguredHost;
|
|
44
47
|
const { stdout } = useStdout();
|
|
45
48
|
const terminalWidth = stdout?.columns ?? 80;
|
|
46
49
|
const logInit = useRef(false);
|
|
@@ -75,6 +78,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
75
78
|
spaceId: initialSpaceId,
|
|
76
79
|
environmentId: initialEnvironmentId,
|
|
77
80
|
cmaToken: initialCmaToken,
|
|
81
|
+
host: resolveWizardHost(toConfiguredHost(initialHost)),
|
|
78
82
|
credentialsError: '',
|
|
79
83
|
serverPreview: null,
|
|
80
84
|
manifest: null,
|
|
@@ -375,7 +379,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
375
379
|
}
|
|
376
380
|
if (returnToPreview) {
|
|
377
381
|
const { extractSessionId, tokensPath } = sessionRef.current;
|
|
378
|
-
void runPreview(extractSessionId, tokensPath, state.spaceId, state.environmentId, state.cmaToken);
|
|
382
|
+
void runPreview(extractSessionId, tokensPath, state.spaceId, state.environmentId, state.cmaToken, state.host);
|
|
379
383
|
}
|
|
380
384
|
else {
|
|
381
385
|
advanceToPushFlow(generatedAcceptedCount);
|
|
@@ -431,19 +435,46 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
431
435
|
}
|
|
432
436
|
// Re-preview with updated definitions
|
|
433
437
|
const { extractSessionId: sid, tokensPath: tp } = sessionRef.current;
|
|
434
|
-
void runPreview(sid, tp, state.spaceId, state.environmentId, state.cmaToken);
|
|
438
|
+
void runPreview(sid, tp, state.spaceId, state.environmentId, state.cmaToken, state.host);
|
|
435
439
|
};
|
|
436
|
-
const
|
|
440
|
+
const advanceWithCredentials = (spaceId, environmentId, cmaToken, host) => {
|
|
441
|
+
const resolvedHost = resolveWizardHost(host);
|
|
437
442
|
credentialsRef.current = { spaceId, environmentId, cmaToken };
|
|
438
|
-
update({
|
|
443
|
+
update({
|
|
444
|
+
spaceId,
|
|
445
|
+
environmentId,
|
|
446
|
+
cmaToken,
|
|
447
|
+
host: resolvedHost,
|
|
448
|
+
credentialsError: '',
|
|
449
|
+
step: 'credential-test-gate',
|
|
450
|
+
});
|
|
451
|
+
};
|
|
452
|
+
const confirmCredentials = async (spaceId, environmentId, cmaToken, host) => {
|
|
453
|
+
const resolvedHost = resolveWizardHost(host);
|
|
454
|
+
try {
|
|
455
|
+
await writeExperiencesCredentials({ spaceId, environmentId, cmaToken, host: resolvedHost });
|
|
456
|
+
advanceWithCredentials(spaceId, environmentId, cmaToken, resolvedHost);
|
|
457
|
+
}
|
|
458
|
+
catch (e) {
|
|
459
|
+
const message = e instanceof Error ? e.message : 'Unable to save credentials';
|
|
460
|
+
update({
|
|
461
|
+
spaceId,
|
|
462
|
+
environmentId,
|
|
463
|
+
cmaToken,
|
|
464
|
+
host: resolvedHost,
|
|
465
|
+
credentialsError: `Failed to save credentials: ${message}`,
|
|
466
|
+
step: 'credentials',
|
|
467
|
+
});
|
|
468
|
+
}
|
|
439
469
|
};
|
|
440
|
-
const validateCredentials = async (spaceId, environmentId, cmaToken) => {
|
|
470
|
+
const validateCredentials = async (spaceId, environmentId, cmaToken, host) => {
|
|
441
471
|
update({ step: 'validating-credentials' });
|
|
442
472
|
try {
|
|
443
|
-
const
|
|
473
|
+
const resolvedHost = resolveWizardHost(host);
|
|
474
|
+
const client = new ImportApiClient({ cmaToken, spaceId, environmentId, host: resolvedHost });
|
|
444
475
|
await client.validateToken();
|
|
445
476
|
const { extractSessionId, tokensPath } = sessionRef.current;
|
|
446
|
-
void runPreview(extractSessionId, tokensPath, spaceId, environmentId, cmaToken);
|
|
477
|
+
void runPreview(extractSessionId, tokensPath, spaceId, environmentId, cmaToken, resolvedHost);
|
|
447
478
|
}
|
|
448
479
|
catch (e) {
|
|
449
480
|
if (e instanceof ApiError && (e.status === 401 || e.status === 403)) {
|
|
@@ -459,10 +490,11 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
459
490
|
});
|
|
460
491
|
}
|
|
461
492
|
};
|
|
462
|
-
const runPreview = async (extractSessionId, tokensPath, spaceId, environmentId, cmaToken) => {
|
|
493
|
+
const runPreview = async (extractSessionId, tokensPath, spaceId, environmentId, cmaToken, host) => {
|
|
463
494
|
update({ step: 'previewing' });
|
|
495
|
+
const resolvedHost = resolveWizardHost(host);
|
|
464
496
|
try {
|
|
465
|
-
const client = new ImportApiClient({ cmaToken, spaceId, environmentId, host:
|
|
497
|
+
const client = new ImportApiClient({ cmaToken, spaceId, environmentId, host: resolvedHost });
|
|
466
498
|
let components = [];
|
|
467
499
|
if (extractSessionId) {
|
|
468
500
|
const db = openPipelineDb();
|
|
@@ -548,7 +580,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
548
580
|
errorMessage: `Not found (404). Check that the space ID, environment ID, and host are correct.\n\n` +
|
|
549
581
|
` Space: ${spaceId}\n` +
|
|
550
582
|
` Environment: ${environmentId}\n` +
|
|
551
|
-
(
|
|
583
|
+
(resolvedHost ? ` Host: ${resolvedHost}\n` : '') +
|
|
552
584
|
`\nIf using a custom --host, make sure the space exists on that host.`,
|
|
553
585
|
});
|
|
554
586
|
return;
|
|
@@ -560,7 +592,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
560
592
|
update({ step: 'error', errorStep: 'apply preview', errorMessage: msg, errorAllowCredentialRetry: true });
|
|
561
593
|
}
|
|
562
594
|
};
|
|
563
|
-
const runPush = async (manifest, spaceId, environmentId, cmaToken, acknowledgeBreakingChanges, preview) => {
|
|
595
|
+
const runPush = async (manifest, spaceId, environmentId, cmaToken, host, acknowledgeBreakingChanges, preview) => {
|
|
564
596
|
if (preview) {
|
|
565
597
|
const hasComponentChanges = preview.components.new.length > 0 ||
|
|
566
598
|
preview.components.changed.length > 0 ||
|
|
@@ -579,7 +611,8 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
579
611
|
}
|
|
580
612
|
update({ step: 'pushing' });
|
|
581
613
|
try {
|
|
582
|
-
const
|
|
614
|
+
const resolvedHost = resolveWizardHost(host);
|
|
615
|
+
const client = new ImportApiClient({ cmaToken, spaceId, environmentId, host: resolvedHost });
|
|
583
616
|
let operation = await client.applyImport(manifest, acknowledgeBreakingChanges);
|
|
584
617
|
try {
|
|
585
618
|
logStep({
|
|
@@ -835,13 +868,15 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
835
868
|
}, onQuit: () => process.exit(0) }));
|
|
836
869
|
}
|
|
837
870
|
case 'credentials':
|
|
838
|
-
return (_jsx(CredentialsStep, { initialSpaceId: state.spaceId, initialEnvironmentId: state.environmentId, initialCmaToken: state.cmaToken, error: state.credentialsError || undefined, onConfirm:
|
|
871
|
+
return (_jsx(CredentialsStep, { initialSpaceId: state.spaceId, initialEnvironmentId: state.environmentId, initialCmaToken: state.cmaToken, initialHost: state.host, error: state.credentialsError || undefined, onConfirm: (spaceId, environmentId, cmaToken, host) => {
|
|
872
|
+
void confirmCredentials(spaceId, environmentId, cmaToken, host);
|
|
873
|
+
}, onContinue: advanceWithCredentials, onQuit: () => process.exit(0) }));
|
|
839
874
|
case 'credential-test-gate':
|
|
840
875
|
return (_jsx(GateStep, { successMessage: "Credentials entered", summary: `Space: ${state.spaceId} · Environment: ${state.environmentId}`, context: "Verify your credentials work before running the import, or skip and find out during the push step.", continueLabel: "Test credentials", skipLabel: "Skip and continue", showSkip: true, onContinue: () => {
|
|
841
|
-
void validateCredentials(state.spaceId, state.environmentId, state.cmaToken);
|
|
876
|
+
void validateCredentials(state.spaceId, state.environmentId, state.cmaToken, state.host);
|
|
842
877
|
}, onSkip: () => {
|
|
843
878
|
const { extractSessionId, tokensPath } = sessionRef.current;
|
|
844
|
-
void runPreview(extractSessionId, tokensPath, state.spaceId, state.environmentId, state.cmaToken);
|
|
879
|
+
void runPreview(extractSessionId, tokensPath, state.spaceId, state.environmentId, state.cmaToken, state.host);
|
|
845
880
|
}, onQuit: () => process.exit(0) }));
|
|
846
881
|
case 'validating-credentials':
|
|
847
882
|
return (_jsx(RunningStep, { stepNumber: totalSteps - 1, totalSteps: totalSteps, title: "Validating credentials", description: "Checking that your space ID and CMA token are valid..." }));
|
|
@@ -849,7 +884,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
849
884
|
return (_jsx(RunningStep, { stepNumber: totalSteps, totalSteps: totalSteps, title: "Computing diff", description: "Computing diff against your Contentful space..." }));
|
|
850
885
|
case 'preview-gate':
|
|
851
886
|
return (_jsx(WizardPreviewStep, { preview: state.serverPreview, spaceId: state.spaceId, environmentId: state.environmentId, stepNumber: totalSteps, totalSteps: totalSteps, onConfirm: (acknowledge) => {
|
|
852
|
-
void runPush(state.manifest, state.spaceId, state.environmentId, state.cmaToken, acknowledge, state.serverPreview);
|
|
887
|
+
void runPush(state.manifest, state.spaceId, state.environmentId, state.cmaToken, state.host, acknowledge, state.serverPreview);
|
|
853
888
|
}, onEdit: () => {
|
|
854
889
|
void runEditFromPreview(state.serverPreview);
|
|
855
890
|
}, onSaveFiles: () => {
|
|
@@ -5,11 +5,12 @@ type CredentialsStepProps = {
|
|
|
5
5
|
initialSpaceId?: string;
|
|
6
6
|
initialEnvironmentId?: string;
|
|
7
7
|
initialCmaToken?: string;
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
initialHost?: string;
|
|
9
|
+
/** Called when the user submits with any field changed from its initial value */
|
|
10
|
+
onConfirm: (spaceId: string, environmentId: string, cmaToken: string, host: string) => void;
|
|
10
11
|
/** Called when the user submits without changing any field (use existing creds as-is) */
|
|
11
|
-
onContinue?: (spaceId: string, environmentId: string, cmaToken: string) => void;
|
|
12
|
+
onContinue?: (spaceId: string, environmentId: string, cmaToken: string, host: string) => void;
|
|
12
13
|
onQuit: () => void;
|
|
13
14
|
};
|
|
14
|
-
export declare function CredentialsStep({ summary, error: externalError, initialSpaceId, initialEnvironmentId, initialCmaToken, onConfirm, onContinue, onQuit, }: CredentialsStepProps): React.ReactElement;
|
|
15
|
+
export declare function CredentialsStep({ summary, error: externalError, initialSpaceId, initialEnvironmentId, initialCmaToken, initialHost, onConfirm, onContinue, onQuit, }: CredentialsStepProps): React.ReactElement;
|
|
15
16
|
export {};
|
|
@@ -2,10 +2,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
|
|
5
|
-
|
|
5
|
+
import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../../../host-utils.js';
|
|
6
|
+
export function CredentialsStep({ summary, error: externalError, initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, onConfirm, onContinue, onQuit, }) {
|
|
7
|
+
const normalizedInitialHost = toConfiguredHost(initialHost) ?? DEFAULT_CONFIGURED_HOST;
|
|
6
8
|
const [spaceId, setSpaceId] = useState(initialSpaceId);
|
|
7
9
|
const [environmentId, setEnvironmentId] = useState(initialEnvironmentId);
|
|
8
10
|
const [cmaToken, setCmaToken] = useState(initialCmaToken);
|
|
11
|
+
const [host, setHost] = useState(normalizedInitialHost);
|
|
9
12
|
const [activeField, setActiveField] = useState('spaceId');
|
|
10
13
|
const [inlineError, setInlineError] = useState(null);
|
|
11
14
|
const [cursorVisible, setCursorVisible] = useState(true);
|
|
@@ -23,25 +26,31 @@ export function CredentialsStep({ summary, error: externalError, initialSpaceId
|
|
|
23
26
|
setActiveField('cmaToken');
|
|
24
27
|
return;
|
|
25
28
|
}
|
|
29
|
+
if (activeField === 'cmaToken') {
|
|
30
|
+
setActiveField('host');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
26
33
|
// Submit
|
|
27
34
|
if (!spaceId.trim() || !environmentId.trim() || !cmaToken.trim()) {
|
|
28
35
|
setInlineError('All fields are required.');
|
|
29
36
|
return;
|
|
30
37
|
}
|
|
31
38
|
setInlineError(null);
|
|
39
|
+
const submittedHost = toConfiguredHost(host) ?? DEFAULT_CONFIGURED_HOST;
|
|
32
40
|
const unchanged = spaceId.trim() === initialSpaceId &&
|
|
33
41
|
environmentId.trim() === initialEnvironmentId &&
|
|
34
|
-
cmaToken.trim() === initialCmaToken
|
|
42
|
+
cmaToken.trim() === initialCmaToken &&
|
|
43
|
+
submittedHost === normalizedInitialHost;
|
|
35
44
|
if (unchanged && onContinue) {
|
|
36
|
-
onContinue(spaceId.trim(), environmentId.trim(), cmaToken.trim());
|
|
45
|
+
onContinue(spaceId.trim(), environmentId.trim(), cmaToken.trim(), submittedHost);
|
|
37
46
|
}
|
|
38
47
|
else {
|
|
39
|
-
onConfirm(spaceId.trim(), environmentId.trim(), cmaToken.trim());
|
|
48
|
+
onConfirm(spaceId.trim(), environmentId.trim(), cmaToken.trim(), submittedHost);
|
|
40
49
|
}
|
|
41
50
|
return;
|
|
42
51
|
}
|
|
43
52
|
if (key.tab) {
|
|
44
|
-
setActiveField((f) =>
|
|
53
|
+
setActiveField((f) => f === 'spaceId' ? 'environmentId' : f === 'environmentId' ? 'cmaToken' : f === 'cmaToken' ? 'host' : 'spaceId');
|
|
45
54
|
return;
|
|
46
55
|
}
|
|
47
56
|
if (key.escape || input === 'q') {
|
|
@@ -53,8 +62,10 @@ export function CredentialsStep({ summary, error: externalError, initialSpaceId
|
|
|
53
62
|
setSpaceId((v) => v.slice(0, -1));
|
|
54
63
|
else if (activeField === 'environmentId')
|
|
55
64
|
setEnvironmentId((v) => v.slice(0, -1));
|
|
56
|
-
else
|
|
65
|
+
else if (activeField === 'cmaToken')
|
|
57
66
|
setCmaToken((v) => v.slice(0, -1));
|
|
67
|
+
else
|
|
68
|
+
setHost((v) => v.slice(0, -1));
|
|
58
69
|
return;
|
|
59
70
|
}
|
|
60
71
|
if (input && !key.ctrl && !key.meta) {
|
|
@@ -62,18 +73,21 @@ export function CredentialsStep({ summary, error: externalError, initialSpaceId
|
|
|
62
73
|
setSpaceId((v) => v + input);
|
|
63
74
|
else if (activeField === 'environmentId')
|
|
64
75
|
setEnvironmentId((v) => v + input);
|
|
65
|
-
else
|
|
76
|
+
else if (activeField === 'cmaToken')
|
|
66
77
|
setCmaToken((v) => v + input);
|
|
78
|
+
else
|
|
79
|
+
setHost((v) => v + input);
|
|
67
80
|
}
|
|
68
81
|
});
|
|
69
82
|
const cursor = cursorVisible ? '█' : ' ';
|
|
70
83
|
function renderField(label, value, field, masked = false) {
|
|
71
84
|
const isActive = activeField === field;
|
|
72
85
|
const display = masked ? '•'.repeat(value.length) : value;
|
|
73
|
-
|
|
86
|
+
const fallback = field === 'host' ? DEFAULT_CONFIGURED_HOST : _jsx(Text, { dimColor: true, children: "(empty)" });
|
|
87
|
+
return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: isActive ? 'cyan' : undefined, children: '?' }), _jsxs(Text, { bold: isActive, children: [label, ":"] }), _jsx(Text, { children: isActive ? display + cursor : display || fallback })] }));
|
|
74
88
|
}
|
|
75
89
|
const displayError = inlineError ?? externalError ?? null;
|
|
76
90
|
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
91
|
? '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" })] })] }));
|
|
92
|
+
: '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), renderField('API Host', host, 'host')] }), activeField === 'host' && _jsx(Text, { dimColor: true, children: "Default: api.contentful.com \u00B7 EU spaces: api.eu.contentful.com" }), 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
93
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { createInterface } from 'node:readline';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import { readExperiencesCredentials, writeExperiencesCredentials, experiencesCredentialsPath, } from '../credentials-store.js';
|
|
9
|
+
import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../host-utils.js';
|
|
9
10
|
const execFileAsync = promisify(execFile);
|
|
10
11
|
const REQUIRED_NODE_MAJOR = 24;
|
|
11
12
|
// ── Output helpers ────────────────────────────────────────────────────────────
|
|
@@ -29,7 +30,13 @@ function dim(msg) {
|
|
|
29
30
|
process.stdout.write(`\x1b[2m${msg}\x1b[0m\n`);
|
|
30
31
|
}
|
|
31
32
|
// ── Prompt helpers ────────────────────────────────────────────────────────────
|
|
33
|
+
function isInteractivePromptSession() {
|
|
34
|
+
return !!(process.stdin.isTTY && process.stdout.isTTY);
|
|
35
|
+
}
|
|
32
36
|
function prompt(question) {
|
|
37
|
+
if (!isInteractivePromptSession()) {
|
|
38
|
+
return Promise.resolve('');
|
|
39
|
+
}
|
|
33
40
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
34
41
|
return new Promise((resolve) => {
|
|
35
42
|
rl.question(question, (answer) => {
|
|
@@ -39,6 +46,9 @@ function prompt(question) {
|
|
|
39
46
|
});
|
|
40
47
|
}
|
|
41
48
|
function promptSecret(question) {
|
|
49
|
+
if (!isInteractivePromptSession()) {
|
|
50
|
+
return Promise.resolve('');
|
|
51
|
+
}
|
|
42
52
|
// Use readline for all prompts — mixing raw-mode stdin listeners with
|
|
43
53
|
// readline createInterface causes readline to buffer+unshift unconsumed
|
|
44
54
|
// input back onto the stream, which the raw listener then re-reads,
|
|
@@ -51,9 +61,10 @@ function promptSecret(question) {
|
|
|
51
61
|
});
|
|
52
62
|
let value = '';
|
|
53
63
|
process.stdout.write(question);
|
|
64
|
+
let origWrite = null;
|
|
54
65
|
if (process.stdin.isTTY) {
|
|
55
66
|
// Intercept the readline output write so we can replace echoed chars with *
|
|
56
|
-
|
|
67
|
+
origWrite = rl.output.write.bind(rl.output);
|
|
57
68
|
rl.output.write = (s) => {
|
|
58
69
|
// Allow newline through; suppress everything else (the echoed characters)
|
|
59
70
|
if (s === '\r\n' || s === '\n' || s === '\r')
|
|
@@ -65,12 +76,24 @@ function promptSecret(question) {
|
|
|
65
76
|
rl.close();
|
|
66
77
|
});
|
|
67
78
|
rl.once('close', () => {
|
|
68
|
-
|
|
79
|
+
// Restore stdout.write before resolving — the interceptor patches rl.output.write
|
|
80
|
+
// which is process.stdout.write, so without restoring it all subsequent output is swallowed.
|
|
81
|
+
if (origWrite) {
|
|
82
|
+
rl.output.write = origWrite;
|
|
83
|
+
}
|
|
84
|
+
// In TTY mode readline already emitted \n when Enter was pressed; only add one in non-TTY.
|
|
85
|
+
if (!process.stdin.isTTY)
|
|
86
|
+
process.stdout.write('\n');
|
|
87
|
+
// rl.close() pauses stdin; resume it so subsequent prompt() calls work.
|
|
88
|
+
process.stdin.resume();
|
|
69
89
|
resolve(value);
|
|
70
90
|
});
|
|
71
91
|
});
|
|
72
92
|
}
|
|
73
93
|
async function confirm(question, defaultYes = true) {
|
|
94
|
+
if (!isInteractivePromptSession()) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
74
97
|
const hint = defaultYes ? '[Y/n]' : '[y/N]';
|
|
75
98
|
const answer = await prompt(` ${question} ${hint} `);
|
|
76
99
|
if (!answer)
|
|
@@ -387,6 +410,8 @@ async function setupContentfulCredentials() {
|
|
|
387
410
|
const currentSpace = stored.spaceId;
|
|
388
411
|
const currentEnv = stored.environmentId;
|
|
389
412
|
const currentToken = stored.cmaToken;
|
|
413
|
+
const storedHost = stored.host;
|
|
414
|
+
const currentHost = storedHost ?? DEFAULT_CONFIGURED_HOST;
|
|
390
415
|
const hasAny = !!(currentSpace || currentEnv || currentToken);
|
|
391
416
|
if (hasAny) {
|
|
392
417
|
info('Current values:');
|
|
@@ -408,6 +433,7 @@ async function setupContentfulCredentials() {
|
|
|
408
433
|
else {
|
|
409
434
|
warn('CMA Token (not set)');
|
|
410
435
|
}
|
|
436
|
+
ok(`API Host ${currentHost}`);
|
|
411
437
|
info('');
|
|
412
438
|
}
|
|
413
439
|
const allSet = !!(currentSpace && currentEnv && currentToken);
|
|
@@ -434,9 +460,12 @@ async function setupContentfulCredentials() {
|
|
|
434
460
|
warn('Space ID and CMA token are required. Skipped.');
|
|
435
461
|
return false;
|
|
436
462
|
}
|
|
437
|
-
await
|
|
463
|
+
const hostInput = await prompt(` API host [${currentHost}]: `);
|
|
464
|
+
const host = toConfiguredHost(hostInput) ?? storedHost;
|
|
465
|
+
await writeExperiencesCredentials({ spaceId, environmentId, cmaToken, ...(host ? { host } : {}) });
|
|
438
466
|
ok(`Credentials saved to ${experiencesCredentialsPath()}`);
|
|
439
|
-
|
|
467
|
+
ok(`API host set to ${host ?? DEFAULT_CONFIGURED_HOST}`);
|
|
468
|
+
info('Run experiences import — credentials will be pre-filled automatically.');
|
|
440
469
|
return true;
|
|
441
470
|
}
|
|
442
471
|
// ── Step 6: Optional quality-of-life ─────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.5.3-dev-build-
|
|
3
|
+
"version": "2.5.3-dev-build-985b472.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"node": "./dist/src/index.js"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://npm.pkg.github.com/"
|
|
24
|
+
},
|
|
21
25
|
"files": [
|
|
22
26
|
"bin/",
|
|
23
27
|
"dist/",
|
|
@@ -32,7 +36,7 @@
|
|
|
32
36
|
"react-dom": "^18.3.1",
|
|
33
37
|
"ts-morph": "^27.0.2",
|
|
34
38
|
"typescript": "^5.9.3",
|
|
35
|
-
"@contentful/experience-design-system-types": "2.5.3-dev-build-
|
|
39
|
+
"@contentful/experience-design-system-types": "2.5.3-dev-build-985b472.0"
|
|
36
40
|
},
|
|
37
41
|
"devDependencies": {
|
|
38
42
|
"@tsconfig/node24": "^24.0.3",
|