grimoire-wizard 0.3.1 → 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/README.md +185 -18
- package/dist/cli.js +602 -109
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +207 -3
- package/dist/index.js +921 -371
- package/dist/index.js.map +1 -1
- package/examples/handlers/setup-project.ts +9 -0
- package/examples/json/all-features.json +66 -0
- package/examples/json/appstore-screenshot-wizard.json +362 -0
- package/examples/json/appstore-upload.json +104 -0
- package/examples/json/basic.json +72 -0
- package/examples/json/batch-generate.json +186 -0
- package/examples/json/brief-builder.json +519 -0
- package/examples/json/conditional.json +155 -0
- package/examples/json/cost-analyzer.json +83 -0
- package/examples/json/demo.json +130 -0
- package/examples/json/scraper-selector.json +63 -0
- package/examples/json/themed-catppuccin.json +39 -0
- package/examples/json/themed.json +103 -0
- package/examples/json/with-actions.json +61 -0
- package/examples/json/with-checks.json +47 -0
- package/examples/json/with-oncomplete.json +45 -0
- package/examples/yaml/appstore-screenshot-wizard.yaml +321 -0
- package/examples/yaml/appstore-upload.yaml +84 -0
- package/examples/yaml/batch-generate.yaml +156 -0
- package/examples/yaml/brief-builder.yaml +429 -0
- package/examples/yaml/cost-analyzer.yaml +69 -0
- package/examples/yaml/pipeline.yaml +35 -0
- package/examples/yaml/scraper-selector.yaml +52 -0
- package/examples/yaml/themed-catppuccin.yaml +31 -0
- package/examples/yaml/with-actions.yaml +45 -0
- package/examples/yaml/with-oncomplete.yaml +35 -0
- package/package.json +1 -1
- /package/examples/{all-features.yaml → yaml/all-features.yaml} +0 -0
- /package/examples/{base.yaml → yaml/base.yaml} +0 -0
- /package/examples/{basic.yaml → yaml/basic.yaml} +0 -0
- /package/examples/{conditional.yaml → yaml/conditional.yaml} +0 -0
- /package/examples/{demo.yaml → yaml/demo.yaml} +0 -0
- /package/examples/{ebay-mcp-setup.yaml → yaml/ebay-mcp-setup.yaml} +0 -0
- /package/examples/{extended.yaml → yaml/extended.yaml} +0 -0
- /package/examples/{themed.yaml → yaml/themed.yaml} +0 -0
- /package/examples/{with-checks.yaml → yaml/with-checks.yaml} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import chalk3 from "chalk";
|
|
5
5
|
import { program } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/parser.ts
|
|
@@ -146,6 +146,10 @@ var messageStepSchema = z.object({
|
|
|
146
146
|
...baseStepFields,
|
|
147
147
|
type: z.literal("message")
|
|
148
148
|
});
|
|
149
|
+
var noteStepSchema = z.object({
|
|
150
|
+
...baseStepFields,
|
|
151
|
+
type: z.literal("note")
|
|
152
|
+
});
|
|
149
153
|
var stepConfigSchema = z.discriminatedUnion("type", [
|
|
150
154
|
textStepSchema,
|
|
151
155
|
selectStepSchema,
|
|
@@ -157,13 +161,15 @@ var stepConfigSchema = z.discriminatedUnion("type", [
|
|
|
157
161
|
editorStepSchema,
|
|
158
162
|
pathStepSchema,
|
|
159
163
|
toggleStepSchema,
|
|
160
|
-
messageStepSchema
|
|
164
|
+
messageStepSchema,
|
|
165
|
+
noteStepSchema
|
|
161
166
|
]);
|
|
162
167
|
var hexColorSchema = z.string().regex(
|
|
163
168
|
/^#[0-9a-fA-F]{6}$/,
|
|
164
169
|
"Must be a 6-digit hex color (e.g., #FF0000)"
|
|
165
170
|
);
|
|
166
171
|
var themeConfigSchema = z.object({
|
|
172
|
+
preset: z.enum(["default", "catppuccin", "dracula", "nord", "tokyonight", "monokai"]).optional(),
|
|
167
173
|
tokens: z.object({
|
|
168
174
|
primary: hexColorSchema.optional(),
|
|
169
175
|
success: hexColorSchema.optional(),
|
|
@@ -178,7 +184,14 @@ var themeConfigSchema = z.object({
|
|
|
178
184
|
stepDone: z.string().optional(),
|
|
179
185
|
stepPending: z.string().optional(),
|
|
180
186
|
pointer: z.string().optional()
|
|
181
|
-
}).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()
|
|
182
195
|
});
|
|
183
196
|
var preFlightCheckSchema = z.object({
|
|
184
197
|
name: z.string(),
|
|
@@ -194,7 +207,8 @@ var wizardConfigSchema = z.object({
|
|
|
194
207
|
meta: z.object({
|
|
195
208
|
name: z.string(),
|
|
196
209
|
version: z.string().optional(),
|
|
197
|
-
description: z.string().optional()
|
|
210
|
+
description: z.string().optional(),
|
|
211
|
+
review: z.boolean().optional()
|
|
198
212
|
}),
|
|
199
213
|
theme: themeConfigSchema.optional(),
|
|
200
214
|
steps: z.array(stepConfigSchema).min(1),
|
|
@@ -204,7 +218,8 @@ var wizardConfigSchema = z.object({
|
|
|
204
218
|
}).optional(),
|
|
205
219
|
extends: z.string().optional(),
|
|
206
220
|
checks: z.array(preFlightCheckSchema).optional(),
|
|
207
|
-
actions: z.array(actionConfigSchema).optional()
|
|
221
|
+
actions: z.array(actionConfigSchema).optional(),
|
|
222
|
+
onComplete: z.string().optional()
|
|
208
223
|
}).superRefine((config, ctx) => {
|
|
209
224
|
const stepIds = /* @__PURE__ */ new Set();
|
|
210
225
|
for (const step of config.steps) {
|
|
@@ -479,6 +494,8 @@ async function loadWizardConfig(filePath) {
|
|
|
479
494
|
|
|
480
495
|
// src/runner.ts
|
|
481
496
|
import { execSync } from "child_process";
|
|
497
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
498
|
+
import { pathToFileURL } from "url";
|
|
482
499
|
|
|
483
500
|
// src/conditions.ts
|
|
484
501
|
function isRecord(value) {
|
|
@@ -758,6 +775,102 @@ function applyValidationRule(rule, value) {
|
|
|
758
775
|
|
|
759
776
|
// src/theme.ts
|
|
760
777
|
import chalk from "chalk";
|
|
778
|
+
|
|
779
|
+
// src/themes/presets.ts
|
|
780
|
+
var THEME_PRESETS = {
|
|
781
|
+
default: {
|
|
782
|
+
primary: "#7C3AED",
|
|
783
|
+
success: "#10B981",
|
|
784
|
+
error: "#EF4444",
|
|
785
|
+
warning: "#F59E0B",
|
|
786
|
+
info: "#3B82F6",
|
|
787
|
+
muted: "#6B7280",
|
|
788
|
+
accent: "#8B5CF6"
|
|
789
|
+
},
|
|
790
|
+
catppuccin: {
|
|
791
|
+
primary: "#cba6f7",
|
|
792
|
+
success: "#a6e3a1",
|
|
793
|
+
error: "#f38ba8",
|
|
794
|
+
warning: "#fab387",
|
|
795
|
+
info: "#74c7ec",
|
|
796
|
+
muted: "#6c7086",
|
|
797
|
+
accent: "#f5c2e7"
|
|
798
|
+
},
|
|
799
|
+
dracula: {
|
|
800
|
+
primary: "#bd93f9",
|
|
801
|
+
success: "#50fa7b",
|
|
802
|
+
error: "#ff5555",
|
|
803
|
+
warning: "#ffb86c",
|
|
804
|
+
info: "#8be9fd",
|
|
805
|
+
muted: "#6272a4",
|
|
806
|
+
accent: "#ff79c6"
|
|
807
|
+
},
|
|
808
|
+
nord: {
|
|
809
|
+
primary: "#88c0d0",
|
|
810
|
+
success: "#a3be8c",
|
|
811
|
+
error: "#bf616a",
|
|
812
|
+
warning: "#ebcb8b",
|
|
813
|
+
info: "#81a1c1",
|
|
814
|
+
muted: "#4c566a",
|
|
815
|
+
accent: "#b48ead"
|
|
816
|
+
},
|
|
817
|
+
tokyonight: {
|
|
818
|
+
primary: "#7aa2f7",
|
|
819
|
+
success: "#9ece6a",
|
|
820
|
+
error: "#f7768e",
|
|
821
|
+
warning: "#e0af68",
|
|
822
|
+
info: "#7dcfff",
|
|
823
|
+
muted: "#565f89",
|
|
824
|
+
accent: "#bb9af7"
|
|
825
|
+
},
|
|
826
|
+
monokai: {
|
|
827
|
+
primary: "#ab9df2",
|
|
828
|
+
success: "#a9dc76",
|
|
829
|
+
error: "#ff6188",
|
|
830
|
+
warning: "#ffd866",
|
|
831
|
+
info: "#78dce8",
|
|
832
|
+
muted: "#727072",
|
|
833
|
+
accent: "#fc9867"
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
var PRESET_NAMES = Object.keys(THEME_PRESETS);
|
|
837
|
+
|
|
838
|
+
// src/spinners.ts
|
|
839
|
+
var spinners = {
|
|
840
|
+
dots: { interval: 80, frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"] },
|
|
841
|
+
dots2: { interval: 80, frames: ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"] },
|
|
842
|
+
line: { interval: 130, frames: ["-", "\\", "|", "/"] },
|
|
843
|
+
arc: { interval: 100, frames: ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"] },
|
|
844
|
+
circle: { interval: 80, frames: ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] },
|
|
845
|
+
circleHalves: { interval: 50, frames: ["\u25D0", "\u25D3", "\u25D1", "\u25D2"] },
|
|
846
|
+
triangle: { interval: 50, frames: ["\u25E2", "\u25E3", "\u25E4", "\u25E5"] },
|
|
847
|
+
pipe: { interval: 100, frames: ["\u2524", "\u2518", "\u2534", "\u2514", "\u251C", "\u250C", "\u252C", "\u2510"] },
|
|
848
|
+
arrow: { interval: 100, frames: ["\u2190", "\u2196", "\u2191", "\u2197", "\u2192", "\u2198", "\u2193", "\u2199"] },
|
|
849
|
+
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"] },
|
|
850
|
+
bouncingBar: { interval: 80, frames: ["[ ]", "[= ]", "[== ]", "[=== ]", "[====]", "[ ===]", "[ ==]", "[ =]", "[ ]", "[ =]", "[ ==]", "[ ===]", "[====]", "[=== ]", "[== ]", "[= ]"] },
|
|
851
|
+
bouncingBall: { interval: 80, frames: ["( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF)", "( \u25CF )", "( \u25CF )", "( \u25CF )", "( \u25CF )", "(\u25CF )"] },
|
|
852
|
+
simpleDots: { interval: 400, frames: [". ", ".. ", "...", " "] },
|
|
853
|
+
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"] },
|
|
854
|
+
star: { interval: 70, frames: ["\u2736", "\u2738", "\u2739", "\u273A", "\u2739", "\u2737"] }
|
|
855
|
+
};
|
|
856
|
+
var DEFAULT_SPINNER = "circle";
|
|
857
|
+
function resolveSpinner(config) {
|
|
858
|
+
if (!config) {
|
|
859
|
+
return spinners[DEFAULT_SPINNER];
|
|
860
|
+
}
|
|
861
|
+
if (typeof config === "string") {
|
|
862
|
+
if (config in spinners) {
|
|
863
|
+
return spinners[config];
|
|
864
|
+
}
|
|
865
|
+
throw new Error(`Unknown spinner preset: "${config}". Available: ${Object.keys(spinners).join(", ")}`);
|
|
866
|
+
}
|
|
867
|
+
return {
|
|
868
|
+
frames: config.frames,
|
|
869
|
+
interval: config.interval ?? 80
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// src/theme.ts
|
|
761
874
|
var DEFAULT_TOKENS = {
|
|
762
875
|
primary: "#5B9BD5",
|
|
763
876
|
success: "#6BCB77",
|
|
@@ -774,7 +887,8 @@ var DEFAULT_ICONS = {
|
|
|
774
887
|
pointer: "\u203A"
|
|
775
888
|
};
|
|
776
889
|
function resolveTheme(themeConfig) {
|
|
777
|
-
const
|
|
890
|
+
const presetTokens = themeConfig?.preset ? THEME_PRESETS[themeConfig.preset] : void 0;
|
|
891
|
+
const tokens = { ...DEFAULT_TOKENS, ...presetTokens, ...themeConfig?.tokens };
|
|
778
892
|
const icons = { ...DEFAULT_ICONS, ...themeConfig?.icons };
|
|
779
893
|
return {
|
|
780
894
|
primary: chalk.hex(tokens.primary),
|
|
@@ -785,7 +899,8 @@ function resolveTheme(themeConfig) {
|
|
|
785
899
|
muted: chalk.hex(tokens.muted),
|
|
786
900
|
accent: chalk.hex(tokens.accent),
|
|
787
901
|
bold: chalk.bold,
|
|
788
|
-
icons
|
|
902
|
+
icons,
|
|
903
|
+
spinner: resolveSpinner(themeConfig?.spinner)
|
|
789
904
|
};
|
|
790
905
|
}
|
|
791
906
|
|
|
@@ -1009,6 +1124,17 @@ function resolveTemplate(template, answers) {
|
|
|
1009
1124
|
return _match;
|
|
1010
1125
|
});
|
|
1011
1126
|
}
|
|
1127
|
+
function resolveTemplateStrict(template, answers) {
|
|
1128
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (_match, key) => {
|
|
1129
|
+
const trimmedKey = key.trim();
|
|
1130
|
+
if (!(trimmedKey in answers)) {
|
|
1131
|
+
throw new Error(`Action references unknown step "${trimmedKey}"`);
|
|
1132
|
+
}
|
|
1133
|
+
const value = answers[trimmedKey];
|
|
1134
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
1135
|
+
return String(value);
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1012
1138
|
|
|
1013
1139
|
// src/banner.ts
|
|
1014
1140
|
import figlet from "figlet";
|
|
@@ -1116,14 +1242,66 @@ function clearCache(wizardName, customDir) {
|
|
|
1116
1242
|
}
|
|
1117
1243
|
}
|
|
1118
1244
|
|
|
1119
|
-
// src/
|
|
1120
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2,
|
|
1245
|
+
// src/progress.ts
|
|
1246
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
1121
1247
|
import { join as join2 } from "path";
|
|
1122
1248
|
import { homedir as homedir2 } from "os";
|
|
1123
|
-
var
|
|
1249
|
+
var DEFAULT_PROGRESS_DIR = join2(homedir2(), ".config", "grimoire", "progress");
|
|
1250
|
+
function getProgressFilePath(wizardName, customDir) {
|
|
1251
|
+
const dir = customDir ?? DEFAULT_PROGRESS_DIR;
|
|
1252
|
+
return join2(dir, `${slugify(wizardName)}.json`);
|
|
1253
|
+
}
|
|
1254
|
+
function saveProgress(wizardName, state, customDir, excludeStepIds) {
|
|
1255
|
+
try {
|
|
1256
|
+
const dir = customDir ?? DEFAULT_PROGRESS_DIR;
|
|
1257
|
+
mkdirSync2(dir, { recursive: true });
|
|
1258
|
+
const excludeSet = new Set(excludeStepIds ?? []);
|
|
1259
|
+
const filteredAnswers = {};
|
|
1260
|
+
for (const [key, value] of Object.entries(state.answers)) {
|
|
1261
|
+
if (!excludeSet.has(key)) {
|
|
1262
|
+
filteredAnswers[key] = value;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
const progress = {
|
|
1266
|
+
currentStepId: state.currentStepId,
|
|
1267
|
+
answers: filteredAnswers,
|
|
1268
|
+
history: state.history,
|
|
1269
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1270
|
+
};
|
|
1271
|
+
const filePath = getProgressFilePath(wizardName, customDir);
|
|
1272
|
+
writeFileSync2(filePath, JSON.stringify(progress, null, 2) + "\n", "utf-8");
|
|
1273
|
+
} catch {
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
function loadProgress(wizardName, customDir) {
|
|
1277
|
+
try {
|
|
1278
|
+
const filePath = getProgressFilePath(wizardName, customDir);
|
|
1279
|
+
const raw = readFileSync3(filePath, "utf-8");
|
|
1280
|
+
const parsed = JSON.parse(raw);
|
|
1281
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "currentStepId" in parsed && "answers" in parsed && "history" in parsed && "savedAt" in parsed) {
|
|
1282
|
+
return parsed;
|
|
1283
|
+
}
|
|
1284
|
+
return null;
|
|
1285
|
+
} catch {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
function clearProgress(wizardName, customDir) {
|
|
1290
|
+
try {
|
|
1291
|
+
const filePath = getProgressFilePath(wizardName, customDir);
|
|
1292
|
+
unlinkSync2(filePath);
|
|
1293
|
+
} catch {
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// src/mru.ts
|
|
1298
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
|
|
1299
|
+
import { join as join3 } from "path";
|
|
1300
|
+
import { homedir as homedir3 } from "os";
|
|
1301
|
+
var MRU_DIR = join3(homedir3(), ".config", "grimoire", "mru");
|
|
1124
1302
|
function getMruFilePath(wizardName) {
|
|
1125
1303
|
const safeName = wizardName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1126
|
-
return
|
|
1304
|
+
return join3(MRU_DIR, `${safeName}.json`);
|
|
1127
1305
|
}
|
|
1128
1306
|
function loadMruData(wizardName) {
|
|
1129
1307
|
const filePath = getMruFilePath(wizardName);
|
|
@@ -1131,7 +1309,7 @@ function loadMruData(wizardName) {
|
|
|
1131
1309
|
if (!existsSync(filePath)) {
|
|
1132
1310
|
return {};
|
|
1133
1311
|
}
|
|
1134
|
-
const raw =
|
|
1312
|
+
const raw = readFileSync4(filePath, "utf-8");
|
|
1135
1313
|
const parsed = JSON.parse(raw);
|
|
1136
1314
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1137
1315
|
return {};
|
|
@@ -1144,8 +1322,8 @@ function loadMruData(wizardName) {
|
|
|
1144
1322
|
function saveMruData(wizardName, data) {
|
|
1145
1323
|
const filePath = getMruFilePath(wizardName);
|
|
1146
1324
|
try {
|
|
1147
|
-
|
|
1148
|
-
|
|
1325
|
+
mkdirSync3(MRU_DIR, { recursive: true });
|
|
1326
|
+
writeFileSync3(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
1149
1327
|
} catch {
|
|
1150
1328
|
}
|
|
1151
1329
|
}
|
|
@@ -1199,13 +1377,21 @@ function getOrderedOptions(wizardName, stepId, options) {
|
|
|
1199
1377
|
}
|
|
1200
1378
|
|
|
1201
1379
|
// src/runner.ts
|
|
1202
|
-
function
|
|
1380
|
+
function emitEvent(renderer, event, theme) {
|
|
1381
|
+
if (renderer.onEvent) {
|
|
1382
|
+
renderer.onEvent(event, theme);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
function runPreFlightChecks(checks, theme, renderer) {
|
|
1386
|
+
if (renderer) emitEvent(renderer, { type: "checks:start", checks }, theme);
|
|
1203
1387
|
for (const check of checks) {
|
|
1204
1388
|
try {
|
|
1205
1389
|
execSync(check.run, { stdio: "pipe" });
|
|
1206
1390
|
console.log(` ${theme.success("\u2713")} ${check.name}`);
|
|
1391
|
+
if (renderer) emitEvent(renderer, { type: "check:pass", name: check.name }, theme);
|
|
1207
1392
|
} catch {
|
|
1208
1393
|
console.log(` ${theme.error("\u2717")} ${check.name}: ${check.message}`);
|
|
1394
|
+
if (renderer) emitEvent(renderer, { type: "check:fail", name: check.name, message: check.message }, theme);
|
|
1209
1395
|
throw new Error(`Pre-flight check failed: ${check.name} \u2014 ${check.message}`);
|
|
1210
1396
|
}
|
|
1211
1397
|
}
|
|
@@ -1215,7 +1401,7 @@ function getMockValue(step, mockAnswers) {
|
|
|
1215
1401
|
if (step.id in mockAnswers) {
|
|
1216
1402
|
return mockAnswers[step.id];
|
|
1217
1403
|
}
|
|
1218
|
-
if (step.type === "message") {
|
|
1404
|
+
if (step.type === "message" || step.type === "note") {
|
|
1219
1405
|
return true;
|
|
1220
1406
|
}
|
|
1221
1407
|
const defaultValue = getStepDefault(step);
|
|
@@ -1243,6 +1429,7 @@ function getStepDefault(step) {
|
|
|
1243
1429
|
return step.default;
|
|
1244
1430
|
case "password":
|
|
1245
1431
|
case "message":
|
|
1432
|
+
case "note":
|
|
1246
1433
|
return void 0;
|
|
1247
1434
|
}
|
|
1248
1435
|
}
|
|
@@ -1256,6 +1443,21 @@ async function runWizard(config, options) {
|
|
|
1256
1443
|
const cacheDir = typeof options?.cache === "object" ? options.cache.dir : void 0;
|
|
1257
1444
|
const mruEnabled = !isMock && options?.mru !== false;
|
|
1258
1445
|
let state = createWizardState(config);
|
|
1446
|
+
const resumeEnabled = !isMock && options?.resume !== false;
|
|
1447
|
+
if (resumeEnabled) {
|
|
1448
|
+
const saved = loadProgress(config.meta.name);
|
|
1449
|
+
if (saved) {
|
|
1450
|
+
const stepExists = config.steps.some((s) => s.id === saved.currentStepId);
|
|
1451
|
+
if (stepExists) {
|
|
1452
|
+
state = {
|
|
1453
|
+
...state,
|
|
1454
|
+
currentStepId: saved.currentStepId,
|
|
1455
|
+
answers: { ...state.answers, ...saved.answers },
|
|
1456
|
+
history: saved.history
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1259
1461
|
const cachedAnswers = cacheEnabled ? loadCachedAnswers(config.meta.name, cacheDir) : void 0;
|
|
1260
1462
|
const userPlugins = options?.plugins;
|
|
1261
1463
|
if (userPlugins) {
|
|
@@ -1265,103 +1467,174 @@ async function runWizard(config, options) {
|
|
|
1265
1467
|
}
|
|
1266
1468
|
try {
|
|
1267
1469
|
if (!isMock && config.checks && config.checks.length > 0) {
|
|
1268
|
-
runPreFlightChecks(config.checks, theme);
|
|
1470
|
+
runPreFlightChecks(config.checks, theme, renderer);
|
|
1269
1471
|
}
|
|
1270
1472
|
if (!quiet) {
|
|
1271
1473
|
printWizardHeader(config, theme, options?.plain);
|
|
1272
1474
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1475
|
+
const visibleStepsForCount = getVisibleSteps(config, state.answers);
|
|
1476
|
+
emitEvent(renderer, { type: "session:start", wizard: config.meta.name, description: config.meta.description, totalSteps: visibleStepsForCount.length }, theme);
|
|
1477
|
+
let needsReview = true;
|
|
1478
|
+
while (needsReview) {
|
|
1479
|
+
let previousGroup;
|
|
1480
|
+
while (state.status === "running") {
|
|
1481
|
+
const visibleSteps = getVisibleSteps(config, state.answers);
|
|
1482
|
+
const currentStep = config.steps.find((s) => s.id === state.currentStepId);
|
|
1483
|
+
if (!currentStep) {
|
|
1484
|
+
throw new Error(`Current step not found: "${state.currentStepId}"`);
|
|
1485
|
+
}
|
|
1281
1486
|
if (currentStep.group !== void 0 && currentStep.group !== previousGroup) {
|
|
1282
1487
|
const resolvedGroup = resolveTemplate(currentStep.group, state.answers);
|
|
1283
|
-
|
|
1488
|
+
if (!isMock) {
|
|
1489
|
+
renderer.renderGroupHeader(resolvedGroup, theme);
|
|
1490
|
+
}
|
|
1491
|
+
emitEvent(renderer, { type: "group:start", group: resolvedGroup }, theme);
|
|
1284
1492
|
}
|
|
1285
1493
|
previousGroup = currentStep.group;
|
|
1286
1494
|
const stepIndex = visibleSteps.findIndex((s) => s.id === state.currentStepId);
|
|
1287
1495
|
const resolvedMessage = resolveTemplate(currentStep.message, state.answers);
|
|
1288
1496
|
const resolvedDescription = currentStep.description ? resolveTemplate(currentStep.description, state.answers) : void 0;
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
const
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1497
|
+
if (!isMock) {
|
|
1498
|
+
renderer.renderStepHeader(stepIndex, visibleSteps.length, resolvedMessage, theme, resolvedDescription);
|
|
1499
|
+
}
|
|
1500
|
+
emitEvent(renderer, { type: "step:start", stepId: currentStep.id, stepIndex, totalVisible: visibleSteps.length, step: currentStep }, theme);
|
|
1501
|
+
if (currentStep.type === "note") {
|
|
1502
|
+
emitEvent(renderer, { type: "note", title: resolvedMessage, body: resolvedDescription ?? "" }, theme);
|
|
1503
|
+
}
|
|
1504
|
+
if (options?.onBeforeStep) {
|
|
1505
|
+
await options.onBeforeStep(currentStep.id, currentStep, state);
|
|
1506
|
+
}
|
|
1507
|
+
const pluginStep = getPluginStep(currentStep.type);
|
|
1508
|
+
const resolvedStep = pluginStep ? currentStep : resolveStepDefaults(currentStep, cachedAnswers);
|
|
1509
|
+
const withTemplate = options?.templateAnswers ? applyTemplateDefaults(resolvedStep, options.templateAnswers) : resolvedStep;
|
|
1510
|
+
const templatedStep = resolveStepTemplates(withTemplate, state.answers);
|
|
1511
|
+
const mruStep = mruEnabled ? applyMruOrdering(templatedStep, config.meta.name) : templatedStep;
|
|
1512
|
+
try {
|
|
1513
|
+
const value = isMock ? getMockValue(mruStep, mockAnswers) : pluginStep ? await pluginStep.render(toStepRecord(mruStep), state, theme) : await renderStep(renderer, mruStep, state, theme);
|
|
1514
|
+
if (pluginStep?.validate) {
|
|
1515
|
+
const pluginError = pluginStep.validate(value, toStepRecord(templatedStep));
|
|
1516
|
+
if (pluginError) {
|
|
1517
|
+
if (isMock) {
|
|
1518
|
+
throw new Error(
|
|
1519
|
+
`Mock mode: validation failed for step "${currentStep.id}": ${pluginError}`
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
console.log(theme.error(`
|
|
1523
|
+
${pluginError}
|
|
1524
|
+
`));
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
const nextState = wizardReducer(state, { type: "NEXT", value }, config);
|
|
1529
|
+
if (nextState.errors[currentStep.id]) {
|
|
1530
|
+
const errorMsg = resolveTemplate(nextState.errors[currentStep.id] ?? "", state.answers);
|
|
1531
|
+
emitEvent(renderer, { type: "step:error", stepId: currentStep.id, error: errorMsg }, theme);
|
|
1304
1532
|
if (isMock) {
|
|
1305
1533
|
throw new Error(
|
|
1306
|
-
`Mock mode: validation failed for step "${currentStep.id}": ${
|
|
1534
|
+
`Mock mode: validation failed for step "${currentStep.id}": ${errorMsg}`
|
|
1307
1535
|
);
|
|
1308
1536
|
}
|
|
1309
1537
|
console.log(theme.error(`
|
|
1310
|
-
${
|
|
1538
|
+
${errorMsg}
|
|
1311
1539
|
`));
|
|
1540
|
+
state = { ...nextState, errors: {} };
|
|
1312
1541
|
continue;
|
|
1313
1542
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
if (isMock) {
|
|
1319
|
-
throw new Error(
|
|
1320
|
-
`Mock mode: validation failed for step "${currentStep.id}": ${errorMsg}`
|
|
1321
|
-
);
|
|
1322
|
-
}
|
|
1323
|
-
console.log(theme.error(`
|
|
1324
|
-
${errorMsg}
|
|
1325
|
-
`));
|
|
1326
|
-
state = { ...nextState, errors: {} };
|
|
1327
|
-
continue;
|
|
1328
|
-
}
|
|
1329
|
-
if (!isMock && options?.asyncValidate) {
|
|
1330
|
-
const asyncError = await options.asyncValidate(currentStep.id, value, nextState.answers);
|
|
1331
|
-
if (asyncError !== null) {
|
|
1332
|
-
console.log(theme.error(`
|
|
1543
|
+
if (!isMock && options?.asyncValidate) {
|
|
1544
|
+
const asyncError = await options.asyncValidate(currentStep.id, value, nextState.answers);
|
|
1545
|
+
if (asyncError !== null) {
|
|
1546
|
+
console.log(theme.error(`
|
|
1333
1547
|
${asyncError}
|
|
1334
1548
|
`));
|
|
1335
|
-
|
|
1336
|
-
|
|
1549
|
+
state = { ...nextState, errors: {} };
|
|
1550
|
+
continue;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
if (options?.onAfterStep) {
|
|
1554
|
+
await options.onAfterStep(currentStep.id, value, nextState);
|
|
1555
|
+
}
|
|
1556
|
+
state = nextState;
|
|
1557
|
+
emitEvent(renderer, { type: "step:complete", stepId: currentStep.id, value, step: currentStep }, theme);
|
|
1558
|
+
if (mruEnabled && isSelectLikeStep(currentStep.type)) {
|
|
1559
|
+
recordSelection(config.meta.name, currentStep.id, value);
|
|
1337
1560
|
}
|
|
1561
|
+
options?.onStepComplete?.(currentStep.id, value, state);
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
if (!isMock && isUserCancel(error)) {
|
|
1564
|
+
state = wizardReducer(state, { type: "CANCEL" }, config);
|
|
1565
|
+
options?.onCancel?.(state);
|
|
1566
|
+
const passwordStepIds = config.steps.filter((s) => s.type === "password").map((s) => s.id);
|
|
1567
|
+
saveProgress(config.meta.name, {
|
|
1568
|
+
currentStepId: state.currentStepId,
|
|
1569
|
+
answers: state.answers,
|
|
1570
|
+
history: state.history
|
|
1571
|
+
}, void 0, passwordStepIds);
|
|
1572
|
+
emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: true }, theme);
|
|
1573
|
+
if (!quiet) {
|
|
1574
|
+
console.log(theme.warning("\n Wizard cancelled.\n"));
|
|
1575
|
+
}
|
|
1576
|
+
return state.answers;
|
|
1577
|
+
}
|
|
1578
|
+
throw error;
|
|
1338
1579
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1580
|
+
}
|
|
1581
|
+
if (config.meta.review && !isMock && state.status === "done") {
|
|
1582
|
+
const reviewLines = [];
|
|
1583
|
+
for (const step of config.steps) {
|
|
1584
|
+
const answer = state.answers[step.id];
|
|
1585
|
+
if (answer === void 0) continue;
|
|
1586
|
+
const display = step.type === "password" ? "****" : Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
|
|
1587
|
+
reviewLines.push(`${step.id}: ${display}`);
|
|
1341
1588
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1589
|
+
emitEvent(renderer, { type: "note", title: "Review your answers", body: reviewLines.join("\n") }, theme);
|
|
1590
|
+
console.log(`
|
|
1591
|
+
${theme.bold("Review your answers:")}
|
|
1592
|
+
`);
|
|
1593
|
+
for (const line of reviewLines) {
|
|
1594
|
+
console.log(` ${line}`);
|
|
1345
1595
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1596
|
+
console.log();
|
|
1597
|
+
const { confirm: confirmPrompt } = await import("@inquirer/prompts");
|
|
1598
|
+
const ok = await confirmPrompt({
|
|
1599
|
+
message: "Everything look right?",
|
|
1600
|
+
default: true
|
|
1601
|
+
});
|
|
1602
|
+
if (ok) {
|
|
1603
|
+
needsReview = false;
|
|
1604
|
+
} else {
|
|
1605
|
+
const { select: selectPrompt } = await import("@inquirer/prompts");
|
|
1606
|
+
const stepsWithAnswers = config.steps.filter(
|
|
1607
|
+
(s) => state.answers[s.id] !== void 0 && s.type !== "note" && s.type !== "message"
|
|
1608
|
+
);
|
|
1609
|
+
const stepToRevisit = await selectPrompt({
|
|
1610
|
+
message: "Which step would you like to change?",
|
|
1611
|
+
choices: stepsWithAnswers.map((s) => ({
|
|
1612
|
+
name: `${s.id}: ${s.type === "password" ? "****" : String(state.answers[s.id] ?? "")}`,
|
|
1613
|
+
value: s.id
|
|
1614
|
+
}))
|
|
1615
|
+
});
|
|
1616
|
+
state = {
|
|
1617
|
+
...state,
|
|
1618
|
+
currentStepId: stepToRevisit,
|
|
1619
|
+
status: "running"
|
|
1620
|
+
};
|
|
1355
1621
|
}
|
|
1356
|
-
|
|
1622
|
+
} else {
|
|
1623
|
+
needsReview = false;
|
|
1357
1624
|
}
|
|
1358
1625
|
}
|
|
1359
1626
|
if (state.status === "done" && !quiet) {
|
|
1360
1627
|
renderer.renderSummary(state.answers, config.steps, theme);
|
|
1361
1628
|
}
|
|
1362
|
-
if (state.status === "done" &&
|
|
1363
|
-
|
|
1629
|
+
if (state.status === "done" && !isMock) {
|
|
1630
|
+
if (config.onComplete) {
|
|
1631
|
+
await executeOnComplete(config.onComplete, options?.configFilePath, state.answers, config, theme, renderer);
|
|
1632
|
+
}
|
|
1633
|
+
if (config.actions && config.actions.length > 0) {
|
|
1634
|
+
await executeActions(config.actions, state.answers, theme, renderer);
|
|
1635
|
+
}
|
|
1364
1636
|
}
|
|
1637
|
+
emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: state.status === "cancelled" }, theme);
|
|
1365
1638
|
if (state.status === "done" && cacheEnabled) {
|
|
1366
1639
|
const passwordStepIds = new Set(
|
|
1367
1640
|
config.steps.filter((s) => s.type === "password").map((s) => s.id)
|
|
@@ -1374,6 +1647,9 @@ async function runWizard(config, options) {
|
|
|
1374
1647
|
}
|
|
1375
1648
|
saveCachedAnswers(config.meta.name, answersToCache, cacheDir);
|
|
1376
1649
|
}
|
|
1650
|
+
if (state.status === "done") {
|
|
1651
|
+
clearProgress(config.meta.name);
|
|
1652
|
+
}
|
|
1377
1653
|
return state.answers;
|
|
1378
1654
|
} finally {
|
|
1379
1655
|
if (userPlugins) {
|
|
@@ -1413,6 +1689,8 @@ function renderStep(renderer, step, state, theme) {
|
|
|
1413
1689
|
case "message":
|
|
1414
1690
|
renderer.renderMessage(step, state, theme);
|
|
1415
1691
|
return Promise.resolve(true);
|
|
1692
|
+
case "note":
|
|
1693
|
+
return Promise.resolve(true);
|
|
1416
1694
|
}
|
|
1417
1695
|
}
|
|
1418
1696
|
function resolveStepDefaults(step, cachedAnswers) {
|
|
@@ -1463,6 +1741,7 @@ function resolveStepDefaults(step, cachedAnswers) {
|
|
|
1463
1741
|
}
|
|
1464
1742
|
case "password":
|
|
1465
1743
|
case "message":
|
|
1744
|
+
case "note":
|
|
1466
1745
|
return step;
|
|
1467
1746
|
}
|
|
1468
1747
|
}
|
|
@@ -1472,7 +1751,7 @@ function getCachedDefault(stepId, cachedAnswers) {
|
|
|
1472
1751
|
}
|
|
1473
1752
|
function applyTemplateDefaults(step, templateAnswers) {
|
|
1474
1753
|
if (!(step.id in templateAnswers)) return step;
|
|
1475
|
-
if (step.type === "password" || step.type === "message") return step;
|
|
1754
|
+
if (step.type === "password" || step.type === "message" || step.type === "note") return step;
|
|
1476
1755
|
const value = templateAnswers[step.id];
|
|
1477
1756
|
switch (step.type) {
|
|
1478
1757
|
case "text":
|
|
@@ -1561,13 +1840,34 @@ function resolveStepTemplates(step, answers) {
|
|
|
1561
1840
|
case "confirm":
|
|
1562
1841
|
case "toggle":
|
|
1563
1842
|
case "message":
|
|
1843
|
+
case "note":
|
|
1564
1844
|
return {
|
|
1565
1845
|
...step,
|
|
1566
1846
|
description: step.description ? resolveTemplate(step.description, answers) : void 0
|
|
1567
1847
|
};
|
|
1568
1848
|
}
|
|
1569
1849
|
}
|
|
1570
|
-
async function
|
|
1850
|
+
async function executeOnComplete(handlerPath, configFilePath, answers, config, theme, renderer) {
|
|
1851
|
+
if (renderer) emitEvent(renderer, { type: "oncomplete:start" }, theme);
|
|
1852
|
+
const resolvedPath = configFilePath ? resolve2(dirname2(configFilePath), handlerPath) : resolve2(handlerPath);
|
|
1853
|
+
try {
|
|
1854
|
+
const mod = await import(pathToFileURL(resolvedPath).href);
|
|
1855
|
+
if (typeof mod.default !== "function") {
|
|
1856
|
+
throw new Error(`onComplete handler "${handlerPath}" must export a default function`);
|
|
1857
|
+
}
|
|
1858
|
+
await mod.default({ answers, config });
|
|
1859
|
+
if (renderer) emitEvent(renderer, { type: "oncomplete:pass" }, theme);
|
|
1860
|
+
} catch (error) {
|
|
1861
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1862
|
+
if (renderer) emitEvent(renderer, { type: "oncomplete:fail", error: message }, theme);
|
|
1863
|
+
console.log(`
|
|
1864
|
+
${theme.error("\u2717")} onComplete handler failed: ${message}
|
|
1865
|
+
`);
|
|
1866
|
+
throw error;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
async function executeActions(actions, answers, theme, renderer) {
|
|
1870
|
+
if (renderer) emitEvent(renderer, { type: "actions:start" }, theme);
|
|
1571
1871
|
console.log(`
|
|
1572
1872
|
${theme.bold("Running actions...")}
|
|
1573
1873
|
`);
|
|
@@ -1575,14 +1875,16 @@ async function executeActions(actions, answers, theme) {
|
|
|
1575
1875
|
if (action.when && !evaluateCondition(action.when, answers)) {
|
|
1576
1876
|
continue;
|
|
1577
1877
|
}
|
|
1578
|
-
const resolvedCommand =
|
|
1579
|
-
const resolvedName = action.name ?
|
|
1878
|
+
const resolvedCommand = resolveTemplateStrict(action.run, answers);
|
|
1879
|
+
const resolvedName = action.name ? resolveTemplateStrict(action.name, answers) : void 0;
|
|
1580
1880
|
const label = resolvedName ?? resolvedCommand;
|
|
1581
1881
|
try {
|
|
1582
1882
|
execSync(resolvedCommand, { stdio: "pipe" });
|
|
1583
1883
|
console.log(` ${theme.success("\u2713")} ${label}`);
|
|
1884
|
+
if (renderer) emitEvent(renderer, { type: "action:pass", name: label }, theme);
|
|
1584
1885
|
} catch {
|
|
1585
1886
|
console.log(` ${theme.error("\u2717")} ${label}`);
|
|
1887
|
+
if (renderer) emitEvent(renderer, { type: "action:fail", name: label }, theme);
|
|
1586
1888
|
throw new Error(`Action failed: ${label}`);
|
|
1587
1889
|
}
|
|
1588
1890
|
}
|
|
@@ -1606,7 +1908,7 @@ function isUserCancel(error) {
|
|
|
1606
1908
|
// src/scaffolder.ts
|
|
1607
1909
|
import { input as input2, select as select2, confirm as confirm2, number as number2 } from "@inquirer/prompts";
|
|
1608
1910
|
import { stringify } from "yaml";
|
|
1609
|
-
import { writeFileSync as
|
|
1911
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
1610
1912
|
import { relative } from "path";
|
|
1611
1913
|
var STEP_TYPE_CHOICES = [
|
|
1612
1914
|
{ name: "text", value: "text" },
|
|
@@ -1723,7 +2025,7 @@ async function scaffoldWizard(outputPath) {
|
|
|
1723
2025
|
config.theme = { tokens: { primary: primaryColor } };
|
|
1724
2026
|
}
|
|
1725
2027
|
const yamlContent = stringify(config);
|
|
1726
|
-
|
|
2028
|
+
writeFileSync4(outputPath, yamlContent, "utf-8");
|
|
1727
2029
|
const displayPath = relative(process.cwd(), outputPath);
|
|
1728
2030
|
console.log(`
|
|
1729
2031
|
\u2713 Created wizard config: ${outputPath}`);
|
|
@@ -1914,20 +2216,20 @@ complete -c grimoire -n "__fish_seen_subcommand_from completion" -f -a "fish" -d
|
|
|
1914
2216
|
}
|
|
1915
2217
|
|
|
1916
2218
|
// src/templates.ts
|
|
1917
|
-
import { mkdirSync as
|
|
1918
|
-
import { join as
|
|
1919
|
-
import { homedir as
|
|
1920
|
-
var TEMPLATES_DIR =
|
|
2219
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
|
|
2220
|
+
import { join as join4 } from "path";
|
|
2221
|
+
import { homedir as homedir4 } from "os";
|
|
2222
|
+
var TEMPLATES_DIR = join4(homedir4(), ".config", "grimoire", "templates");
|
|
1921
2223
|
function getWizardTemplateDir(wizardName) {
|
|
1922
|
-
return
|
|
2224
|
+
return join4(TEMPLATES_DIR, slugify(wizardName));
|
|
1923
2225
|
}
|
|
1924
2226
|
function getTemplateFilePath(wizardName, templateName) {
|
|
1925
|
-
return
|
|
2227
|
+
return join4(getWizardTemplateDir(wizardName), `${slugify(templateName)}.json`);
|
|
1926
2228
|
}
|
|
1927
2229
|
function loadTemplate(wizardName, templateName) {
|
|
1928
2230
|
try {
|
|
1929
2231
|
const filePath = getTemplateFilePath(wizardName, templateName);
|
|
1930
|
-
const raw =
|
|
2232
|
+
const raw = readFileSync5(filePath, "utf-8");
|
|
1931
2233
|
const parsed = JSON.parse(raw);
|
|
1932
2234
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1933
2235
|
return parsed;
|
|
@@ -1949,7 +2251,7 @@ function listTemplates(wizardName) {
|
|
|
1949
2251
|
function deleteTemplate(wizardName, templateName) {
|
|
1950
2252
|
try {
|
|
1951
2253
|
const filePath = getTemplateFilePath(wizardName, templateName);
|
|
1952
|
-
|
|
2254
|
+
unlinkSync3(filePath);
|
|
1953
2255
|
} catch {
|
|
1954
2256
|
}
|
|
1955
2257
|
}
|
|
@@ -2149,25 +2451,205 @@ var InkRenderer = class {
|
|
|
2149
2451
|
}
|
|
2150
2452
|
};
|
|
2151
2453
|
|
|
2454
|
+
// src/renderers/clack.ts
|
|
2455
|
+
import chalk2 from "chalk";
|
|
2456
|
+
|
|
2457
|
+
// src/renderers/symbols.ts
|
|
2458
|
+
function isUnicodeSupported() {
|
|
2459
|
+
if (process.platform === "win32") {
|
|
2460
|
+
return Boolean(process.env["WT_SESSION"]) || process.env["TERM_PROGRAM"] === "vscode";
|
|
2461
|
+
}
|
|
2462
|
+
return process.env["TERM"] !== "linux";
|
|
2463
|
+
}
|
|
2464
|
+
var unicode = isUnicodeSupported();
|
|
2465
|
+
var u = (unicodeChar, fallback) => unicode ? unicodeChar : fallback;
|
|
2466
|
+
var S_BAR_START = u("\u250C", "T");
|
|
2467
|
+
var S_BAR = u("\u2502", "|");
|
|
2468
|
+
var S_BAR_END = u("\u2514", "\u2014");
|
|
2469
|
+
var S_STEP_ACTIVE = u("\u25C6", "*");
|
|
2470
|
+
var S_STEP_SUBMIT = u("\u25C7", "o");
|
|
2471
|
+
var S_STEP_CANCEL = u("\u25A0", "x");
|
|
2472
|
+
var S_STEP_ERROR = u("\u25B2", "x");
|
|
2473
|
+
var S_CORNER_TR = u("\u256E", "+");
|
|
2474
|
+
var S_CORNER_BR = u("\u256F", "+");
|
|
2475
|
+
var S_BAR_H = u("\u2500", "-");
|
|
2476
|
+
|
|
2477
|
+
// src/renderers/clack.ts
|
|
2478
|
+
var ClackRenderer = class extends InquirerRenderer {
|
|
2479
|
+
spinnerInterval;
|
|
2480
|
+
spinnerFrameIndex = 0;
|
|
2481
|
+
renderStepHeader() {
|
|
2482
|
+
}
|
|
2483
|
+
renderGroupHeader() {
|
|
2484
|
+
}
|
|
2485
|
+
renderSummary(answers, steps, theme) {
|
|
2486
|
+
const entries = [];
|
|
2487
|
+
for (const step of steps) {
|
|
2488
|
+
const answer = answers[step.id];
|
|
2489
|
+
if (answer === void 0) continue;
|
|
2490
|
+
const display = Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
|
|
2491
|
+
entries.push({ id: step.id, display });
|
|
2492
|
+
}
|
|
2493
|
+
if (entries.length === 0) return;
|
|
2494
|
+
const title = "Summary";
|
|
2495
|
+
const lines = entries.map((e) => `${e.id}: ${e.display}`);
|
|
2496
|
+
this.writeNoteBox(title, lines, theme);
|
|
2497
|
+
}
|
|
2498
|
+
onEvent(event, theme) {
|
|
2499
|
+
switch (event.type) {
|
|
2500
|
+
case "session:start":
|
|
2501
|
+
this.handleSessionStart(event, theme);
|
|
2502
|
+
break;
|
|
2503
|
+
case "session:end":
|
|
2504
|
+
this.handleSessionEnd(event, theme);
|
|
2505
|
+
break;
|
|
2506
|
+
case "step:start":
|
|
2507
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2508
|
+
`);
|
|
2509
|
+
break;
|
|
2510
|
+
case "step:complete":
|
|
2511
|
+
this.handleStepComplete(event, theme);
|
|
2512
|
+
break;
|
|
2513
|
+
case "step:error":
|
|
2514
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(`${S_STEP_ERROR} ${event.error}`)}
|
|
2515
|
+
`);
|
|
2516
|
+
break;
|
|
2517
|
+
case "step:back":
|
|
2518
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.muted("\u21A9 Back")}
|
|
2519
|
+
`);
|
|
2520
|
+
break;
|
|
2521
|
+
case "group:start":
|
|
2522
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2523
|
+
`);
|
|
2524
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.accent(event.group)}
|
|
2525
|
+
`);
|
|
2526
|
+
break;
|
|
2527
|
+
case "note":
|
|
2528
|
+
this.writeNoteBox(event.title, event.body.split("\n"), theme);
|
|
2529
|
+
break;
|
|
2530
|
+
case "spinner:start":
|
|
2531
|
+
this.startSpinner(event.message, theme);
|
|
2532
|
+
break;
|
|
2533
|
+
case "spinner:stop":
|
|
2534
|
+
this.stopSpinner(event.message, theme);
|
|
2535
|
+
break;
|
|
2536
|
+
case "checks:start":
|
|
2537
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2538
|
+
`);
|
|
2539
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running checks...")}
|
|
2540
|
+
`);
|
|
2541
|
+
break;
|
|
2542
|
+
case "check:pass":
|
|
2543
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
|
|
2544
|
+
`);
|
|
2545
|
+
break;
|
|
2546
|
+
case "check:fail":
|
|
2547
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}: ${event.message}
|
|
2548
|
+
`);
|
|
2549
|
+
break;
|
|
2550
|
+
case "actions:start":
|
|
2551
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2552
|
+
`);
|
|
2553
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running actions...")}
|
|
2554
|
+
`);
|
|
2555
|
+
break;
|
|
2556
|
+
case "action:pass":
|
|
2557
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
|
|
2558
|
+
`);
|
|
2559
|
+
break;
|
|
2560
|
+
case "action:fail":
|
|
2561
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}
|
|
2562
|
+
`);
|
|
2563
|
+
break;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
handleSessionStart(event, theme) {
|
|
2567
|
+
process.stdout.write(`${chalk2.gray(S_BAR_START)} ${theme.bold(event.wizard)}
|
|
2568
|
+
`);
|
|
2569
|
+
if (event.description) {
|
|
2570
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.muted(event.description)}
|
|
2571
|
+
`);
|
|
2572
|
+
}
|
|
2573
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2574
|
+
`);
|
|
2575
|
+
}
|
|
2576
|
+
handleSessionEnd(event, theme) {
|
|
2577
|
+
if (event.cancelled) {
|
|
2578
|
+
process.stdout.write(`${theme.warning(S_STEP_CANCEL)} Cancelled
|
|
2579
|
+
`);
|
|
2580
|
+
} else {
|
|
2581
|
+
process.stdout.write(`${chalk2.gray(S_BAR_END)} ${theme.success("You're all set!")}
|
|
2582
|
+
`);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
handleStepComplete(event, theme) {
|
|
2586
|
+
const displayValue = event.step.type === "password" ? "****" : this.formatValue(event.value);
|
|
2587
|
+
process.stdout.write(
|
|
2588
|
+
`${chalk2.gray(S_STEP_SUBMIT)} ${event.step.message} ${chalk2.gray("\xB7")} ${theme.muted(displayValue)}
|
|
2589
|
+
`
|
|
2590
|
+
);
|
|
2591
|
+
}
|
|
2592
|
+
formatValue(value) {
|
|
2593
|
+
if (Array.isArray(value)) return value.map(String).join(", ");
|
|
2594
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
2595
|
+
return String(value);
|
|
2596
|
+
}
|
|
2597
|
+
writeNoteBox(title, lines, _theme) {
|
|
2598
|
+
const maxLen = Math.max(title.length, ...lines.map((l) => l.length));
|
|
2599
|
+
const padded = maxLen + 2;
|
|
2600
|
+
const topLine = `${S_BAR_H.repeat(padded - title.length - 1)}${S_CORNER_TR}`;
|
|
2601
|
+
const bottomLine = `${S_BAR_H.repeat(padded)}${S_CORNER_BR}`;
|
|
2602
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2603
|
+
`);
|
|
2604
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256D${S_BAR_H} ${title} ${topLine}`)}
|
|
2605
|
+
`);
|
|
2606
|
+
for (const line of lines) {
|
|
2607
|
+
const pad = " ".repeat(maxLen - line.length);
|
|
2608
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(S_BAR)} ${line}${pad} ${chalk2.gray(S_BAR)}
|
|
2609
|
+
`);
|
|
2610
|
+
}
|
|
2611
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256E${bottomLine}`)}
|
|
2612
|
+
`);
|
|
2613
|
+
}
|
|
2614
|
+
startSpinner(message, theme) {
|
|
2615
|
+
this.spinnerFrameIndex = 0;
|
|
2616
|
+
const { frames, interval } = theme.spinner;
|
|
2617
|
+
this.spinnerInterval = setInterval(() => {
|
|
2618
|
+
const frame = frames[this.spinnerFrameIndex % frames.length];
|
|
2619
|
+
process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}`);
|
|
2620
|
+
this.spinnerFrameIndex++;
|
|
2621
|
+
}, interval);
|
|
2622
|
+
}
|
|
2623
|
+
stopSpinner(message, theme) {
|
|
2624
|
+
if (this.spinnerInterval) {
|
|
2625
|
+
clearInterval(this.spinnerInterval);
|
|
2626
|
+
this.spinnerInterval = void 0;
|
|
2627
|
+
}
|
|
2628
|
+
const finalMessage = message ?? "Done";
|
|
2629
|
+
process.stdout.write(`\r${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${finalMessage}
|
|
2630
|
+
`);
|
|
2631
|
+
}
|
|
2632
|
+
};
|
|
2633
|
+
|
|
2152
2634
|
// src/cli.ts
|
|
2153
|
-
import { writeFileSync as
|
|
2154
|
-
import { resolve as
|
|
2635
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
2636
|
+
import { resolve as resolve3 } from "path";
|
|
2155
2637
|
import { fileURLToPath } from "url";
|
|
2156
2638
|
import { stringify as yamlStringify } from "yaml";
|
|
2157
2639
|
var plainMode = false;
|
|
2158
2640
|
function applyColorMode(opts) {
|
|
2159
2641
|
if (opts.plain || opts.color === false || process.env["NO_COLOR"] !== void 0) {
|
|
2160
|
-
|
|
2642
|
+
chalk3.level = 0;
|
|
2161
2643
|
plainMode = true;
|
|
2162
2644
|
}
|
|
2163
2645
|
}
|
|
2164
|
-
program.name("grimoire").description("Config-driven CLI wizard framework").version("0.
|
|
2646
|
+
program.name("grimoire").description("Config-driven CLI wizard framework").version("0.4.0").option("--no-color", "Disable colored output").option("--plain", "Plain output mode (no colors, no banner)").hook("preAction", () => {
|
|
2165
2647
|
const globalOpts = program.opts();
|
|
2166
2648
|
applyColorMode(globalOpts);
|
|
2167
2649
|
});
|
|
2168
|
-
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("--renderer <type>", "Renderer to use: inquirer (default) or
|
|
2650
|
+
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) => {
|
|
2169
2651
|
try {
|
|
2170
|
-
const fullPath =
|
|
2652
|
+
const fullPath = resolve3(configPath);
|
|
2171
2653
|
const config = await loadWizardConfig(fullPath);
|
|
2172
2654
|
if (opts.dryRun) {
|
|
2173
2655
|
printDryRun(config);
|
|
@@ -2192,10 +2674,12 @@ program.command("run").description("Run a wizard from a config file").argument("
|
|
|
2192
2674
|
plain: plainMode,
|
|
2193
2675
|
mockAnswers,
|
|
2194
2676
|
templateAnswers,
|
|
2195
|
-
cache: opts.cache
|
|
2677
|
+
cache: opts.cache,
|
|
2678
|
+
resume: opts.resume,
|
|
2679
|
+
configFilePath: fullPath
|
|
2196
2680
|
});
|
|
2197
2681
|
const rawOutputPath = opts.output ?? config.output?.path;
|
|
2198
|
-
const outputPath = rawOutputPath ?
|
|
2682
|
+
const outputPath = rawOutputPath ? resolve3(resolveTemplate(rawOutputPath, answers)) : void 0;
|
|
2199
2683
|
if (isJsonOutput) {
|
|
2200
2684
|
const stepsCompleted = Object.keys(answers).length;
|
|
2201
2685
|
const result = {
|
|
@@ -2208,14 +2692,14 @@ program.command("run").description("Run a wizard from a config file").argument("
|
|
|
2208
2692
|
const jsonStr = JSON.stringify(result, null, 2);
|
|
2209
2693
|
console.log(jsonStr);
|
|
2210
2694
|
if (outputPath) {
|
|
2211
|
-
|
|
2695
|
+
writeFileSync6(outputPath, jsonStr + "\n", "utf-8");
|
|
2212
2696
|
}
|
|
2213
2697
|
return;
|
|
2214
2698
|
}
|
|
2215
2699
|
if (outputPath) {
|
|
2216
2700
|
const format = opts.format ?? config.output?.format ?? "json";
|
|
2217
2701
|
const content = formatOutput(answers, format);
|
|
2218
|
-
|
|
2702
|
+
writeFileSync6(outputPath, content, "utf-8");
|
|
2219
2703
|
if (!opts.quiet) {
|
|
2220
2704
|
console.log(`
|
|
2221
2705
|
Answers written to: ${outputPath}
|
|
@@ -2241,7 +2725,7 @@ program.command("run").description("Run a wizard from a config file").argument("
|
|
|
2241
2725
|
});
|
|
2242
2726
|
program.command("validate").description("Validate a wizard config file without running it").argument("<config>", "Path to wizard config file").action(async (configPath) => {
|
|
2243
2727
|
try {
|
|
2244
|
-
const fullPath =
|
|
2728
|
+
const fullPath = resolve3(configPath);
|
|
2245
2729
|
const config = await loadWizardConfig(fullPath);
|
|
2246
2730
|
console.log(`
|
|
2247
2731
|
\u2713 Valid wizard config: "${config.meta.name}"`);
|
|
@@ -2258,7 +2742,7 @@ program.command("validate").description("Validate a wizard config file without r
|
|
|
2258
2742
|
});
|
|
2259
2743
|
program.command("create").description("Interactively scaffold a new wizard config file").argument("[output]", "Output file path", "wizard.yaml").action(async (output) => {
|
|
2260
2744
|
try {
|
|
2261
|
-
const resolvedPath =
|
|
2745
|
+
const resolvedPath = resolve3(output);
|
|
2262
2746
|
await scaffoldWizard(resolvedPath);
|
|
2263
2747
|
} catch (error) {
|
|
2264
2748
|
if (error instanceof Error) {
|
|
@@ -2271,11 +2755,12 @@ program.command("create").description("Interactively scaffold a new wizard confi
|
|
|
2271
2755
|
});
|
|
2272
2756
|
program.command("demo").description("Run a demo wizard showcasing all step types").action(async () => {
|
|
2273
2757
|
try {
|
|
2274
|
-
const demoPath =
|
|
2758
|
+
const demoPath = resolve3(
|
|
2275
2759
|
fileURLToPath(import.meta.url),
|
|
2276
2760
|
"..",
|
|
2277
2761
|
"..",
|
|
2278
2762
|
"examples",
|
|
2763
|
+
"yaml",
|
|
2279
2764
|
"demo.yaml"
|
|
2280
2765
|
);
|
|
2281
2766
|
const config = await loadWizardConfig(demoPath);
|
|
@@ -2409,6 +2894,11 @@ function printDryRun(config) {
|
|
|
2409
2894
|
console.log(` Step ${num} ${typeStr} ${idStr} ${msg}${suffix}`);
|
|
2410
2895
|
}
|
|
2411
2896
|
console.log();
|
|
2897
|
+
if (config.onComplete) {
|
|
2898
|
+
console.log(` ${theme.bold("onComplete handler:")}`);
|
|
2899
|
+
console.log(` ${theme.muted(config.onComplete)}`);
|
|
2900
|
+
console.log();
|
|
2901
|
+
}
|
|
2412
2902
|
if (config.actions && config.actions.length > 0) {
|
|
2413
2903
|
console.log(` ${theme.bold("Post-wizard actions:")}`);
|
|
2414
2904
|
for (const action of config.actions) {
|
|
@@ -2462,7 +2952,10 @@ function resolveRenderer(rendererName) {
|
|
|
2462
2952
|
if (rendererName === "ink") {
|
|
2463
2953
|
return new InkRenderer();
|
|
2464
2954
|
}
|
|
2465
|
-
|
|
2955
|
+
if (rendererName === "clack") {
|
|
2956
|
+
return new ClackRenderer();
|
|
2957
|
+
}
|
|
2958
|
+
throw new Error(`Unknown renderer: "${rendererName}". Supported: inquirer, ink, clack`);
|
|
2466
2959
|
}
|
|
2467
2960
|
function toEnvKey(key) {
|
|
2468
2961
|
return key.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase();
|