@contentful/experience-design-system-cli 2.12.0 → 2.12.1-dev-build-589b615.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 +214 -357
- package/dist/package.json +1 -1
- package/dist/src/analyze/extract/astro.js +9 -0
- package/dist/src/analyze/extract/react.js +23 -0
- package/dist/src/analyze/extract/stencil.js +3 -0
- package/dist/src/analyze/extract/vue.js +6 -0
- package/dist/src/analyze/extract/web-components.js +10 -0
- package/dist/src/analyze/pre-classify.d.ts +1 -1
- package/dist/src/analyze/pre-classify.js +3 -3
- package/dist/src/analyze/select/persistence.d.ts +13 -0
- package/dist/src/analyze/select/persistence.js +38 -0
- package/dist/src/analyze/select/preview-annotations.d.ts +26 -0
- package/dist/src/analyze/select/preview-annotations.js +62 -0
- package/dist/src/analyze/select/tui/App.js +5 -2
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.d.ts +30 -0
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +137 -0
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +58 -1
- package/dist/src/analyze/select/tui/components/FieldEditor.js +637 -161
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +1 -1
- package/dist/src/analyze/select/tui/components/JsonPanel.js +5 -1
- package/dist/src/analyze/select/tui/components/RationalePanel.d.ts +34 -0
- package/dist/src/analyze/select/tui/components/RationalePanel.js +92 -0
- package/dist/src/analyze/select/tui/components/Sidebar.d.ts +13 -1
- package/dist/src/analyze/select/tui/components/Sidebar.js +27 -3
- package/dist/src/analyze/select/tui/hooks/scroll-offset.d.ts +29 -0
- package/dist/src/analyze/select/tui/hooks/scroll-offset.js +26 -0
- package/dist/src/analyze/select-agent/command.d.ts +2 -0
- package/dist/src/analyze/select-agent/command.js +259 -45
- package/dist/src/analyze/select-agent/show-rationale.d.ts +35 -0
- package/dist/src/analyze/select-agent/show-rationale.js +92 -0
- package/dist/src/apply/command.js +7 -3
- package/dist/src/apply/tui/ServerApplyView.d.ts +3 -1
- package/dist/src/apply/tui/ServerApplyView.js +3 -2
- package/dist/src/credentials-store.d.ts +5 -0
- package/dist/src/credentials-store.js +7 -1
- package/dist/src/generate/agent-runner.d.ts +14 -0
- package/dist/src/generate/agent-runner.js +16 -0
- package/dist/src/generate/command.d.ts +6 -0
- package/dist/src/generate/command.js +45 -10
- package/dist/src/generate/progress.d.ts +9 -0
- package/dist/src/generate/progress.js +11 -0
- package/dist/src/generate/prompt-builder.d.ts +8 -0
- package/dist/src/generate/prompt-builder.js +22 -11
- package/dist/src/import/agent-model-resolve.d.ts +23 -0
- package/dist/src/import/agent-model-resolve.js +35 -0
- package/dist/src/import/auto-filter-resolve.d.ts +12 -0
- package/dist/src/import/auto-filter-resolve.js +16 -0
- package/dist/src/import/command.js +248 -8
- package/dist/src/import/orchestrator.d.ts +2 -0
- package/dist/src/import/orchestrator.js +11 -3
- package/dist/src/import/print-prompt.d.ts +35 -0
- package/dist/src/import/print-prompt.js +31 -0
- package/dist/src/import/tui/CustomPromptBanner.d.ts +11 -0
- package/dist/src/import/tui/CustomPromptBanner.js +7 -0
- package/dist/src/import/tui/WizardApp.d.ts +112 -8
- package/dist/src/import/tui/WizardApp.js +814 -333
- package/dist/src/import/tui/auto-filter-error.d.ts +2 -0
- package/dist/src/import/tui/auto-filter-error.js +29 -0
- package/dist/src/import/tui/final-review-host.d.ts +15 -0
- package/dist/src/import/tui/final-review-host.js +20 -0
- package/dist/src/import/tui/push-decision-gate-helpers.d.ts +8 -0
- package/dist/src/import/tui/push-decision-gate-helpers.js +8 -0
- package/dist/src/import/tui/push-progress.d.ts +23 -0
- package/dist/src/import/tui/push-progress.js +14 -0
- package/dist/src/import/tui/run-print-files-helpers.d.ts +26 -0
- package/dist/src/import/tui/run-print-files-helpers.js +8 -0
- package/dist/src/import/tui/run-teaser.d.ts +5 -0
- package/dist/src/import/tui/run-teaser.js +9 -0
- package/dist/src/import/tui/runLivePreview.d.ts +35 -0
- package/dist/src/import/tui/runLivePreview.js +73 -0
- package/dist/src/import/tui/runScopeGate.d.ts +22 -0
- package/dist/src/import/tui/runScopeGate.js +28 -0
- package/dist/src/import/tui/scope-gate-host.d.ts +21 -0
- package/dist/src/import/tui/scope-gate-host.js +20 -0
- package/dist/src/import/tui/spawn-generate.d.ts +33 -0
- package/dist/src/import/tui/spawn-generate.js +53 -0
- package/dist/src/import/tui/steps/CredentialsStep.d.ts +25 -1
- package/dist/src/import/tui/steps/CredentialsStep.js +31 -3
- package/dist/src/import/tui/steps/DoneStep.d.ts +6 -1
- package/dist/src/import/tui/steps/DoneStep.js +10 -4
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +29 -2
- package/dist/src/import/tui/steps/GenerateReviewStep.js +412 -48
- package/dist/src/import/tui/steps/PushDecisionGateStep.d.ts +19 -0
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +76 -0
- package/dist/src/import/tui/steps/PushingStep.d.ts +10 -0
- package/dist/src/import/tui/steps/PushingStep.js +40 -0
- package/dist/src/import/tui/steps/ScopeGateStep.d.ts +23 -0
- package/dist/src/import/tui/steps/ScopeGateStep.js +243 -0
- package/dist/src/import/tui/steps/TokenInputStep.js +20 -3
- package/dist/src/import/tui/useLivePreview.d.ts +35 -0
- package/dist/src/import/tui/useLivePreview.js +120 -0
- package/dist/src/import/tui/wizard-generate-progress.d.ts +21 -0
- package/dist/src/import/tui/wizard-generate-progress.js +25 -0
- package/dist/src/import/tui/wizard-save-flow.d.ts +37 -0
- package/dist/src/import/tui/wizard-save-flow.js +20 -0
- package/dist/src/import/tui/wizard-state-transitions.d.ts +80 -0
- package/dist/src/import/tui/wizard-state-transitions.js +79 -0
- package/dist/src/lib/contentful-urls.d.ts +34 -0
- package/dist/src/lib/contentful-urls.js +41 -0
- package/dist/src/program.js +2 -0
- package/dist/src/runs/export-helpers.d.ts +30 -0
- package/dist/src/runs/export-helpers.js +59 -0
- package/dist/src/runs/fingerprint.d.ts +47 -0
- package/dist/src/runs/fingerprint.js +75 -0
- package/dist/src/runs/ls-command.d.ts +12 -0
- package/dist/src/runs/ls-command.js +137 -0
- package/dist/src/runs/modify-launcher.d.ts +24 -0
- package/dist/src/runs/modify-launcher.js +36 -0
- package/dist/src/runs/path-prompt.d.ts +8 -0
- package/dist/src/runs/path-prompt.js +72 -0
- package/dist/src/runs/push-creds-prompt.d.ts +25 -0
- package/dist/src/runs/push-creds-prompt.js +39 -0
- package/dist/src/runs/push-helpers.d.ts +29 -0
- package/dist/src/runs/push-helpers.js +69 -0
- package/dist/src/runs/replay-helpers.d.ts +67 -0
- package/dist/src/runs/replay-helpers.js +148 -0
- package/dist/src/runs/resolve-run-target.d.ts +12 -0
- package/dist/src/runs/resolve-run-target.js +45 -0
- package/dist/src/runs/run-picker-mount.d.ts +46 -0
- package/dist/src/runs/run-picker-mount.js +72 -0
- package/dist/src/runs/run-picker.d.ts +17 -0
- package/dist/src/runs/run-picker.js +145 -0
- package/dist/src/runs/save-conflict.d.ts +8 -0
- package/dist/src/runs/save-conflict.js +56 -0
- package/dist/src/runs/save-path-resolver.d.ts +55 -0
- package/dist/src/runs/save-path-resolver.js +71 -0
- package/dist/src/runs/staleness.d.ts +38 -0
- package/dist/src/runs/staleness.js +140 -0
- package/dist/src/runs/store.d.ts +78 -0
- package/dist/src/runs/store.js +136 -0
- package/dist/src/session/cache-keys.d.ts +15 -0
- package/dist/src/session/cache-keys.js +38 -0
- package/dist/src/session/db.d.ts +89 -3
- package/dist/src/session/db.js +447 -104
- package/dist/src/setup/auto-filter-prompt.d.ts +13 -0
- package/dist/src/setup/auto-filter-prompt.js +24 -0
- package/dist/src/setup/command.d.ts +7 -0
- package/dist/src/setup/command.js +58 -2
- package/dist/src/types.d.ts +6 -0
- package/package.json +2 -2
- package/skills/generate-components.md +54 -6
- package/skills/select-components.md +13 -4
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type RunRecord } from '../../runs/store.js';
|
|
2
|
+
import { type OnConflictMode } from '../../runs/save-path-resolver.js';
|
|
3
|
+
/**
|
|
4
|
+
* Wizard save-flow orchestration helpers (Task 4 / spec part 1+2).
|
|
5
|
+
*
|
|
6
|
+
* Kept as a separate module so the logic is testable without rendering the
|
|
7
|
+
* full Ink WizardApp.
|
|
8
|
+
*/
|
|
9
|
+
export type PlanSaveFlowInput = {
|
|
10
|
+
/** Resolved default save path (e.g. `${project}/.contentful`). */
|
|
11
|
+
defaultPath: string;
|
|
12
|
+
/** When set, bypass the inline prompt entirely (`--out-dir` flag). */
|
|
13
|
+
outDirOverride?: string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Headless conflict-resolution mode (`--on-conflict`). Only meaningful when
|
|
16
|
+
* `outDirOverride` is also set — otherwise we still need to prompt for the
|
|
17
|
+
* path interactively. When provided alongside an override, the conflict
|
|
18
|
+
* gate is bypassed and the mode is applied via `resolveSavePath`.
|
|
19
|
+
*/
|
|
20
|
+
onConflict?: OnConflictMode | undefined;
|
|
21
|
+
};
|
|
22
|
+
export type PlanSaveFlowOutput = {
|
|
23
|
+
kind: 'write';
|
|
24
|
+
path: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'prompt';
|
|
27
|
+
defaultPath: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'fail';
|
|
30
|
+
conflict: {
|
|
31
|
+
path: string;
|
|
32
|
+
files: string[];
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export declare function planSaveFlow(input: PlanSaveFlowInput): Promise<PlanSaveFlowOutput>;
|
|
36
|
+
export type RecordRunAfterSaveInput = Omit<RunRecord, 'id' | 'createdAt'>;
|
|
37
|
+
export declare function recordRunAfterSave(input: RecordRunAfterSaveInput): Promise<RunRecord>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { appendRun } from '../../runs/store.js';
|
|
2
|
+
import { resolveSavePath } from '../../runs/save-path-resolver.js';
|
|
3
|
+
export async function planSaveFlow(input) {
|
|
4
|
+
if (input.outDirOverride) {
|
|
5
|
+
if (input.onConflict) {
|
|
6
|
+
const resolved = await resolveSavePath(input.outDirOverride, { onConflict: input.onConflict });
|
|
7
|
+
if (resolved.kind === 'write')
|
|
8
|
+
return { kind: 'write', path: resolved.path };
|
|
9
|
+
if (resolved.kind === 'fail')
|
|
10
|
+
return { kind: 'fail', conflict: resolved.conflict };
|
|
11
|
+
// `no-conflict` / `conflict` never come back when onConflict is set.
|
|
12
|
+
return { kind: 'write', path: input.outDirOverride };
|
|
13
|
+
}
|
|
14
|
+
return { kind: 'write', path: input.outDirOverride };
|
|
15
|
+
}
|
|
16
|
+
return { kind: 'prompt', defaultPath: input.defaultPath };
|
|
17
|
+
}
|
|
18
|
+
export async function recordRunAfterSave(input) {
|
|
19
|
+
return appendRun(input);
|
|
20
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure state-machine helpers for the wizard. Extracted from WizardApp.tsx so the
|
|
3
|
+
* transitions can be unit-tested without rendering ink.
|
|
4
|
+
*
|
|
5
|
+
* Bug fix (2026-06): credentials moved BEFORE generate so:
|
|
6
|
+
* 1. Bad creds (401/403) are caught upfront — before the operator pays the cost
|
|
7
|
+
* of extract + LLM classification (potentially many minutes).
|
|
8
|
+
* 2. Live preview in final-review actually fires, because the validated creds
|
|
9
|
+
* are present in WizardApp state when GenerateReviewStep mounts.
|
|
10
|
+
*
|
|
11
|
+
* New order:
|
|
12
|
+
* welcome → token-input → path-validation → extracting → scope-gate
|
|
13
|
+
* → credentials (validates inline) → generating
|
|
14
|
+
* → generating → final-review → push-decision-gate → previewing
|
|
15
|
+
* → preview-gate → pushing → done
|
|
16
|
+
*
|
|
17
|
+
* `--no-push` short-circuits the credentials/preview/push branch — the operator
|
|
18
|
+
* just wants extract + classify + final-review without ever touching Contentful.
|
|
19
|
+
*/
|
|
20
|
+
export type WizardStepAfterScope = 'credentials' | 'generating' | 'push-decision-gate' | 'print-gate';
|
|
21
|
+
export type WizardStepAfterCredentials = 'generating' | 'push-decision-gate';
|
|
22
|
+
/**
|
|
23
|
+
* Transition target after the scope-gate confirms.
|
|
24
|
+
*
|
|
25
|
+
* - acceptedCount > 0:
|
|
26
|
+
* - !noPush → `credentials` (gather + validate creds upfront, before LLM cost)
|
|
27
|
+
* - noPush → `generating` (skip creds entirely; live preview no-ops gracefully)
|
|
28
|
+
* - acceptedCount === 0 (everything rejected):
|
|
29
|
+
* - !noPush → `credentials` (still need creds to push tokens / removals)
|
|
30
|
+
* - noPush → `print-gate` (nothing to do; let the operator save files and exit)
|
|
31
|
+
*/
|
|
32
|
+
export declare function nextStepAfterScopeGate(opts: {
|
|
33
|
+
acceptedCount: number;
|
|
34
|
+
noPush: boolean;
|
|
35
|
+
}): WizardStepAfterScope;
|
|
36
|
+
/**
|
|
37
|
+
* Transition target after credentials are validated (or skipped via the
|
|
38
|
+
* inline validation pings the API). Drives the post-credentials branch:
|
|
39
|
+
* - acceptedCount > 0 → `generating` (run the generator now that creds are good)
|
|
40
|
+
* - acceptedCount === 0 → `push-decision-gate` (no components to generate;
|
|
41
|
+
* skip straight to push-decision and preview)
|
|
42
|
+
*/
|
|
43
|
+
export declare function nextStepAfterCredentialsValidated(opts: {
|
|
44
|
+
acceptedCount: number;
|
|
45
|
+
}): WizardStepAfterCredentials;
|
|
46
|
+
/**
|
|
47
|
+
* Skip-credentials spec — Task 2. When the operator pressed `s` on the
|
|
48
|
+
* credentials screen, the wizard advanced without validating creds. That
|
|
49
|
+
* makes `previewImport` impossible (no working token) and undesirable (the
|
|
50
|
+
* whole point is to inspect locally without touching the server). Callers
|
|
51
|
+
* check this before issuing the API call and route to
|
|
52
|
+
* `buildSkippedPreviewTransition()` instead.
|
|
53
|
+
*/
|
|
54
|
+
export declare function shouldBypassPreview(state: {
|
|
55
|
+
credentialsSkipped: boolean;
|
|
56
|
+
}): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* State patch applied when `shouldBypassPreview` is true: skip the preview
|
|
59
|
+
* step entirely, surface no server preview to downstream consumers, and
|
|
60
|
+
* hand control to the push-decision-gate (which will render with push
|
|
61
|
+
* disabled — see Task 3).
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildSkippedPreviewTransition(): {
|
|
64
|
+
step: 'push-decision-gate';
|
|
65
|
+
serverPreview: null;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Skip-credentials spec — Task 4 (defensive guard). The push-decision-gate
|
|
69
|
+
* disables "Save AND push" and "Push only" when credentialsSkipped is true,
|
|
70
|
+
* so `runPush` should never be reached. If a state-machine regression ever
|
|
71
|
+
* routed an operator past that guard, `runPush` checks this helper and
|
|
72
|
+
* refuses to issue the API call — instead it routes back to the print-files
|
|
73
|
+
* local-save path via `buildSkippedPushTransition`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function shouldRefusePush(state: {
|
|
76
|
+
credentialsSkipped: boolean;
|
|
77
|
+
}): boolean;
|
|
78
|
+
export declare function buildSkippedPushTransition(): {
|
|
79
|
+
step: 'print-gate';
|
|
80
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure state-machine helpers for the wizard. Extracted from WizardApp.tsx so the
|
|
3
|
+
* transitions can be unit-tested without rendering ink.
|
|
4
|
+
*
|
|
5
|
+
* Bug fix (2026-06): credentials moved BEFORE generate so:
|
|
6
|
+
* 1. Bad creds (401/403) are caught upfront — before the operator pays the cost
|
|
7
|
+
* of extract + LLM classification (potentially many minutes).
|
|
8
|
+
* 2. Live preview in final-review actually fires, because the validated creds
|
|
9
|
+
* are present in WizardApp state when GenerateReviewStep mounts.
|
|
10
|
+
*
|
|
11
|
+
* New order:
|
|
12
|
+
* welcome → token-input → path-validation → extracting → scope-gate
|
|
13
|
+
* → credentials (validates inline) → generating
|
|
14
|
+
* → generating → final-review → push-decision-gate → previewing
|
|
15
|
+
* → preview-gate → pushing → done
|
|
16
|
+
*
|
|
17
|
+
* `--no-push` short-circuits the credentials/preview/push branch — the operator
|
|
18
|
+
* just wants extract + classify + final-review without ever touching Contentful.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Transition target after the scope-gate confirms.
|
|
22
|
+
*
|
|
23
|
+
* - acceptedCount > 0:
|
|
24
|
+
* - !noPush → `credentials` (gather + validate creds upfront, before LLM cost)
|
|
25
|
+
* - noPush → `generating` (skip creds entirely; live preview no-ops gracefully)
|
|
26
|
+
* - acceptedCount === 0 (everything rejected):
|
|
27
|
+
* - !noPush → `credentials` (still need creds to push tokens / removals)
|
|
28
|
+
* - noPush → `print-gate` (nothing to do; let the operator save files and exit)
|
|
29
|
+
*/
|
|
30
|
+
export function nextStepAfterScopeGate(opts) {
|
|
31
|
+
if (opts.acceptedCount > 0) {
|
|
32
|
+
return opts.noPush ? 'generating' : 'credentials';
|
|
33
|
+
}
|
|
34
|
+
return opts.noPush ? 'print-gate' : 'credentials';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Transition target after credentials are validated (or skipped via the
|
|
38
|
+
* inline validation pings the API). Drives the post-credentials branch:
|
|
39
|
+
* - acceptedCount > 0 → `generating` (run the generator now that creds are good)
|
|
40
|
+
* - acceptedCount === 0 → `push-decision-gate` (no components to generate;
|
|
41
|
+
* skip straight to push-decision and preview)
|
|
42
|
+
*/
|
|
43
|
+
export function nextStepAfterCredentialsValidated(opts) {
|
|
44
|
+
return opts.acceptedCount > 0 ? 'generating' : 'push-decision-gate';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Skip-credentials spec — Task 2. When the operator pressed `s` on the
|
|
48
|
+
* credentials screen, the wizard advanced without validating creds. That
|
|
49
|
+
* makes `previewImport` impossible (no working token) and undesirable (the
|
|
50
|
+
* whole point is to inspect locally without touching the server). Callers
|
|
51
|
+
* check this before issuing the API call and route to
|
|
52
|
+
* `buildSkippedPreviewTransition()` instead.
|
|
53
|
+
*/
|
|
54
|
+
export function shouldBypassPreview(state) {
|
|
55
|
+
return state.credentialsSkipped === true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* State patch applied when `shouldBypassPreview` is true: skip the preview
|
|
59
|
+
* step entirely, surface no server preview to downstream consumers, and
|
|
60
|
+
* hand control to the push-decision-gate (which will render with push
|
|
61
|
+
* disabled — see Task 3).
|
|
62
|
+
*/
|
|
63
|
+
export function buildSkippedPreviewTransition() {
|
|
64
|
+
return { step: 'push-decision-gate', serverPreview: null };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Skip-credentials spec — Task 4 (defensive guard). The push-decision-gate
|
|
68
|
+
* disables "Save AND push" and "Push only" when credentialsSkipped is true,
|
|
69
|
+
* so `runPush` should never be reached. If a state-machine regression ever
|
|
70
|
+
* routed an operator past that guard, `runPush` checks this helper and
|
|
71
|
+
* refuses to issue the API call — instead it routes back to the print-files
|
|
72
|
+
* local-save path via `buildSkippedPushTransition`.
|
|
73
|
+
*/
|
|
74
|
+
export function shouldRefusePush(state) {
|
|
75
|
+
return state.credentialsSkipped === true;
|
|
76
|
+
}
|
|
77
|
+
export function buildSkippedPushTransition() {
|
|
78
|
+
return { step: 'print-gate' };
|
|
79
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Contentful webapp URL builders.
|
|
3
|
+
*
|
|
4
|
+
* The wizard's `done` step and the `experiences import` orchestrator both used
|
|
5
|
+
* to inline the same post-push URL formatter. This module centralizes that
|
|
6
|
+
* formatter so the wizard, `apply push`, and `apply select` all emit the same
|
|
7
|
+
* URL shape.
|
|
8
|
+
*
|
|
9
|
+
* Path: `/views/components` (modern path; the legacy `/exo/components` was
|
|
10
|
+
* replaced in INTEG-4227).
|
|
11
|
+
*
|
|
12
|
+
* Host mapping: api.contentful.com → app.contentful.com,
|
|
13
|
+
* api.flinkly.com → app.flinkly.com,
|
|
14
|
+
* api.quirely.com → app.quirely.com.
|
|
15
|
+
* Unknown hosts fall through unchanged (no guessing — see the parity-gap-fills
|
|
16
|
+
* spec, "Gap 4 URL host mapping" risk note).
|
|
17
|
+
*/
|
|
18
|
+
export interface BuildPostPushUrlInput {
|
|
19
|
+
host: string;
|
|
20
|
+
spaceId: string;
|
|
21
|
+
environmentId: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Map a Contentful API host to the corresponding webapp host. For api.*.com
|
|
25
|
+
* inputs this swaps the `api.` prefix to `app.`; any other host is returned
|
|
26
|
+
* as-is (callers get the raw host, no guessing).
|
|
27
|
+
*/
|
|
28
|
+
export declare function apiHostToAppHost(host: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Build the Contentful webapp URL operators see after a successful push.
|
|
31
|
+
* Matches the wizard `done` step's existing output byte-for-byte for
|
|
32
|
+
* `api.contentful.com`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildPostPushUrl({ host, spaceId, environmentId }: BuildPostPushUrlInput): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Contentful webapp URL builders.
|
|
3
|
+
*
|
|
4
|
+
* The wizard's `done` step and the `experiences import` orchestrator both used
|
|
5
|
+
* to inline the same post-push URL formatter. This module centralizes that
|
|
6
|
+
* formatter so the wizard, `apply push`, and `apply select` all emit the same
|
|
7
|
+
* URL shape.
|
|
8
|
+
*
|
|
9
|
+
* Path: `/views/components` (modern path; the legacy `/exo/components` was
|
|
10
|
+
* replaced in INTEG-4227).
|
|
11
|
+
*
|
|
12
|
+
* Host mapping: api.contentful.com → app.contentful.com,
|
|
13
|
+
* api.flinkly.com → app.flinkly.com,
|
|
14
|
+
* api.quirely.com → app.quirely.com.
|
|
15
|
+
* Unknown hosts fall through unchanged (no guessing — see the parity-gap-fills
|
|
16
|
+
* spec, "Gap 4 URL host mapping" risk note).
|
|
17
|
+
*/
|
|
18
|
+
function normalizeHost(host) {
|
|
19
|
+
return host
|
|
20
|
+
.trim()
|
|
21
|
+
.replace(/^https?:\/\//i, '')
|
|
22
|
+
.replace(/\/+$/, '');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Map a Contentful API host to the corresponding webapp host. For api.*.com
|
|
26
|
+
* inputs this swaps the `api.` prefix to `app.`; any other host is returned
|
|
27
|
+
* as-is (callers get the raw host, no guessing).
|
|
28
|
+
*/
|
|
29
|
+
export function apiHostToAppHost(host) {
|
|
30
|
+
const normalized = normalizeHost(host);
|
|
31
|
+
return normalized.replace(/^api\./, 'app.');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build the Contentful webapp URL operators see after a successful push.
|
|
35
|
+
* Matches the wizard `done` step's existing output byte-for-byte for
|
|
36
|
+
* `api.contentful.com`.
|
|
37
|
+
*/
|
|
38
|
+
export function buildPostPushUrl({ host, spaceId, environmentId }) {
|
|
39
|
+
const appHost = apiHostToAppHost(host);
|
|
40
|
+
return `https://${appHost}/spaces/${spaceId}/environments/${environmentId}/views/components`;
|
|
41
|
+
}
|
package/dist/src/program.js
CHANGED
|
@@ -10,6 +10,7 @@ import { registerSessionCommand } from './session/command.js';
|
|
|
10
10
|
import { registerPrintCommand } from './print/command.js';
|
|
11
11
|
import { registerImportCommand } from './import/command.js';
|
|
12
12
|
import { registerSetupCommand } from './setup/command.js';
|
|
13
|
+
import { registerRunsCommand } from './runs/ls-command.js';
|
|
13
14
|
const require = createRequire(import.meta.url);
|
|
14
15
|
const pkg = require('../package.json');
|
|
15
16
|
/**
|
|
@@ -62,6 +63,7 @@ export function createProgram() {
|
|
|
62
63
|
registerSessionCommand(program);
|
|
63
64
|
registerImportCommand(program);
|
|
64
65
|
registerSetupCommand(program);
|
|
66
|
+
registerRunsCommand(program);
|
|
65
67
|
registerBuildCommand(program);
|
|
66
68
|
return program;
|
|
67
69
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type PrintResult = {
|
|
2
|
+
ok: true;
|
|
3
|
+
} | {
|
|
4
|
+
ok: false;
|
|
5
|
+
error: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the path to the package's CLI binary (`<pkg>/bin/cli.js`).
|
|
9
|
+
*
|
|
10
|
+
* Walks up from this module's location until it finds a directory that
|
|
11
|
+
* contains `bin/cli.js`. This is robust to layout differences between source
|
|
12
|
+
* (`<pkg>/src/runs/export-helpers.ts`) and compiled output
|
|
13
|
+
* (`<pkg>/dist/src/runs/export-helpers.js`) — the previous string-counted
|
|
14
|
+
* `..` chain was off-by-one against the compiled layout, sending lookups to
|
|
15
|
+
* `<pkg>/dist/bin/cli.js` (which does not exist).
|
|
16
|
+
*/
|
|
17
|
+
declare function findCliPath(): string;
|
|
18
|
+
export declare function printComponentsFromSession(opts: {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
outPath: string;
|
|
21
|
+
}): Promise<PrintResult>;
|
|
22
|
+
export declare function printTokensFromSession(opts: {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
outPath: string;
|
|
25
|
+
}): Promise<PrintResult>;
|
|
26
|
+
/** Internal test surface. Not part of the public API. */
|
|
27
|
+
export declare const __testing: {
|
|
28
|
+
findCliPath: typeof findCliPath;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the path to the package's CLI binary (`<pkg>/bin/cli.js`).
|
|
7
|
+
*
|
|
8
|
+
* Walks up from this module's location until it finds a directory that
|
|
9
|
+
* contains `bin/cli.js`. This is robust to layout differences between source
|
|
10
|
+
* (`<pkg>/src/runs/export-helpers.ts`) and compiled output
|
|
11
|
+
* (`<pkg>/dist/src/runs/export-helpers.js`) — the previous string-counted
|
|
12
|
+
* `..` chain was off-by-one against the compiled layout, sending lookups to
|
|
13
|
+
* `<pkg>/dist/bin/cli.js` (which does not exist).
|
|
14
|
+
*/
|
|
15
|
+
function findCliPath() {
|
|
16
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
// Search up to 8 levels; the real distance is 3 (src) or 4 (dist/src).
|
|
18
|
+
for (let i = 0; i < 8; i++) {
|
|
19
|
+
const candidate = join(dir, 'bin', 'cli.js');
|
|
20
|
+
if (existsSync(candidate))
|
|
21
|
+
return candidate;
|
|
22
|
+
const parent = dirname(dir);
|
|
23
|
+
if (parent === dir)
|
|
24
|
+
break;
|
|
25
|
+
dir = parent;
|
|
26
|
+
}
|
|
27
|
+
// Fall back to the compiled-layout path; the existsSync check in runCli
|
|
28
|
+
// will surface a clear error if it is missing.
|
|
29
|
+
return join(fileURLToPath(import.meta.url), '..', '..', '..', '..', 'bin', 'cli.js');
|
|
30
|
+
}
|
|
31
|
+
function runCli(args) {
|
|
32
|
+
const cliPath = findCliPath();
|
|
33
|
+
if (!existsSync(cliPath)) {
|
|
34
|
+
return Promise.resolve({
|
|
35
|
+
exitCode: 1,
|
|
36
|
+
stdout: '',
|
|
37
|
+
stderr: `Cannot find CLI binary at ${cliPath}`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return new Promise((res) => {
|
|
41
|
+
execFile('node', [cliPath, ...args], (err, stdout, stderr) => {
|
|
42
|
+
res({ exitCode: err && 'code' in err && typeof err.code === 'number' ? err.code : err ? 1 : 0, stdout, stderr });
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export async function printComponentsFromSession(opts) {
|
|
47
|
+
const r = await runCli(['print', 'components', '--session', opts.sessionId, '--out', opts.outPath]);
|
|
48
|
+
if (r.exitCode === 0)
|
|
49
|
+
return { ok: true };
|
|
50
|
+
return { ok: false, error: r.stderr.trim() || `exit ${r.exitCode}` };
|
|
51
|
+
}
|
|
52
|
+
export async function printTokensFromSession(opts) {
|
|
53
|
+
const r = await runCli(['print', 'tokens', '--session', opts.sessionId, '--out', opts.outPath]);
|
|
54
|
+
if (r.exitCode === 0)
|
|
55
|
+
return { ok: true };
|
|
56
|
+
return { ok: false, error: r.stderr.trim() || `exit ${r.exitCode}` };
|
|
57
|
+
}
|
|
58
|
+
/** Internal test surface. Not part of the public API. */
|
|
59
|
+
export const __testing = { findCliPath };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type SourceFileEntry = {
|
|
2
|
+
mtime: string;
|
|
3
|
+
componentName?: string;
|
|
4
|
+
};
|
|
5
|
+
export type SourceFingerprint = {
|
|
6
|
+
files: Record<string, SourceFileEntry>;
|
|
7
|
+
rawTokensPath: string | null;
|
|
8
|
+
rawTokensMtime: string | null;
|
|
9
|
+
rawTokensContentHash: string | null;
|
|
10
|
+
};
|
|
11
|
+
export type SavedFingerprint = {
|
|
12
|
+
componentsJsonHash: string | null;
|
|
13
|
+
tokensJsonHash: string | null;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Minimal duck-typed sqlite interface; we don't depend on better-sqlite3
|
|
17
|
+
* directly from this module (the wizard already opens the DB and passes it
|
|
18
|
+
* in). `name` may be null in the DB; we filter those rows out.
|
|
19
|
+
*/
|
|
20
|
+
export interface RawComponentsDb {
|
|
21
|
+
prepare(sql: string): {
|
|
22
|
+
all(sessionId: string): Array<Record<string, unknown>>;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare function sha256Hex(buf: Buffer | string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Build a per-file fingerprint of every source file that contributed to a
|
|
28
|
+
* run. Stats each unique source_path once (first-seen component name wins),
|
|
29
|
+
* and (optionally) hashes the raw tokens file too.
|
|
30
|
+
*
|
|
31
|
+
* Missing files do NOT throw — they're simply omitted. The read-side
|
|
32
|
+
* staleness check re-stats from disk and surfaces missing files there so we
|
|
33
|
+
* can report which one disappeared.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildSourceFingerprint(opts: {
|
|
36
|
+
db: RawComponentsDb;
|
|
37
|
+
extractSessionId: string;
|
|
38
|
+
rawTokensPath?: string | null;
|
|
39
|
+
}): Promise<SourceFingerprint>;
|
|
40
|
+
/**
|
|
41
|
+
* Hash the saved JSON contents the wizard just wrote. Caller passes the raw
|
|
42
|
+
* bytes (or strings) so we hash exactly what landed on disk.
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildSavedFingerprint(input: {
|
|
45
|
+
componentsJson: string | Buffer | null;
|
|
46
|
+
tokensJson: string | Buffer | null;
|
|
47
|
+
}): SavedFingerprint;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
function rowField(row, key) {
|
|
5
|
+
const v = row[key];
|
|
6
|
+
return typeof v === 'string' ? v : null;
|
|
7
|
+
}
|
|
8
|
+
export function sha256Hex(buf) {
|
|
9
|
+
return createHash('sha256').update(buf).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Build a per-file fingerprint of every source file that contributed to a
|
|
13
|
+
* run. Stats each unique source_path once (first-seen component name wins),
|
|
14
|
+
* and (optionally) hashes the raw tokens file too.
|
|
15
|
+
*
|
|
16
|
+
* Missing files do NOT throw — they're simply omitted. The read-side
|
|
17
|
+
* staleness check re-stats from disk and surfaces missing files there so we
|
|
18
|
+
* can report which one disappeared.
|
|
19
|
+
*/
|
|
20
|
+
export async function buildSourceFingerprint(opts) {
|
|
21
|
+
const rows = opts.db
|
|
22
|
+
.prepare('SELECT name, source_path FROM raw_components WHERE session_id = ?')
|
|
23
|
+
.all(opts.extractSessionId);
|
|
24
|
+
const files = {};
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
for (const row of rows) {
|
|
27
|
+
const sourcePath = rowField(row, 'source_path');
|
|
28
|
+
const name = rowField(row, 'name');
|
|
29
|
+
if (!sourcePath)
|
|
30
|
+
continue;
|
|
31
|
+
const abs = resolve(sourcePath);
|
|
32
|
+
if (seen.has(abs))
|
|
33
|
+
continue;
|
|
34
|
+
seen.add(abs);
|
|
35
|
+
try {
|
|
36
|
+
const st = await stat(abs);
|
|
37
|
+
const entry = { mtime: st.mtime.toISOString() };
|
|
38
|
+
if (name)
|
|
39
|
+
entry.componentName = name;
|
|
40
|
+
files[abs] = entry;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Source file missing at fingerprint time — skip silently. Staleness
|
|
44
|
+
// can only fire on files we recorded; a file removed before save
|
|
45
|
+
// simply isn't tracked. Operators usually re-extract in that case.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let rawTokensPath = null;
|
|
49
|
+
let rawTokensMtime = null;
|
|
50
|
+
let rawTokensContentHash = null;
|
|
51
|
+
if (opts.rawTokensPath) {
|
|
52
|
+
const abs = resolve(opts.rawTokensPath);
|
|
53
|
+
rawTokensPath = abs;
|
|
54
|
+
try {
|
|
55
|
+
const [st, buf] = await Promise.all([stat(abs), readFile(abs)]);
|
|
56
|
+
rawTokensMtime = st.mtime.toISOString();
|
|
57
|
+
rawTokensContentHash = sha256Hex(buf);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Missing raw tokens — record the path but leave mtime/hash null so
|
|
61
|
+
// the staleness check can detect it as missing on read.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { files, rawTokensPath, rawTokensMtime, rawTokensContentHash };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Hash the saved JSON contents the wizard just wrote. Caller passes the raw
|
|
68
|
+
* bytes (or strings) so we hash exactly what landed on disk.
|
|
69
|
+
*/
|
|
70
|
+
export function buildSavedFingerprint(input) {
|
|
71
|
+
return {
|
|
72
|
+
componentsJsonHash: input.componentsJson != null ? sha256Hex(input.componentsJson) : null,
|
|
73
|
+
tokensJsonHash: input.tokensJson != null ? sha256Hex(input.tokensJson) : null,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export type RunLsOptions = {
|
|
3
|
+
write?: (chunk: string) => void;
|
|
4
|
+
projectPath?: string;
|
|
5
|
+
limit?: number;
|
|
6
|
+
target?: string;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
pushed?: boolean;
|
|
9
|
+
notPushed?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function runLsCommand(opts?: RunLsOptions): Promise<void>;
|
|
12
|
+
export declare function registerRunsCommand(program: Command): void;
|