grimoire-wizard 0.4.0 → 0.5.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/index.d.ts CHANGED
@@ -164,6 +164,10 @@ interface ThemeConfig {
164
164
  stepPending?: string;
165
165
  pointer?: string;
166
166
  };
167
+ spinner?: string | {
168
+ frames: string[];
169
+ interval?: number;
170
+ };
167
171
  }
168
172
  interface PreFlightCheck {
169
173
  name: string;
@@ -175,6 +179,10 @@ interface ActionConfig {
175
179
  run: string;
176
180
  when?: Condition;
177
181
  }
182
+ type OnCompleteHandler = (context: {
183
+ answers: Record<string, unknown>;
184
+ config: WizardConfig;
185
+ }) => Promise<void> | void;
178
186
  interface WizardConfig {
179
187
  meta: {
180
188
  name: string;
@@ -191,6 +199,7 @@ interface WizardConfig {
191
199
  extends?: string;
192
200
  checks?: PreFlightCheck[];
193
201
  actions?: ActionConfig[];
202
+ onComplete?: string;
194
203
  }
195
204
  interface WizardState {
196
205
  currentStepId: string;
@@ -268,6 +277,13 @@ type WizardEvent = {
268
277
  } | {
269
278
  type: 'action:fail';
270
279
  name: string;
280
+ } | {
281
+ type: 'oncomplete:start';
282
+ } | {
283
+ type: 'oncomplete:pass';
284
+ } | {
285
+ type: 'oncomplete:fail';
286
+ error: string;
271
287
  };
272
288
  interface ResolvedTheme {
273
289
  primary: (text: string) => string;
@@ -279,6 +295,10 @@ interface ResolvedTheme {
279
295
  accent: (text: string) => string;
280
296
  bold: (text: string) => string;
281
297
  icons: Required<NonNullable<ThemeConfig['icons']>>;
298
+ spinner: {
299
+ frames: string[];
300
+ interval: number;
301
+ };
282
302
  }
283
303
  interface WizardRenderer {
284
304
  renderText(step: TextStepConfig, state: WizardState, theme: ResolvedTheme): Promise<string>;
@@ -315,6 +335,79 @@ declare function wizardReducer(state: WizardState, transition: WizardTransition,
315
335
 
316
336
  declare function resolveTheme(themeConfig?: ThemeConfig): ResolvedTheme;
317
337
 
338
+ interface SpinnerConfig {
339
+ frames: string[];
340
+ interval: number;
341
+ }
342
+ declare const spinners: {
343
+ readonly dots: {
344
+ readonly interval: 80;
345
+ readonly frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
346
+ };
347
+ readonly dots2: {
348
+ readonly interval: 80;
349
+ readonly frames: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"];
350
+ };
351
+ readonly line: {
352
+ readonly interval: 130;
353
+ readonly frames: ["-", "\\", "|", "/"];
354
+ };
355
+ readonly arc: {
356
+ readonly interval: 100;
357
+ readonly frames: ["◜", "◠", "◝", "◞", "◡", "◟"];
358
+ };
359
+ readonly circle: {
360
+ readonly interval: 80;
361
+ readonly frames: ["◒", "◐", "◓", "◑"];
362
+ };
363
+ readonly circleHalves: {
364
+ readonly interval: 50;
365
+ readonly frames: ["◐", "◓", "◑", "◒"];
366
+ };
367
+ readonly triangle: {
368
+ readonly interval: 50;
369
+ readonly frames: ["◢", "◣", "◤", "◥"];
370
+ };
371
+ readonly pipe: {
372
+ readonly interval: 100;
373
+ readonly frames: ["┤", "┘", "┴", "└", "├", "┌", "┬", "┐"];
374
+ };
375
+ readonly arrow: {
376
+ readonly interval: 100;
377
+ readonly frames: ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"];
378
+ };
379
+ readonly arrow3: {
380
+ readonly interval: 120;
381
+ readonly frames: ["▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"];
382
+ };
383
+ readonly bouncingBar: {
384
+ readonly interval: 80;
385
+ readonly frames: ["[ ]", "[= ]", "[== ]", "[=== ]", "[====]", "[ ===]", "[ ==]", "[ =]", "[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"];
386
+ };
387
+ readonly bouncingBall: {
388
+ readonly interval: 80;
389
+ readonly frames: ["( ● )", "( ● )", "( ● )", "( ● )", "( ●)", "( ● )", "( ● )", "( ● )", "( ● )", "(● )"];
390
+ };
391
+ readonly simpleDots: {
392
+ readonly interval: 400;
393
+ readonly frames: [". ", ".. ", "...", " "];
394
+ };
395
+ readonly aesthetic: {
396
+ readonly interval: 80;
397
+ readonly frames: ["▰▱▱▱▱▱▱", "▰▰▱▱▱▱▱", "▰▰▰▱▱▱▱", "▰▰▰▰▱▱▱", "▰▰▰▰▰▱▱", "▰▰▰▰▰▰▱", "▰▰▰▰▰▰▰", "▰▱▱▱▱▱▱"];
398
+ };
399
+ readonly star: {
400
+ readonly interval: 70;
401
+ readonly frames: ["✶", "✸", "✹", "✺", "✹", "✷"];
402
+ };
403
+ };
404
+ type SpinnerName = keyof typeof spinners;
405
+ declare const DEFAULT_SPINNER: SpinnerName;
406
+ declare function resolveSpinner(config?: string | {
407
+ frames: string[];
408
+ interval?: number;
409
+ }): SpinnerConfig;
410
+
318
411
  declare function resolveEnvDefault(value: string | undefined): string | undefined;
319
412
 
320
413
  interface StepPlugin {
@@ -346,6 +439,7 @@ interface RunWizardOptions {
346
439
  };
347
440
  mru?: boolean;
348
441
  resume?: boolean;
442
+ configFilePath?: string;
349
443
  }
350
444
  declare function runPreFlightChecks(checks: PreFlightCheck[], theme: ResolvedTheme, renderer?: WizardRenderer): void;
351
445
  declare function runWizard(config: WizardConfig, options?: RunWizardOptions): Promise<Record<string, unknown>>;
@@ -375,6 +469,12 @@ declare class InquirerRenderer implements WizardRenderer {
375
469
  * Array values are joined with ", ". Unresolved placeholders remain as-is.
376
470
  */
377
471
  declare function resolveTemplate(template: string, answers: Record<string, unknown>): string;
472
+ /**
473
+ * Resolve {{stepId}} placeholders strictly — throws if any placeholder
474
+ * references a step-id not found in the answers map.
475
+ * Used by actions where missing answers indicate a config error.
476
+ */
477
+ declare function resolveTemplateStrict(template: string, answers: Record<string, unknown>): string;
378
478
 
379
479
  /**
380
480
  * Render a figlet ASCII art banner for the wizard name.
@@ -455,4 +555,4 @@ interface PipelineStep {
455
555
  }
456
556
  declare function runPipeline(steps: PipelineStep[], globalOptions?: Omit<RunWizardOptions, 'mockAnswers' | 'templateAnswers'>): Promise<Record<string, Record<string, unknown>>>;
457
557
 
458
- export { type ActionConfig, ClackRenderer, type Condition, type ConfirmStepConfig, type EditorStepConfig, type GrimoirePlugin, InkRenderer, InquirerRenderer, type MessageStepConfig, type MultiSelectStepConfig, type NoteStepConfig, type NumberStepConfig, type PasswordStepConfig, type PathStepConfig, type PipelineStep, type PreFlightCheck, type ResolvedTheme, type RunWizardOptions, type SavedProgress, type SearchStepConfig, type SelectChoice, type SelectOption, type SelectStepConfig, type SeparatorOption, type StepConfig, type StepPlugin, type TextStepConfig, type ThemeConfig, type ToggleStepConfig, type ValidationRule, type WizardConfig, type WizardEvent, type WizardRenderer, type WizardState, type WizardTransition, clearCache, clearMruData, clearPlugins, clearProgress, createWizardState, defineWizard, deleteTemplate, evaluateCondition, getCacheDir, getOrderedOptions, getPluginStep, getVisibleSteps, isStepVisible, listTemplates, loadCachedAnswers, loadProgress, loadTemplate, loadWizardConfig, parseWizardConfig, parseWizardYAML, recordSelection, registerPlugin, renderBanner, resolveEnvDefault, resolveNextStep, resolveTemplate, resolveTheme, runPipeline, runPreFlightChecks, runWizard, saveCachedAnswers, saveProgress, saveTemplate, slugify, validateStepAnswer, wizardReducer };
558
+ export { type ActionConfig, ClackRenderer, type Condition, type ConfirmStepConfig, DEFAULT_SPINNER, type EditorStepConfig, type GrimoirePlugin, InkRenderer, InquirerRenderer, type MessageStepConfig, type MultiSelectStepConfig, type NoteStepConfig, type NumberStepConfig, type OnCompleteHandler, type PasswordStepConfig, type PathStepConfig, type PipelineStep, type PreFlightCheck, type ResolvedTheme, type RunWizardOptions, type SavedProgress, type SearchStepConfig, type SelectChoice, type SelectOption, type SelectStepConfig, type SeparatorOption, type SpinnerConfig, type SpinnerName, type StepConfig, type StepPlugin, type TextStepConfig, type ThemeConfig, type ToggleStepConfig, type ValidationRule, type WizardConfig, type WizardEvent, type WizardRenderer, type WizardState, type WizardTransition, clearCache, clearMruData, clearPlugins, clearProgress, createWizardState, defineWizard, deleteTemplate, evaluateCondition, getCacheDir, getOrderedOptions, getPluginStep, getVisibleSteps, isStepVisible, listTemplates, loadCachedAnswers, loadProgress, loadTemplate, loadWizardConfig, parseWizardConfig, parseWizardYAML, recordSelection, registerPlugin, renderBanner, resolveEnvDefault, resolveNextStep, resolveSpinner, resolveTemplate, resolveTemplateStrict, resolveTheme, runPipeline, runPreFlightChecks, runWizard, saveCachedAnswers, saveProgress, saveTemplate, slugify, spinners, validateStepAnswer, wizardReducer };
package/dist/index.js CHANGED
@@ -218,7 +218,14 @@ var init_schema = __esm({
218
218
  stepDone: z.string().optional(),
219
219
  stepPending: z.string().optional(),
220
220
  pointer: z.string().optional()
221
- }).optional()
221
+ }).optional(),
222
+ spinner: z.union([
223
+ z.string(),
224
+ z.object({
225
+ frames: z.array(z.string()).min(1),
226
+ interval: z.number().positive().optional()
227
+ })
228
+ ]).optional()
222
229
  });
223
230
  preFlightCheckSchema = z.object({
224
231
  name: z.string(),
@@ -245,7 +252,8 @@ var init_schema = __esm({
245
252
  }).optional(),
246
253
  extends: z.string().optional(),
247
254
  checks: z.array(preFlightCheckSchema).optional(),
248
- actions: z.array(actionConfigSchema).optional()
255
+ actions: z.array(actionConfigSchema).optional(),
256
+ onComplete: z.string().optional()
249
257
  }).superRefine((config, ctx) => {
250
258
  const stepIds = /* @__PURE__ */ new Set();
251
259
  for (const step of config.steps) {
@@ -869,6 +877,41 @@ var THEME_PRESETS = {
869
877
  };
870
878
  var PRESET_NAMES = Object.keys(THEME_PRESETS);
871
879
 
880
+ // src/spinners.ts
881
+ var spinners = {
882
+ dots: { interval: 80, frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] },
883
+ dots2: { interval: 80, frames: ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"] },
884
+ line: { interval: 130, frames: ["-", "\\", "|", "/"] },
885
+ arc: { interval: 100, frames: ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"] },
886
+ circle: { interval: 80, frames: ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] },
887
+ circleHalves: { interval: 50, frames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"] },
888
+ triangle: { interval: 50, frames: ["\u25E2", "\u25E3", "\u25E4", "\u25E5"] },
889
+ pipe: { interval: 100, frames: ["\u2524", "\u2518", "\u2534", "\u2514", "\u251C", "\u250C", "\u252C", "\u2510"] },
890
+ arrow: { interval: 100, frames: ["\u2190", "\u2196", "\u2191", "\u2197", "\u2192", "\u2198", "\u2193", "\u2199"] },
891
+ 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"] },
892
+ bouncingBar: { interval: 80, frames: ["[ ]", "[= ]", "[== ]", "[=== ]", "[====]", "[ ===]", "[ ==]", "[ =]", "[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"] },
893
+ bouncingBall: { interval: 80, frames: ["( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF)", "( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF )", "(\u25CF )"] },
894
+ simpleDots: { interval: 400, frames: [". ", ".. ", "...", " "] },
895
+ 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"] },
896
+ star: { interval: 70, frames: ["\u2736", "\u2738", "\u2739", "\u273A", "\u2739", "\u2737"] }
897
+ };
898
+ var DEFAULT_SPINNER = "circle";
899
+ function resolveSpinner(config) {
900
+ if (!config) {
901
+ return spinners[DEFAULT_SPINNER];
902
+ }
903
+ if (typeof config === "string") {
904
+ if (config in spinners) {
905
+ return spinners[config];
906
+ }
907
+ throw new Error(`Unknown spinner preset: "${config}". Available: ${Object.keys(spinners).join(", ")}`);
908
+ }
909
+ return {
910
+ frames: config.frames,
911
+ interval: config.interval ?? 80
912
+ };
913
+ }
914
+
872
915
  // src/theme.ts
873
916
  var DEFAULT_TOKENS = {
874
917
  primary: "#5B9BD5",
@@ -898,7 +941,8 @@ function resolveTheme(themeConfig) {
898
941
  muted: chalk.hex(tokens.muted),
899
942
  accent: chalk.hex(tokens.accent),
900
943
  bold: chalk.bold,
901
- icons
944
+ icons,
945
+ spinner: resolveSpinner(themeConfig?.spinner)
902
946
  };
903
947
  }
904
948
 
@@ -925,6 +969,8 @@ function resolveEnvDefaultBoolean(value) {
925
969
 
926
970
  // src/runner.ts
927
971
  import { execSync } from "child_process";
972
+ import { resolve as resolve2, dirname as dirname2 } from "path";
973
+ import { pathToFileURL } from "url";
928
974
 
929
975
  // src/renderers/inquirer.ts
930
976
  import {
@@ -1125,6 +1171,17 @@ function resolveTemplate(template, answers) {
1125
1171
  return _match;
1126
1172
  });
1127
1173
  }
1174
+ function resolveTemplateStrict(template, answers) {
1175
+ return template.replace(/\{\{([^}]+)\}\}/g, (_match, key) => {
1176
+ const trimmedKey = key.trim();
1177
+ if (!(trimmedKey in answers)) {
1178
+ throw new Error(`Action references unknown step "${trimmedKey}"`);
1179
+ }
1180
+ const value = answers[trimmedKey];
1181
+ if (Array.isArray(value)) return value.join(", ");
1182
+ return String(value);
1183
+ });
1184
+ }
1128
1185
 
1129
1186
  // src/banner.ts
1130
1187
  import figlet from "figlet";
@@ -1625,8 +1682,13 @@ async function runWizard(config, options) {
1625
1682
  if (state.status === "done" && !quiet) {
1626
1683
  renderer.renderSummary(state.answers, config.steps, theme);
1627
1684
  }
1628
- if (state.status === "done" && config.actions && config.actions.length > 0 && !isMock) {
1629
- await executeActions(config.actions, state.answers, theme, renderer);
1685
+ if (state.status === "done" && !isMock) {
1686
+ if (config.onComplete) {
1687
+ await executeOnComplete(config.onComplete, options?.configFilePath, state.answers, config, theme, renderer);
1688
+ }
1689
+ if (config.actions && config.actions.length > 0) {
1690
+ await executeActions(config.actions, state.answers, theme, renderer);
1691
+ }
1630
1692
  }
1631
1693
  emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: state.status === "cancelled" }, theme);
1632
1694
  if (state.status === "done" && cacheEnabled) {
@@ -1841,6 +1903,25 @@ function resolveStepTemplates(step, answers) {
1841
1903
  };
1842
1904
  }
1843
1905
  }
1906
+ async function executeOnComplete(handlerPath, configFilePath, answers, config, theme, renderer) {
1907
+ if (renderer) emitEvent(renderer, { type: "oncomplete:start" }, theme);
1908
+ const resolvedPath = configFilePath ? resolve2(dirname2(configFilePath), handlerPath) : resolve2(handlerPath);
1909
+ try {
1910
+ const mod = await import(pathToFileURL(resolvedPath).href);
1911
+ if (typeof mod.default !== "function") {
1912
+ throw new Error(`onComplete handler "${handlerPath}" must export a default function`);
1913
+ }
1914
+ await mod.default({ answers, config });
1915
+ if (renderer) emitEvent(renderer, { type: "oncomplete:pass" }, theme);
1916
+ } catch (error) {
1917
+ const message = error instanceof Error ? error.message : String(error);
1918
+ if (renderer) emitEvent(renderer, { type: "oncomplete:fail", error: message }, theme);
1919
+ console.log(`
1920
+ ${theme.error("\u2717")} onComplete handler failed: ${message}
1921
+ `);
1922
+ throw error;
1923
+ }
1924
+ }
1844
1925
  async function executeActions(actions, answers, theme, renderer) {
1845
1926
  if (renderer) emitEvent(renderer, { type: "actions:start" }, theme);
1846
1927
  console.log(`
@@ -1850,8 +1931,8 @@ async function executeActions(actions, answers, theme, renderer) {
1850
1931
  if (action.when && !evaluateCondition(action.when, answers)) {
1851
1932
  continue;
1852
1933
  }
1853
- const resolvedCommand = resolveTemplate(action.run, answers);
1854
- const resolvedName = action.name ? resolveTemplate(action.name, answers) : void 0;
1934
+ const resolvedCommand = resolveTemplateStrict(action.run, answers);
1935
+ const resolvedName = action.name ? resolveTemplateStrict(action.name, answers) : void 0;
1855
1936
  const label = resolvedName ?? resolvedCommand;
1856
1937
  try {
1857
1938
  execSync(resolvedCommand, { stdio: "pipe" });
@@ -2102,7 +2183,6 @@ var S_STEP_ERROR = u("\u25B2", "x");
2102
2183
  var S_CORNER_TR = u("\u256E", "+");
2103
2184
  var S_CORNER_BR = u("\u256F", "+");
2104
2185
  var S_BAR_H = u("\u2500", "-");
2105
- var S_SPINNER_FRAMES = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"];
2106
2186
 
2107
2187
  // src/renderers/clack.ts
2108
2188
  var ClackRenderer = class extends InquirerRenderer {
@@ -2241,13 +2321,14 @@ var ClackRenderer = class extends InquirerRenderer {
2241
2321
  process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256E${bottomLine}`)}
2242
2322
  `);
2243
2323
  }
2244
- startSpinner(message, _theme) {
2324
+ startSpinner(message, theme) {
2245
2325
  this.spinnerFrameIndex = 0;
2326
+ const { frames, interval } = theme.spinner;
2246
2327
  this.spinnerInterval = setInterval(() => {
2247
- const frame = S_SPINNER_FRAMES[this.spinnerFrameIndex % S_SPINNER_FRAMES.length];
2328
+ const frame = frames[this.spinnerFrameIndex % frames.length];
2248
2329
  process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}`);
2249
2330
  this.spinnerFrameIndex++;
2250
- }, 80);
2331
+ }, interval);
2251
2332
  }
2252
2333
  stopSpinner(message, theme) {
2253
2334
  if (this.spinnerInterval) {
@@ -2345,6 +2426,7 @@ async function runPipeline(steps, globalOptions) {
2345
2426
  }
2346
2427
  export {
2347
2428
  ClackRenderer,
2429
+ DEFAULT_SPINNER,
2348
2430
  InkRenderer,
2349
2431
  InquirerRenderer,
2350
2432
  clearCache,
@@ -2372,7 +2454,9 @@ export {
2372
2454
  renderBanner,
2373
2455
  resolveEnvDefault,
2374
2456
  resolveNextStep,
2457
+ resolveSpinner,
2375
2458
  resolveTemplate,
2459
+ resolveTemplateStrict,
2376
2460
  resolveTheme,
2377
2461
  runPipeline,
2378
2462
  runPreFlightChecks,
@@ -2381,6 +2465,7 @@ export {
2381
2465
  saveProgress,
2382
2466
  saveTemplate,
2383
2467
  slugify,
2468
+ spinners,
2384
2469
  validateStepAnswer,
2385
2470
  wizardReducer
2386
2471
  };