grimoire-wizard 0.4.0 → 0.5.1
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 +176 -1449
- package/dist/cli.js +134 -24
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +104 -1
- package/dist/index.js +125 -17
- package/dist/index.js.map +1 -1
- package/examples/handlers/setup-project.ts +9 -0
- package/examples/json/with-actions.json +61 -0
- package/examples/json/with-oncomplete.json +45 -0
- package/examples/yaml/with-actions.yaml +45 -0
- package/examples/yaml/with-oncomplete.yaml +35 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -184,7 +184,14 @@ var themeConfigSchema = z.object({
|
|
|
184
184
|
stepDone: z.string().optional(),
|
|
185
185
|
stepPending: z.string().optional(),
|
|
186
186
|
pointer: z.string().optional()
|
|
187
|
-
}).optional()
|
|
187
|
+
}).optional(),
|
|
188
|
+
spinner: z.union([
|
|
189
|
+
z.string(),
|
|
190
|
+
z.object({
|
|
191
|
+
frames: z.array(z.string()).min(1),
|
|
192
|
+
interval: z.number().positive().optional()
|
|
193
|
+
})
|
|
194
|
+
]).optional()
|
|
188
195
|
});
|
|
189
196
|
var preFlightCheckSchema = z.object({
|
|
190
197
|
name: z.string(),
|
|
@@ -201,7 +208,8 @@ var wizardConfigSchema = z.object({
|
|
|
201
208
|
name: z.string(),
|
|
202
209
|
version: z.string().optional(),
|
|
203
210
|
description: z.string().optional(),
|
|
204
|
-
review: z.boolean().optional()
|
|
211
|
+
review: z.boolean().optional(),
|
|
212
|
+
icon: z.string().optional()
|
|
205
213
|
}),
|
|
206
214
|
theme: themeConfigSchema.optional(),
|
|
207
215
|
steps: z.array(stepConfigSchema).min(1),
|
|
@@ -211,7 +219,8 @@ var wizardConfigSchema = z.object({
|
|
|
211
219
|
}).optional(),
|
|
212
220
|
extends: z.string().optional(),
|
|
213
221
|
checks: z.array(preFlightCheckSchema).optional(),
|
|
214
|
-
actions: z.array(actionConfigSchema).optional()
|
|
222
|
+
actions: z.array(actionConfigSchema).optional(),
|
|
223
|
+
onComplete: z.string().optional()
|
|
215
224
|
}).superRefine((config, ctx) => {
|
|
216
225
|
const stepIds = /* @__PURE__ */ new Set();
|
|
217
226
|
for (const step of config.steps) {
|
|
@@ -486,6 +495,8 @@ async function loadWizardConfig(filePath) {
|
|
|
486
495
|
|
|
487
496
|
// src/runner.ts
|
|
488
497
|
import { execSync } from "child_process";
|
|
498
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
499
|
+
import { pathToFileURL } from "url";
|
|
489
500
|
|
|
490
501
|
// src/conditions.ts
|
|
491
502
|
function isRecord(value) {
|
|
@@ -825,6 +836,41 @@ var THEME_PRESETS = {
|
|
|
825
836
|
};
|
|
826
837
|
var PRESET_NAMES = Object.keys(THEME_PRESETS);
|
|
827
838
|
|
|
839
|
+
// src/spinners.ts
|
|
840
|
+
var spinners = {
|
|
841
|
+
dots: { interval: 80, frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] },
|
|
842
|
+
dots2: { interval: 80, frames: ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"] },
|
|
843
|
+
line: { interval: 130, frames: ["-", "\\", "|", "/"] },
|
|
844
|
+
arc: { interval: 100, frames: ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"] },
|
|
845
|
+
circle: { interval: 80, frames: ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] },
|
|
846
|
+
circleHalves: { interval: 50, frames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"] },
|
|
847
|
+
triangle: { interval: 50, frames: ["\u25E2", "\u25E3", "\u25E4", "\u25E5"] },
|
|
848
|
+
pipe: { interval: 100, frames: ["\u2524", "\u2518", "\u2534", "\u2514", "\u251C", "\u250C", "\u252C", "\u2510"] },
|
|
849
|
+
arrow: { interval: 100, frames: ["\u2190", "\u2196", "\u2191", "\u2197", "\u2192", "\u2198", "\u2193", "\u2199"] },
|
|
850
|
+
arrow3: { interval: 120, frames: ["\u25B9\u25B9\u25B9\u25B9\u25B9", "\u25B8\u25B9\u25B9\u25B9\u25B9", "\u25B9\u25B8\u25B9\u25B9\u25B9", "\u25B9\u25B9\u25B8\u25B9\u25B9", "\u25B9\u25B9\u25B9\u25B8\u25B9", "\u25B9\u25B9\u25B9\u25B9\u25B8"] },
|
|
851
|
+
bouncingBar: { interval: 80, frames: ["[ ]", "[= ]", "[== ]", "[=== ]", "[====]", "[ ===]", "[ ==]", "[ =]", "[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"] },
|
|
852
|
+
bouncingBall: { interval: 80, frames: ["( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF)", "( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF )", "(\u25CF )"] },
|
|
853
|
+
simpleDots: { interval: 400, frames: [". ", ".. ", "...", " "] },
|
|
854
|
+
aesthetic: { interval: 80, frames: ["\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B1\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B1\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B0\u25B1\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B0\u25B0\u25B1", "\u25B0\u25B0\u25B0\u25B0\u25B0\u25B0\u25B0", "\u25B0\u25B1\u25B1\u25B1\u25B1\u25B1\u25B1"] },
|
|
855
|
+
star: { interval: 70, frames: ["\u2736", "\u2738", "\u2739", "\u273A", "\u2739", "\u2737"] }
|
|
856
|
+
};
|
|
857
|
+
var DEFAULT_SPINNER = "circle";
|
|
858
|
+
function resolveSpinner(config) {
|
|
859
|
+
if (!config) {
|
|
860
|
+
return spinners[DEFAULT_SPINNER];
|
|
861
|
+
}
|
|
862
|
+
if (typeof config === "string") {
|
|
863
|
+
if (config in spinners) {
|
|
864
|
+
return spinners[config];
|
|
865
|
+
}
|
|
866
|
+
throw new Error(`Unknown spinner preset: "${config}". Available: ${Object.keys(spinners).join(", ")}`);
|
|
867
|
+
}
|
|
868
|
+
return {
|
|
869
|
+
frames: config.frames,
|
|
870
|
+
interval: config.interval ?? 80
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
|
|
828
874
|
// src/theme.ts
|
|
829
875
|
var DEFAULT_TOKENS = {
|
|
830
876
|
primary: "#5B9BD5",
|
|
@@ -854,7 +900,8 @@ function resolveTheme(themeConfig) {
|
|
|
854
900
|
muted: chalk.hex(tokens.muted),
|
|
855
901
|
accent: chalk.hex(tokens.accent),
|
|
856
902
|
bold: chalk.bold,
|
|
857
|
-
icons
|
|
903
|
+
icons,
|
|
904
|
+
spinner: resolveSpinner(themeConfig?.spinner)
|
|
858
905
|
};
|
|
859
906
|
}
|
|
860
907
|
|
|
@@ -1078,14 +1125,27 @@ function resolveTemplate(template, answers) {
|
|
|
1078
1125
|
return _match;
|
|
1079
1126
|
});
|
|
1080
1127
|
}
|
|
1128
|
+
function resolveTemplateStrict(template, answers) {
|
|
1129
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (_match, key) => {
|
|
1130
|
+
const trimmedKey = key.trim();
|
|
1131
|
+
if (!(trimmedKey in answers)) {
|
|
1132
|
+
throw new Error(`Action references unknown step "${trimmedKey}"`);
|
|
1133
|
+
}
|
|
1134
|
+
const value = answers[trimmedKey];
|
|
1135
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
1136
|
+
return String(value);
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1081
1139
|
|
|
1082
1140
|
// src/banner.ts
|
|
1083
1141
|
import figlet from "figlet";
|
|
1084
1142
|
import gradient from "gradient-string";
|
|
1085
1143
|
var GRIMOIRE_GRADIENT = gradient(["#C084FC", "#5B9BD5", "#6BCB77"]);
|
|
1086
1144
|
function renderBanner(name, theme, options) {
|
|
1145
|
+
const icon = options?.icon;
|
|
1146
|
+
const prefix = icon ? `${icon} ` : "";
|
|
1087
1147
|
if (options?.plain) {
|
|
1088
|
-
return ` ${theme.bold(name)}`;
|
|
1148
|
+
return ` ${prefix}${theme.bold(name)}`;
|
|
1089
1149
|
}
|
|
1090
1150
|
try {
|
|
1091
1151
|
const art = figlet.textSync(name, {
|
|
@@ -1093,9 +1153,11 @@ function renderBanner(name, theme, options) {
|
|
|
1093
1153
|
horizontalLayout: "default"
|
|
1094
1154
|
});
|
|
1095
1155
|
const lines = art.split("\n").map((line) => ` ${line}`).join("\n");
|
|
1096
|
-
|
|
1156
|
+
const banner = GRIMOIRE_GRADIENT(lines);
|
|
1157
|
+
return icon ? ` ${icon}
|
|
1158
|
+
${banner}` : banner;
|
|
1097
1159
|
} catch {
|
|
1098
|
-
return ` ${theme.bold(name)}`;
|
|
1160
|
+
return ` ${prefix}${theme.bold(name)}`;
|
|
1099
1161
|
}
|
|
1100
1162
|
}
|
|
1101
1163
|
|
|
@@ -1328,11 +1390,14 @@ function emitEvent(renderer, event, theme) {
|
|
|
1328
1390
|
function runPreFlightChecks(checks, theme, renderer) {
|
|
1329
1391
|
if (renderer) emitEvent(renderer, { type: "checks:start", checks }, theme);
|
|
1330
1392
|
for (const check of checks) {
|
|
1393
|
+
if (renderer) emitEvent(renderer, { type: "spinner:start", message: check.name }, theme);
|
|
1331
1394
|
try {
|
|
1332
1395
|
execSync(check.run, { stdio: "pipe" });
|
|
1396
|
+
if (renderer) emitEvent(renderer, { type: "spinner:stop", message: `${check.name}` }, theme);
|
|
1333
1397
|
console.log(` ${theme.success("\u2713")} ${check.name}`);
|
|
1334
1398
|
if (renderer) emitEvent(renderer, { type: "check:pass", name: check.name }, theme);
|
|
1335
1399
|
} catch {
|
|
1400
|
+
if (renderer) emitEvent(renderer, { type: "spinner:stop" }, theme);
|
|
1336
1401
|
console.log(` ${theme.error("\u2717")} ${check.name}: ${check.message}`);
|
|
1337
1402
|
if (renderer) emitEvent(renderer, { type: "check:fail", name: check.name, message: check.message }, theme);
|
|
1338
1403
|
throw new Error(`Pre-flight check failed: ${check.name} \u2014 ${check.message}`);
|
|
@@ -1452,8 +1517,17 @@ async function runWizard(config, options) {
|
|
|
1452
1517
|
const withTemplate = options?.templateAnswers ? applyTemplateDefaults(resolvedStep, options.templateAnswers) : resolvedStep;
|
|
1453
1518
|
const templatedStep = resolveStepTemplates(withTemplate, state.answers);
|
|
1454
1519
|
const mruStep = mruEnabled ? applyMruOrdering(templatedStep, config.meta.name) : templatedStep;
|
|
1520
|
+
let finalStep = mruStep;
|
|
1521
|
+
if (!isMock && options?.optionsProvider && isSelectLikeStep(currentStep.type)) {
|
|
1522
|
+
if (renderer) emitEvent(renderer, { type: "spinner:start", message: resolvedMessage }, theme);
|
|
1523
|
+
const dynamicOptions = await options.optionsProvider(currentStep.id, state.answers);
|
|
1524
|
+
if (renderer) emitEvent(renderer, { type: "spinner:stop", message: resolvedMessage }, theme);
|
|
1525
|
+
if (dynamicOptions) {
|
|
1526
|
+
finalStep = { ...mruStep, options: dynamicOptions };
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1455
1529
|
try {
|
|
1456
|
-
const value = isMock ? getMockValue(
|
|
1530
|
+
const value = isMock ? getMockValue(finalStep, mockAnswers) : pluginStep ? await pluginStep.render(toStepRecord(finalStep), state, theme) : await renderStep(renderer, finalStep, state, theme);
|
|
1457
1531
|
if (pluginStep?.validate) {
|
|
1458
1532
|
const pluginError = pluginStep.validate(value, toStepRecord(templatedStep));
|
|
1459
1533
|
if (pluginError) {
|
|
@@ -1569,8 +1643,13 @@ async function runWizard(config, options) {
|
|
|
1569
1643
|
if (state.status === "done" && !quiet) {
|
|
1570
1644
|
renderer.renderSummary(state.answers, config.steps, theme);
|
|
1571
1645
|
}
|
|
1572
|
-
if (state.status === "done" &&
|
|
1573
|
-
|
|
1646
|
+
if (state.status === "done" && !isMock) {
|
|
1647
|
+
if (config.onComplete) {
|
|
1648
|
+
await executeOnComplete(config.onComplete, options?.configFilePath, state.answers, config, theme, renderer);
|
|
1649
|
+
}
|
|
1650
|
+
if (config.actions && config.actions.length > 0) {
|
|
1651
|
+
await executeActions(config.actions, state.answers, theme, renderer);
|
|
1652
|
+
}
|
|
1574
1653
|
}
|
|
1575
1654
|
emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: state.status === "cancelled" }, theme);
|
|
1576
1655
|
if (state.status === "done" && cacheEnabled) {
|
|
@@ -1785,6 +1864,28 @@ function resolveStepTemplates(step, answers) {
|
|
|
1785
1864
|
};
|
|
1786
1865
|
}
|
|
1787
1866
|
}
|
|
1867
|
+
async function executeOnComplete(handlerPath, configFilePath, answers, config, theme, renderer) {
|
|
1868
|
+
if (renderer) emitEvent(renderer, { type: "oncomplete:start" }, theme);
|
|
1869
|
+
if (renderer) emitEvent(renderer, { type: "spinner:start", message: `Running onComplete handler...` }, theme);
|
|
1870
|
+
const resolvedPath = configFilePath ? resolve2(dirname2(configFilePath), handlerPath) : resolve2(handlerPath);
|
|
1871
|
+
try {
|
|
1872
|
+
const mod = await import(pathToFileURL(resolvedPath).href);
|
|
1873
|
+
if (typeof mod.default !== "function") {
|
|
1874
|
+
throw new Error(`onComplete handler "${handlerPath}" must export a default function`);
|
|
1875
|
+
}
|
|
1876
|
+
await mod.default({ answers, config });
|
|
1877
|
+
if (renderer) emitEvent(renderer, { type: "spinner:stop", message: "Handler complete" }, theme);
|
|
1878
|
+
if (renderer) emitEvent(renderer, { type: "oncomplete:pass" }, theme);
|
|
1879
|
+
} catch (error) {
|
|
1880
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1881
|
+
if (renderer) emitEvent(renderer, { type: "spinner:stop" }, theme);
|
|
1882
|
+
if (renderer) emitEvent(renderer, { type: "oncomplete:fail", error: message }, theme);
|
|
1883
|
+
console.log(`
|
|
1884
|
+
${theme.error("\u2717")} onComplete handler failed: ${message}
|
|
1885
|
+
`);
|
|
1886
|
+
throw error;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1788
1889
|
async function executeActions(actions, answers, theme, renderer) {
|
|
1789
1890
|
if (renderer) emitEvent(renderer, { type: "actions:start" }, theme);
|
|
1790
1891
|
console.log(`
|
|
@@ -1794,14 +1895,17 @@ async function executeActions(actions, answers, theme, renderer) {
|
|
|
1794
1895
|
if (action.when && !evaluateCondition(action.when, answers)) {
|
|
1795
1896
|
continue;
|
|
1796
1897
|
}
|
|
1797
|
-
const resolvedCommand =
|
|
1798
|
-
const resolvedName = action.name ?
|
|
1898
|
+
const resolvedCommand = resolveTemplateStrict(action.run, answers);
|
|
1899
|
+
const resolvedName = action.name ? resolveTemplateStrict(action.name, answers) : void 0;
|
|
1799
1900
|
const label = resolvedName ?? resolvedCommand;
|
|
1901
|
+
if (renderer) emitEvent(renderer, { type: "spinner:start", message: label }, theme);
|
|
1800
1902
|
try {
|
|
1801
1903
|
execSync(resolvedCommand, { stdio: "pipe" });
|
|
1904
|
+
if (renderer) emitEvent(renderer, { type: "spinner:stop", message: label }, theme);
|
|
1802
1905
|
console.log(` ${theme.success("\u2713")} ${label}`);
|
|
1803
1906
|
if (renderer) emitEvent(renderer, { type: "action:pass", name: label }, theme);
|
|
1804
1907
|
} catch {
|
|
1908
|
+
if (renderer) emitEvent(renderer, { type: "spinner:stop" }, theme);
|
|
1805
1909
|
console.log(` ${theme.error("\u2717")} ${label}`);
|
|
1806
1910
|
if (renderer) emitEvent(renderer, { type: "action:fail", name: label }, theme);
|
|
1807
1911
|
throw new Error(`Action failed: ${label}`);
|
|
@@ -1811,7 +1915,7 @@ async function executeActions(actions, answers, theme, renderer) {
|
|
|
1811
1915
|
}
|
|
1812
1916
|
function printWizardHeader(config, theme, plain) {
|
|
1813
1917
|
console.log();
|
|
1814
|
-
console.log(renderBanner(config.meta.name, theme, { plain }));
|
|
1918
|
+
console.log(renderBanner(config.meta.name, theme, { plain, icon: config.meta.icon }));
|
|
1815
1919
|
if (config.meta.description) {
|
|
1816
1920
|
console.log(` ${theme.muted(config.meta.description)}`);
|
|
1817
1921
|
}
|
|
@@ -2392,7 +2496,6 @@ var S_STEP_ERROR = u("\u25B2", "x");
|
|
|
2392
2496
|
var S_CORNER_TR = u("\u256E", "+");
|
|
2393
2497
|
var S_CORNER_BR = u("\u256F", "+");
|
|
2394
2498
|
var S_BAR_H = u("\u2500", "-");
|
|
2395
|
-
var S_SPINNER_FRAMES = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"];
|
|
2396
2499
|
|
|
2397
2500
|
// src/renderers/clack.ts
|
|
2398
2501
|
var ClackRenderer = class extends InquirerRenderer {
|
|
@@ -2531,13 +2634,14 @@ var ClackRenderer = class extends InquirerRenderer {
|
|
|
2531
2634
|
process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256E${bottomLine}`)}
|
|
2532
2635
|
`);
|
|
2533
2636
|
}
|
|
2534
|
-
startSpinner(message,
|
|
2637
|
+
startSpinner(message, theme) {
|
|
2535
2638
|
this.spinnerFrameIndex = 0;
|
|
2639
|
+
const { frames, interval } = theme.spinner;
|
|
2536
2640
|
this.spinnerInterval = setInterval(() => {
|
|
2537
|
-
const frame =
|
|
2641
|
+
const frame = frames[this.spinnerFrameIndex % frames.length];
|
|
2538
2642
|
process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}`);
|
|
2539
2643
|
this.spinnerFrameIndex++;
|
|
2540
|
-
},
|
|
2644
|
+
}, interval);
|
|
2541
2645
|
}
|
|
2542
2646
|
stopSpinner(message, theme) {
|
|
2543
2647
|
if (this.spinnerInterval) {
|
|
@@ -2552,7 +2656,7 @@ var ClackRenderer = class extends InquirerRenderer {
|
|
|
2552
2656
|
|
|
2553
2657
|
// src/cli.ts
|
|
2554
2658
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
2555
|
-
import { resolve as
|
|
2659
|
+
import { resolve as resolve3 } from "path";
|
|
2556
2660
|
import { fileURLToPath } from "url";
|
|
2557
2661
|
import { stringify as yamlStringify } from "yaml";
|
|
2558
2662
|
var plainMode = false;
|
|
@@ -2568,7 +2672,7 @@ program.name("grimoire").description("Config-driven CLI wizard framework").versi
|
|
|
2568
2672
|
});
|
|
2569
2673
|
program.command("run").description("Run a wizard from a config file").argument("<config>", "Path to wizard config file (.yaml, .json, .js, .ts)").option("-o, --output <path>", "Write answers to file").option("-f, --format <format>", "Output format: json, env, yaml", "json").option("-q, --quiet", "Suppress header and summary output").option("--dry-run", "Show step plan without running the wizard").option("--mock <json>", "Run wizard with preset answers (JSON string)").option("--json", "Output structured JSON result to stdout").option("--no-cache", "Disable answer caching for this run").option("--no-resume", "Disable progress resume for this run").option("--renderer <type>", "Renderer to use: inquirer (default), ink, or clack", "inquirer").option("--template <name>", "Load a saved template as defaults").action(async (configPath, opts) => {
|
|
2570
2674
|
try {
|
|
2571
|
-
const fullPath =
|
|
2675
|
+
const fullPath = resolve3(configPath);
|
|
2572
2676
|
const config = await loadWizardConfig(fullPath);
|
|
2573
2677
|
if (opts.dryRun) {
|
|
2574
2678
|
printDryRun(config);
|
|
@@ -2594,10 +2698,11 @@ program.command("run").description("Run a wizard from a config file").argument("
|
|
|
2594
2698
|
mockAnswers,
|
|
2595
2699
|
templateAnswers,
|
|
2596
2700
|
cache: opts.cache,
|
|
2597
|
-
resume: opts.resume
|
|
2701
|
+
resume: opts.resume,
|
|
2702
|
+
configFilePath: fullPath
|
|
2598
2703
|
});
|
|
2599
2704
|
const rawOutputPath = opts.output ?? config.output?.path;
|
|
2600
|
-
const outputPath = rawOutputPath ?
|
|
2705
|
+
const outputPath = rawOutputPath ? resolve3(resolveTemplate(rawOutputPath, answers)) : void 0;
|
|
2601
2706
|
if (isJsonOutput) {
|
|
2602
2707
|
const stepsCompleted = Object.keys(answers).length;
|
|
2603
2708
|
const result = {
|
|
@@ -2643,7 +2748,7 @@ program.command("run").description("Run a wizard from a config file").argument("
|
|
|
2643
2748
|
});
|
|
2644
2749
|
program.command("validate").description("Validate a wizard config file without running it").argument("<config>", "Path to wizard config file").action(async (configPath) => {
|
|
2645
2750
|
try {
|
|
2646
|
-
const fullPath =
|
|
2751
|
+
const fullPath = resolve3(configPath);
|
|
2647
2752
|
const config = await loadWizardConfig(fullPath);
|
|
2648
2753
|
console.log(`
|
|
2649
2754
|
\u2713 Valid wizard config: "${config.meta.name}"`);
|
|
@@ -2660,7 +2765,7 @@ program.command("validate").description("Validate a wizard config file without r
|
|
|
2660
2765
|
});
|
|
2661
2766
|
program.command("create").description("Interactively scaffold a new wizard config file").argument("[output]", "Output file path", "wizard.yaml").action(async (output) => {
|
|
2662
2767
|
try {
|
|
2663
|
-
const resolvedPath =
|
|
2768
|
+
const resolvedPath = resolve3(output);
|
|
2664
2769
|
await scaffoldWizard(resolvedPath);
|
|
2665
2770
|
} catch (error) {
|
|
2666
2771
|
if (error instanceof Error) {
|
|
@@ -2673,7 +2778,7 @@ program.command("create").description("Interactively scaffold a new wizard confi
|
|
|
2673
2778
|
});
|
|
2674
2779
|
program.command("demo").description("Run a demo wizard showcasing all step types").action(async () => {
|
|
2675
2780
|
try {
|
|
2676
|
-
const demoPath =
|
|
2781
|
+
const demoPath = resolve3(
|
|
2677
2782
|
fileURLToPath(import.meta.url),
|
|
2678
2783
|
"..",
|
|
2679
2784
|
"..",
|
|
@@ -2812,6 +2917,11 @@ function printDryRun(config) {
|
|
|
2812
2917
|
console.log(` Step ${num} ${typeStr} ${idStr} ${msg}${suffix}`);
|
|
2813
2918
|
}
|
|
2814
2919
|
console.log();
|
|
2920
|
+
if (config.onComplete) {
|
|
2921
|
+
console.log(` ${theme.bold("onComplete handler:")}`);
|
|
2922
|
+
console.log(` ${theme.muted(config.onComplete)}`);
|
|
2923
|
+
console.log();
|
|
2924
|
+
}
|
|
2815
2925
|
if (config.actions && config.actions.length > 0) {
|
|
2816
2926
|
console.log(` ${theme.bold("Post-wizard actions:")}`);
|
|
2817
2927
|
for (const action of config.actions) {
|