@contentful/experience-design-system-cli 2.37.1-dev-build-c1d02862.0 → 2.37.1-dev-build-40c29bf0.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/dist/app.js +3191 -317
- package/dist/index.js +3462 -316
- package/dist/package.json +1 -1
- package/dist/src/tui/import/steps/07-scope-gate/cache-check.d.ts +29 -0
- package/{legacy/dist/src/import/tui/scope-gate-columns.d.ts → dist/src/tui/import/steps/07-scope-gate/columns.d.ts} +22 -2
- package/dist/src/tui/import/steps/07-scope-gate/graph/composite-closure.d.ts +26 -0
- package/dist/src/tui/import/steps/07-scope-gate/graph/cycle-detection.d.ts +22 -0
- package/dist/src/tui/import/steps/07-scope-gate/graph/fuzzy-search.d.ts +3 -0
- package/dist/src/tui/import/steps/07-scope-gate/graph/index.d.ts +33 -0
- package/dist/src/tui/import/steps/07-scope-gate/graph/lineage.d.ts +29 -0
- package/dist/src/tui/import/steps/07-scope-gate/graph/search-neighborhood.d.ts +3 -0
- package/dist/src/tui/import/steps/07-scope-gate/graph/slot-graph.d.ts +11 -0
- package/dist/src/tui/import/steps/07-scope-gate/keymap.d.ts +84 -0
- package/dist/src/tui/import/steps/07-scope-gate/load.d.ts +8 -0
- package/dist/src/tui/import/steps/07-scope-gate/panels/added-column.d.ts +15 -0
- package/dist/src/tui/import/steps/07-scope-gate/panels/cycles-panel.d.ts +8 -0
- package/dist/src/tui/import/steps/07-scope-gate/panels/reject-prompt.d.ts +5 -0
- package/dist/src/tui/import/steps/07-scope-gate/screen.d.ts +9 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/autocomplete.d.ts +5 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/components/CompactControlBar.d.ts +4 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/components/GotoBanner.d.ts +32 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/components/GroupedSidebar.d.ts +93 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/components/HelpOverlay.d.ts +21 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/components/LegendEntry.d.ts +2 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/components/SearchMatchSummary.d.ts +11 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/components/badge.d.ts +13 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/components/windowed-panel.d.ts +17 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/group-collapse.d.ts +2 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/hooks/sidebar-search-state.d.ts +17 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/hooks/use-terminal-size.d.ts +6 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/hooks/useImmediateInput.d.ts +20 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/hooks/useOverlayPanel.d.ts +13 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/lineage-layout.d.ts +68 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/sidebar-help.d.ts +2 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/sidebar-input.d.ts +13 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/sidebar-navigation.d.ts +18 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/step-filters.d.ts +19 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/theme.d.ts +8 -0
- package/dist/src/tui/import/steps/07-scope-gate/sidebar/types.d.ts +7 -0
- package/dist/src/tui/import/steps/07-scope-gate/state.d.ts +19 -0
- package/dist/src/tui/import/steps/07-scope-gate/types.d.ts +24 -0
- package/dist/src/tui/import/steps/07-scope-gate/use-scope-gate-flow.d.ts +31 -0
- package/dist/src/tui/import/steps/07-scope-gate/use-scope-gate.d.ts +76 -0
- package/legacy/dist/src/import/tui/handoff.d.ts +11 -0
- package/legacy/dist/src/index.js +1511 -2733
- package/legacy/package.json +1 -1
- package/package.json +4 -4
- package/legacy/dist/src/import/tui/steps/ScopeGateStep.d.ts +0 -48
- /package/{legacy/dist/src/analyze → dist/src/tui/import/steps/07-scope-gate/graph}/scope-gate-cascade.d.ts +0 -0
- /package/{legacy/dist/src/import/tui → dist/src/tui/import/steps/07-scope-gate/sidebar}/ai-flag.d.ts +0 -0
- /package/{legacy/dist/src/import/tui → dist/src/tui/import/steps/07-scope-gate/sidebar}/components/CounterStrip.d.ts +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Maximum layout rows; the active terminal height determines the actual budget. */
|
|
2
|
+
export declare const VISIBLE_COUNT = 200;
|
|
3
|
+
/** Maximum supported panel window; normal terminals remain terminal-sized. */
|
|
4
|
+
export declare const MAX_PANEL_ROWS = 200;
|
|
5
|
+
/** Floors — both surfaces stay usable even on a small terminal. */
|
|
6
|
+
export declare const SIDEBAR_MIN = 4;
|
|
7
|
+
export declare const MIN_PANEL_ROWS = 4;
|
|
8
|
+
/**
|
|
9
|
+
* Fallback when `stdout.rows` is unavailable (piped output, tests). No TTY
|
|
10
|
+
* means Ink is not interactively repainting, so there is no flicker risk — we
|
|
11
|
+
* assume enough height for a terminal-sized sidebar so the non-interactive
|
|
12
|
+
* render is complete and unclipped.
|
|
13
|
+
*/
|
|
14
|
+
export declare const FALLBACK_ROWS = 40;
|
|
15
|
+
/**
|
|
16
|
+
* Vertical space consumed by everything ABOVE/BELOW the columns row. The panel
|
|
17
|
+
* shares the columns row, so the fit constraint while open is simply
|
|
18
|
+
* `FIXED_OVERHEAD + panelBoxHeight <= rows`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const FIXED_OVERHEAD: number;
|
|
21
|
+
export declare const PANEL_BOX_CHROME = 6;
|
|
22
|
+
/**
|
|
23
|
+
* L2e — fixed vertical chrome around the sidebar in the BASE (no-panel) case,
|
|
24
|
+
* counting EVERYTHING that is not a variable sidebar entry row: the wizard
|
|
25
|
+
* header bar, the counter strip (with its blank separator), the cycle banner,
|
|
26
|
+
* the nothing-selected /
|
|
27
|
+
* AI-exclusion hint, the GroupedSidebar box borders + scroll indicator, the
|
|
28
|
+
* focused-detail block, and the wrapping legend region. The value intentionally
|
|
29
|
+
* leaves a small safety margin so resize-induced wrapping does not overflow.
|
|
30
|
+
*/
|
|
31
|
+
export declare const BASE_CHROME_OVERHEAD = 18;
|
|
32
|
+
export interface LineageLayoutInput {
|
|
33
|
+
/** Terminal height in rows (`stdout.rows`, or the fallback). */
|
|
34
|
+
rows: number;
|
|
35
|
+
/** Whether the lineage panel is currently open. */
|
|
36
|
+
panelOpen: boolean;
|
|
37
|
+
/** Number of lineage entries — caps the panel window so it never over-allocates. */
|
|
38
|
+
entryCount?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface LineageLayout {
|
|
41
|
+
/** Rows the sidebar should render (`visibleCount`). Constant across open/closed. */
|
|
42
|
+
sidebarVisible: number;
|
|
43
|
+
/** Rows a sidebar replacement panel should window to (`maxRows`). */
|
|
44
|
+
panelMaxRows: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The sidebar height is constant (the panel replaces it in the same slot, so
|
|
48
|
+
* there is nothing to shrink). Both values are derived from the terminal so
|
|
49
|
+
* resizing grows or shrinks the viewport without content-driven layout changes.
|
|
50
|
+
*/
|
|
51
|
+
export declare function computeLineageLayout({ rows, panelOpen, entryCount }: LineageLayoutInput): LineageLayout;
|
|
52
|
+
export interface SidebarBudget {
|
|
53
|
+
/** Rows the GroupedSidebar should render (`visibleCount`). */
|
|
54
|
+
sidebarVisibleCount: number;
|
|
55
|
+
/** Rows a sidebar replacement panel should window to (`maxRows`). */
|
|
56
|
+
panelMaxRows: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* L2e — autoscale the BASE frame to the terminal height. Even with the lineage
|
|
60
|
+
* panel closed, the visible-row budget follows `stdout.rows` minus
|
|
61
|
+
* `BASE_CHROME_OVERHEAD` so the whole frame stays within the terminal while
|
|
62
|
+
* preserving the shared panel-open sizing behavior.
|
|
63
|
+
*
|
|
64
|
+
* Floor: `BASE_CHROME_OVERHEAD + SIDEBAR_MIN`. Below it `SIDEBAR_MIN`
|
|
65
|
+
* (usability) intentionally wins over the fit — a terminal that small can't
|
|
66
|
+
* host the chrome regardless.
|
|
67
|
+
*/
|
|
68
|
+
export declare function computeSidebarBudget({ rows, panelOpen, entryCount }: LineageLayoutInput): SidebarBudget;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type SearchInputKey = {
|
|
2
|
+
tab: boolean;
|
|
3
|
+
backspace: boolean;
|
|
4
|
+
};
|
|
5
|
+
type SearchInputOptions = {
|
|
6
|
+
query: string;
|
|
7
|
+
names: string[];
|
|
8
|
+
setQuery: (value: string | ((current: string) => string)) => void;
|
|
9
|
+
setCandidates: (value: string[]) => void;
|
|
10
|
+
};
|
|
11
|
+
export declare function handleSidebarSearchInput(input: string, key: SearchInputKey, { query, names, setQuery, setCandidates }: SearchInputOptions): boolean;
|
|
12
|
+
export declare function formatSearchMatchSummary(matches: number, total: number): string;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ComponentGraphNode, Closure } from '../graph/composite-closure.js';
|
|
2
|
+
import { type GroupedSidebarItem } from './components/GroupedSidebar.js';
|
|
3
|
+
export declare function collectExpandedGroupRoots(closures: Map<string, Closure>, cycleParticipants: Iterable<string>): Set<string>;
|
|
4
|
+
export declare function computeSidebarViewToggle({ currentView, currentKey, currentScroll, visibleCount, items, cycleParticipants, expandedGroups, graph, showFlatTier, }: {
|
|
5
|
+
currentView: 'grouped' | 'flat';
|
|
6
|
+
currentKey: string | null | undefined;
|
|
7
|
+
currentScroll: number;
|
|
8
|
+
visibleCount: number;
|
|
9
|
+
items: GroupedSidebarItem[];
|
|
10
|
+
cycleParticipants: Set<string>;
|
|
11
|
+
expandedGroups: Set<string>;
|
|
12
|
+
graph: ComponentGraphNode[];
|
|
13
|
+
showFlatTier?: boolean;
|
|
14
|
+
}): {
|
|
15
|
+
view: 'grouped' | 'flat';
|
|
16
|
+
cursor: number;
|
|
17
|
+
scroll: number;
|
|
18
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type FilterCategory = 'broken' | 'cycles' | 'deleted';
|
|
2
|
+
export interface FilterDataSources {
|
|
3
|
+
/** Component keys that participate in a slot cycle. */
|
|
4
|
+
cycles?: Iterable<string>;
|
|
5
|
+
/** Component keys the step considers "broken" (issue/warning/AI-flagged). */
|
|
6
|
+
broken?: Iterable<string>;
|
|
7
|
+
/** Component keys marked for deletion (GenerateReview only). */
|
|
8
|
+
deleted?: Iterable<string>;
|
|
9
|
+
}
|
|
10
|
+
export declare function computeFilterKeys(input: {
|
|
11
|
+
filters: Iterable<FilterCategory>;
|
|
12
|
+
data: FilterDataSources;
|
|
13
|
+
}): Set<string> | undefined;
|
|
14
|
+
export declare function intersectFilterKeys(a: Set<string> | undefined, b: Set<string> | undefined): Set<string> | undefined;
|
|
15
|
+
export declare function buildFlatDimPredicate(input: {
|
|
16
|
+
viewMode: 'grouped' | 'flat';
|
|
17
|
+
searchQuery: string;
|
|
18
|
+
filterVisibleKeys: Set<string> | undefined;
|
|
19
|
+
}): ((componentKey: string) => boolean) | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ScopeGraph } from './graph/index.js';
|
|
2
|
+
import type { Decision, ScopeComponent, ScopeDecisions } from './types.js';
|
|
3
|
+
export type DecisionState = Map<string, Decision>;
|
|
4
|
+
export type DecisionAction = {
|
|
5
|
+
type: 'accept';
|
|
6
|
+
name: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: 'reject';
|
|
9
|
+
name: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'toggle-all';
|
|
12
|
+
} | {
|
|
13
|
+
type: 'accept-unflagged';
|
|
14
|
+
};
|
|
15
|
+
export declare function reduceDecisions(state: DecisionState, action: DecisionAction, components: ScopeComponent[], graph: ScopeGraph): DecisionState;
|
|
16
|
+
/** Whether rejecting `name` touches 2 or more other components, so the screen has to ask first. */
|
|
17
|
+
export declare function rejectNeedsConfirmation(graph: ScopeGraph, name: string, state: DecisionState): boolean;
|
|
18
|
+
/** Everything not accepted counts as rejected, as legacy's `partition` did. */
|
|
19
|
+
export declare function partitionDecisions(components: ScopeComponent[], state: DecisionState): ScopeDecisions;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ScopeComponentRow } from '@contentful/experience-design-system-backend-pipeline';
|
|
2
|
+
/**
|
|
3
|
+
* One row of the gate. The loader always fills in the slots, but the screen is handed whatever the caller has, and
|
|
4
|
+
* legacy's screen accepted rows without them (and without the AI fields), so those stay optional here.
|
|
5
|
+
*/
|
|
6
|
+
export type ScopeComponent = Omit<ScopeComponentRow, 'aiDecision' | 'aiReason' | 'needsReview' | 'slots'> & {
|
|
7
|
+
aiDecision?: 'accepted' | 'rejected' | 'failed' | null;
|
|
8
|
+
aiReason?: string | null;
|
|
9
|
+
needsReview?: boolean;
|
|
10
|
+
slots?: ScopeComponentRow['slots'];
|
|
11
|
+
};
|
|
12
|
+
export type Decision = 'accepted' | 'rejected' | 'undecided';
|
|
13
|
+
/** What the user confirmed. Becomes `ImportHandoff.scope`; the legacy wizard saves it and starts generation. */
|
|
14
|
+
export interface ScopeDecisions {
|
|
15
|
+
accepted: string[];
|
|
16
|
+
rejected: string[];
|
|
17
|
+
}
|
|
18
|
+
export type ScopeGateLoad = {
|
|
19
|
+
ok: true;
|
|
20
|
+
components: ScopeComponent[];
|
|
21
|
+
} | {
|
|
22
|
+
ok: false;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ScopeHandoff } from '@contentful/experience-design-system-types/handoff';
|
|
2
|
+
import { type CacheCheckInput } from './cache-check.js';
|
|
3
|
+
import { loadScopeGate } from './load.js';
|
|
4
|
+
import type { ScopeDecisions, ScopeGateLoad } from './types.js';
|
|
5
|
+
export type ScopeGateFlow = {
|
|
6
|
+
status: 'failed';
|
|
7
|
+
message: string;
|
|
8
|
+
} | {
|
|
9
|
+
status: 'deciding';
|
|
10
|
+
load: Extract<ScopeGateLoad, {
|
|
11
|
+
ok: true;
|
|
12
|
+
}>;
|
|
13
|
+
confirm: (decisions: ScopeDecisions) => void;
|
|
14
|
+
}
|
|
15
|
+
/** Confirmed before the cache check finished. */
|
|
16
|
+
| {
|
|
17
|
+
status: 'preparing';
|
|
18
|
+
} | {
|
|
19
|
+
status: 'confirmed';
|
|
20
|
+
scope: ScopeHandoff;
|
|
21
|
+
};
|
|
22
|
+
interface Deps {
|
|
23
|
+
load: typeof loadScopeGate;
|
|
24
|
+
checkCache: (input: CacheCheckInput) => Promise<string[]>;
|
|
25
|
+
}
|
|
26
|
+
/** `input` is undefined until the extraction stage has produced a session, so the hook can be called unconditionally. */
|
|
27
|
+
export declare function useScopeGateFlow(input: {
|
|
28
|
+
sessionId: string;
|
|
29
|
+
cache: CacheCheckInput;
|
|
30
|
+
} | undefined, deps?: Deps): ScopeGateFlow | undefined;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ScopeComponent, ScopeDecisions } from './types.js';
|
|
2
|
+
interface UseScopeGateOptions {
|
|
3
|
+
components: ScopeComponent[];
|
|
4
|
+
onConfirm: (decisions: ScopeDecisions) => void;
|
|
5
|
+
onQuit: () => void;
|
|
6
|
+
}
|
|
7
|
+
type FocusedColumn = 'main' | 'added-components';
|
|
8
|
+
export interface PendingRejectCascade {
|
|
9
|
+
target: string;
|
|
10
|
+
ancestors: string[];
|
|
11
|
+
descendants: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function useScopeGate({ components, onConfirm, onQuit }: UseScopeGateOptions): {
|
|
14
|
+
totalWidth: number;
|
|
15
|
+
columnPlan: {
|
|
16
|
+
layout: "single" | "two-column";
|
|
17
|
+
main: number;
|
|
18
|
+
added: number;
|
|
19
|
+
};
|
|
20
|
+
panelLayout: import("./columns.js").ScopeGatePanelLayout;
|
|
21
|
+
visibleCount: number;
|
|
22
|
+
panelMaxRows: number;
|
|
23
|
+
groupedItems: import("./graph/index.js").SidebarItem[];
|
|
24
|
+
graph: import("./graph/index.js").ScopeGraph;
|
|
25
|
+
hasGroupRoots: boolean;
|
|
26
|
+
expandedGroups: Set<string>;
|
|
27
|
+
toggleExpanded: (rootName: string) => void;
|
|
28
|
+
visibleRows: import("./sidebar/components/GroupedSidebar.js").VisibleRow[];
|
|
29
|
+
safeCursor: number;
|
|
30
|
+
scrollOffset: number;
|
|
31
|
+
selectionStateByKey: Map<string, "accepted" | "rejected" | "undecided">;
|
|
32
|
+
aiFlaggedByKey: Map<string, boolean>;
|
|
33
|
+
dimPredicate: ((componentKey: string) => boolean) | undefined;
|
|
34
|
+
columnOneView: "flat" | "grouped";
|
|
35
|
+
focusedColumn: FocusedColumn;
|
|
36
|
+
showHelp: boolean;
|
|
37
|
+
closeHelp: () => void;
|
|
38
|
+
cyclesPanelOpen: boolean;
|
|
39
|
+
cyclesCursor: number;
|
|
40
|
+
slotCycles: import("./graph/cycle-detection.js").SlotCycle[];
|
|
41
|
+
aiRationalePanelOpen: boolean;
|
|
42
|
+
aiCursor: number;
|
|
43
|
+
aiRows: {
|
|
44
|
+
label: string;
|
|
45
|
+
jumpTarget: string;
|
|
46
|
+
}[];
|
|
47
|
+
aiExcludedCount: number;
|
|
48
|
+
pendingRejectCascade: PendingRejectCascade | null;
|
|
49
|
+
addedComponents: import("./columns.js").AddedComponentEntry[];
|
|
50
|
+
safeAddedComponentsCursor: number;
|
|
51
|
+
counters: {
|
|
52
|
+
accepted: number;
|
|
53
|
+
rejected: number;
|
|
54
|
+
undecided: number;
|
|
55
|
+
groups: number;
|
|
56
|
+
total: number;
|
|
57
|
+
};
|
|
58
|
+
search: {
|
|
59
|
+
searchOpen: boolean;
|
|
60
|
+
setSearchOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
61
|
+
searchQuery: string;
|
|
62
|
+
setSearchQuery: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
63
|
+
autocompleteCandidates: string[];
|
|
64
|
+
setAutocompleteCandidates: import("react").Dispatch<import("react").SetStateAction<string[]>>;
|
|
65
|
+
jumpFilterTarget: string | null;
|
|
66
|
+
setJumpFilterTarget: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
|
67
|
+
columnOneView: "flat" | "grouped";
|
|
68
|
+
setColumnOneView: import("react").Dispatch<import("react").SetStateAction<"flat" | "grouped">>;
|
|
69
|
+
activeFilters: Set<import("./sidebar/step-filters.js").FilterCategory>;
|
|
70
|
+
setActiveFilters: import("react").Dispatch<import("react").SetStateAction<Set<import("./sidebar/step-filters.js").FilterCategory>>>;
|
|
71
|
+
showHelp: boolean;
|
|
72
|
+
setShowHelp: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
73
|
+
};
|
|
74
|
+
searchMatches: string[];
|
|
75
|
+
};
|
|
76
|
+
export {};
|
|
@@ -13,11 +13,22 @@ export interface HandoffState {
|
|
|
13
13
|
existingEntitiesStatus?: 'idle' | 'complete' | 'failed';
|
|
14
14
|
existingEntitiesPath?: string | null;
|
|
15
15
|
}
|
|
16
|
+
/** What the user confirmed at the scope gate, which the CLI now shows. The wizard saves it, then generates. */
|
|
17
|
+
export interface HandoffScope {
|
|
18
|
+
decisions: {
|
|
19
|
+
accepted: string[];
|
|
20
|
+
rejected: string[];
|
|
21
|
+
};
|
|
22
|
+
/** Components the cache check (finished and restored before the wizard started) found already generated. */
|
|
23
|
+
cachedNames: string[];
|
|
24
|
+
}
|
|
25
|
+
export declare function scopeFromHandoff(handoff: ImportHandoff): HandoffScope | null;
|
|
16
26
|
export declare function stateFromHandoff(handoff: ImportHandoff): HandoffState;
|
|
17
27
|
export type StartFromHandoff = {
|
|
18
28
|
ok: true;
|
|
19
29
|
handoff: ImportHandoff;
|
|
20
30
|
state: HandoffState;
|
|
31
|
+
scope: HandoffScope | null;
|
|
21
32
|
} | {
|
|
22
33
|
ok: false;
|
|
23
34
|
error: string;
|