grimoire-wizard 0.3.1 → 0.4.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 +173 -18
- package/dist/cli.js +503 -97
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +107 -3
- package/dist/index.js +832 -367
- package/dist/index.js.map +1 -1
- 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-checks.json +47 -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/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(),
|
|
@@ -194,7 +200,8 @@ var wizardConfigSchema = z.object({
|
|
|
194
200
|
meta: z.object({
|
|
195
201
|
name: z.string(),
|
|
196
202
|
version: z.string().optional(),
|
|
197
|
-
description: z.string().optional()
|
|
203
|
+
description: z.string().optional(),
|
|
204
|
+
review: z.boolean().optional()
|
|
198
205
|
}),
|
|
199
206
|
theme: themeConfigSchema.optional(),
|
|
200
207
|
steps: z.array(stepConfigSchema).min(1),
|
|
@@ -758,6 +765,67 @@ function applyValidationRule(rule, value) {
|
|
|
758
765
|
|
|
759
766
|
// src/theme.ts
|
|
760
767
|
import chalk from "chalk";
|
|
768
|
+
|
|
769
|
+
// src/themes/presets.ts
|
|
770
|
+
var THEME_PRESETS = {
|
|
771
|
+
default: {
|
|
772
|
+
primary: "#7C3AED",
|
|
773
|
+
success: "#10B981",
|
|
774
|
+
error: "#EF4444",
|
|
775
|
+
warning: "#F59E0B",
|
|
776
|
+
info: "#3B82F6",
|
|
777
|
+
muted: "#6B7280",
|
|
778
|
+
accent: "#8B5CF6"
|
|
779
|
+
},
|
|
780
|
+
catppuccin: {
|
|
781
|
+
primary: "#cba6f7",
|
|
782
|
+
success: "#a6e3a1",
|
|
783
|
+
error: "#f38ba8",
|
|
784
|
+
warning: "#fab387",
|
|
785
|
+
info: "#74c7ec",
|
|
786
|
+
muted: "#6c7086",
|
|
787
|
+
accent: "#f5c2e7"
|
|
788
|
+
},
|
|
789
|
+
dracula: {
|
|
790
|
+
primary: "#bd93f9",
|
|
791
|
+
success: "#50fa7b",
|
|
792
|
+
error: "#ff5555",
|
|
793
|
+
warning: "#ffb86c",
|
|
794
|
+
info: "#8be9fd",
|
|
795
|
+
muted: "#6272a4",
|
|
796
|
+
accent: "#ff79c6"
|
|
797
|
+
},
|
|
798
|
+
nord: {
|
|
799
|
+
primary: "#88c0d0",
|
|
800
|
+
success: "#a3be8c",
|
|
801
|
+
error: "#bf616a",
|
|
802
|
+
warning: "#ebcb8b",
|
|
803
|
+
info: "#81a1c1",
|
|
804
|
+
muted: "#4c566a",
|
|
805
|
+
accent: "#b48ead"
|
|
806
|
+
},
|
|
807
|
+
tokyonight: {
|
|
808
|
+
primary: "#7aa2f7",
|
|
809
|
+
success: "#9ece6a",
|
|
810
|
+
error: "#f7768e",
|
|
811
|
+
warning: "#e0af68",
|
|
812
|
+
info: "#7dcfff",
|
|
813
|
+
muted: "#565f89",
|
|
814
|
+
accent: "#bb9af7"
|
|
815
|
+
},
|
|
816
|
+
monokai: {
|
|
817
|
+
primary: "#ab9df2",
|
|
818
|
+
success: "#a9dc76",
|
|
819
|
+
error: "#ff6188",
|
|
820
|
+
warning: "#ffd866",
|
|
821
|
+
info: "#78dce8",
|
|
822
|
+
muted: "#727072",
|
|
823
|
+
accent: "#fc9867"
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
var PRESET_NAMES = Object.keys(THEME_PRESETS);
|
|
827
|
+
|
|
828
|
+
// src/theme.ts
|
|
761
829
|
var DEFAULT_TOKENS = {
|
|
762
830
|
primary: "#5B9BD5",
|
|
763
831
|
success: "#6BCB77",
|
|
@@ -774,7 +842,8 @@ var DEFAULT_ICONS = {
|
|
|
774
842
|
pointer: "\u203A"
|
|
775
843
|
};
|
|
776
844
|
function resolveTheme(themeConfig) {
|
|
777
|
-
const
|
|
845
|
+
const presetTokens = themeConfig?.preset ? THEME_PRESETS[themeConfig.preset] : void 0;
|
|
846
|
+
const tokens = { ...DEFAULT_TOKENS, ...presetTokens, ...themeConfig?.tokens };
|
|
778
847
|
const icons = { ...DEFAULT_ICONS, ...themeConfig?.icons };
|
|
779
848
|
return {
|
|
780
849
|
primary: chalk.hex(tokens.primary),
|
|
@@ -1116,14 +1185,66 @@ function clearCache(wizardName, customDir) {
|
|
|
1116
1185
|
}
|
|
1117
1186
|
}
|
|
1118
1187
|
|
|
1119
|
-
// src/
|
|
1120
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2,
|
|
1188
|
+
// src/progress.ts
|
|
1189
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
1121
1190
|
import { join as join2 } from "path";
|
|
1122
1191
|
import { homedir as homedir2 } from "os";
|
|
1123
|
-
var
|
|
1192
|
+
var DEFAULT_PROGRESS_DIR = join2(homedir2(), ".config", "grimoire", "progress");
|
|
1193
|
+
function getProgressFilePath(wizardName, customDir) {
|
|
1194
|
+
const dir = customDir ?? DEFAULT_PROGRESS_DIR;
|
|
1195
|
+
return join2(dir, `${slugify(wizardName)}.json`);
|
|
1196
|
+
}
|
|
1197
|
+
function saveProgress(wizardName, state, customDir, excludeStepIds) {
|
|
1198
|
+
try {
|
|
1199
|
+
const dir = customDir ?? DEFAULT_PROGRESS_DIR;
|
|
1200
|
+
mkdirSync2(dir, { recursive: true });
|
|
1201
|
+
const excludeSet = new Set(excludeStepIds ?? []);
|
|
1202
|
+
const filteredAnswers = {};
|
|
1203
|
+
for (const [key, value] of Object.entries(state.answers)) {
|
|
1204
|
+
if (!excludeSet.has(key)) {
|
|
1205
|
+
filteredAnswers[key] = value;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
const progress = {
|
|
1209
|
+
currentStepId: state.currentStepId,
|
|
1210
|
+
answers: filteredAnswers,
|
|
1211
|
+
history: state.history,
|
|
1212
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1213
|
+
};
|
|
1214
|
+
const filePath = getProgressFilePath(wizardName, customDir);
|
|
1215
|
+
writeFileSync2(filePath, JSON.stringify(progress, null, 2) + "\n", "utf-8");
|
|
1216
|
+
} catch {
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
function loadProgress(wizardName, customDir) {
|
|
1220
|
+
try {
|
|
1221
|
+
const filePath = getProgressFilePath(wizardName, customDir);
|
|
1222
|
+
const raw = readFileSync3(filePath, "utf-8");
|
|
1223
|
+
const parsed = JSON.parse(raw);
|
|
1224
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "currentStepId" in parsed && "answers" in parsed && "history" in parsed && "savedAt" in parsed) {
|
|
1225
|
+
return parsed;
|
|
1226
|
+
}
|
|
1227
|
+
return null;
|
|
1228
|
+
} catch {
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
function clearProgress(wizardName, customDir) {
|
|
1233
|
+
try {
|
|
1234
|
+
const filePath = getProgressFilePath(wizardName, customDir);
|
|
1235
|
+
unlinkSync2(filePath);
|
|
1236
|
+
} catch {
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// src/mru.ts
|
|
1241
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
|
|
1242
|
+
import { join as join3 } from "path";
|
|
1243
|
+
import { homedir as homedir3 } from "os";
|
|
1244
|
+
var MRU_DIR = join3(homedir3(), ".config", "grimoire", "mru");
|
|
1124
1245
|
function getMruFilePath(wizardName) {
|
|
1125
1246
|
const safeName = wizardName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1126
|
-
return
|
|
1247
|
+
return join3(MRU_DIR, `${safeName}.json`);
|
|
1127
1248
|
}
|
|
1128
1249
|
function loadMruData(wizardName) {
|
|
1129
1250
|
const filePath = getMruFilePath(wizardName);
|
|
@@ -1131,7 +1252,7 @@ function loadMruData(wizardName) {
|
|
|
1131
1252
|
if (!existsSync(filePath)) {
|
|
1132
1253
|
return {};
|
|
1133
1254
|
}
|
|
1134
|
-
const raw =
|
|
1255
|
+
const raw = readFileSync4(filePath, "utf-8");
|
|
1135
1256
|
const parsed = JSON.parse(raw);
|
|
1136
1257
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1137
1258
|
return {};
|
|
@@ -1144,8 +1265,8 @@ function loadMruData(wizardName) {
|
|
|
1144
1265
|
function saveMruData(wizardName, data) {
|
|
1145
1266
|
const filePath = getMruFilePath(wizardName);
|
|
1146
1267
|
try {
|
|
1147
|
-
|
|
1148
|
-
|
|
1268
|
+
mkdirSync3(MRU_DIR, { recursive: true });
|
|
1269
|
+
writeFileSync3(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
1149
1270
|
} catch {
|
|
1150
1271
|
}
|
|
1151
1272
|
}
|
|
@@ -1199,13 +1320,21 @@ function getOrderedOptions(wizardName, stepId, options) {
|
|
|
1199
1320
|
}
|
|
1200
1321
|
|
|
1201
1322
|
// src/runner.ts
|
|
1202
|
-
function
|
|
1323
|
+
function emitEvent(renderer, event, theme) {
|
|
1324
|
+
if (renderer.onEvent) {
|
|
1325
|
+
renderer.onEvent(event, theme);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
function runPreFlightChecks(checks, theme, renderer) {
|
|
1329
|
+
if (renderer) emitEvent(renderer, { type: "checks:start", checks }, theme);
|
|
1203
1330
|
for (const check of checks) {
|
|
1204
1331
|
try {
|
|
1205
1332
|
execSync(check.run, { stdio: "pipe" });
|
|
1206
1333
|
console.log(` ${theme.success("\u2713")} ${check.name}`);
|
|
1334
|
+
if (renderer) emitEvent(renderer, { type: "check:pass", name: check.name }, theme);
|
|
1207
1335
|
} catch {
|
|
1208
1336
|
console.log(` ${theme.error("\u2717")} ${check.name}: ${check.message}`);
|
|
1337
|
+
if (renderer) emitEvent(renderer, { type: "check:fail", name: check.name, message: check.message }, theme);
|
|
1209
1338
|
throw new Error(`Pre-flight check failed: ${check.name} \u2014 ${check.message}`);
|
|
1210
1339
|
}
|
|
1211
1340
|
}
|
|
@@ -1215,7 +1344,7 @@ function getMockValue(step, mockAnswers) {
|
|
|
1215
1344
|
if (step.id in mockAnswers) {
|
|
1216
1345
|
return mockAnswers[step.id];
|
|
1217
1346
|
}
|
|
1218
|
-
if (step.type === "message") {
|
|
1347
|
+
if (step.type === "message" || step.type === "note") {
|
|
1219
1348
|
return true;
|
|
1220
1349
|
}
|
|
1221
1350
|
const defaultValue = getStepDefault(step);
|
|
@@ -1243,6 +1372,7 @@ function getStepDefault(step) {
|
|
|
1243
1372
|
return step.default;
|
|
1244
1373
|
case "password":
|
|
1245
1374
|
case "message":
|
|
1375
|
+
case "note":
|
|
1246
1376
|
return void 0;
|
|
1247
1377
|
}
|
|
1248
1378
|
}
|
|
@@ -1256,6 +1386,21 @@ async function runWizard(config, options) {
|
|
|
1256
1386
|
const cacheDir = typeof options?.cache === "object" ? options.cache.dir : void 0;
|
|
1257
1387
|
const mruEnabled = !isMock && options?.mru !== false;
|
|
1258
1388
|
let state = createWizardState(config);
|
|
1389
|
+
const resumeEnabled = !isMock && options?.resume !== false;
|
|
1390
|
+
if (resumeEnabled) {
|
|
1391
|
+
const saved = loadProgress(config.meta.name);
|
|
1392
|
+
if (saved) {
|
|
1393
|
+
const stepExists = config.steps.some((s) => s.id === saved.currentStepId);
|
|
1394
|
+
if (stepExists) {
|
|
1395
|
+
state = {
|
|
1396
|
+
...state,
|
|
1397
|
+
currentStepId: saved.currentStepId,
|
|
1398
|
+
answers: { ...state.answers, ...saved.answers },
|
|
1399
|
+
history: saved.history
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1259
1404
|
const cachedAnswers = cacheEnabled ? loadCachedAnswers(config.meta.name, cacheDir) : void 0;
|
|
1260
1405
|
const userPlugins = options?.plugins;
|
|
1261
1406
|
if (userPlugins) {
|
|
@@ -1265,103 +1410,169 @@ async function runWizard(config, options) {
|
|
|
1265
1410
|
}
|
|
1266
1411
|
try {
|
|
1267
1412
|
if (!isMock && config.checks && config.checks.length > 0) {
|
|
1268
|
-
runPreFlightChecks(config.checks, theme);
|
|
1413
|
+
runPreFlightChecks(config.checks, theme, renderer);
|
|
1269
1414
|
}
|
|
1270
1415
|
if (!quiet) {
|
|
1271
1416
|
printWizardHeader(config, theme, options?.plain);
|
|
1272
1417
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1418
|
+
const visibleStepsForCount = getVisibleSteps(config, state.answers);
|
|
1419
|
+
emitEvent(renderer, { type: "session:start", wizard: config.meta.name, description: config.meta.description, totalSteps: visibleStepsForCount.length }, theme);
|
|
1420
|
+
let needsReview = true;
|
|
1421
|
+
while (needsReview) {
|
|
1422
|
+
let previousGroup;
|
|
1423
|
+
while (state.status === "running") {
|
|
1424
|
+
const visibleSteps = getVisibleSteps(config, state.answers);
|
|
1425
|
+
const currentStep = config.steps.find((s) => s.id === state.currentStepId);
|
|
1426
|
+
if (!currentStep) {
|
|
1427
|
+
throw new Error(`Current step not found: "${state.currentStepId}"`);
|
|
1428
|
+
}
|
|
1281
1429
|
if (currentStep.group !== void 0 && currentStep.group !== previousGroup) {
|
|
1282
1430
|
const resolvedGroup = resolveTemplate(currentStep.group, state.answers);
|
|
1283
|
-
|
|
1431
|
+
if (!isMock) {
|
|
1432
|
+
renderer.renderGroupHeader(resolvedGroup, theme);
|
|
1433
|
+
}
|
|
1434
|
+
emitEvent(renderer, { type: "group:start", group: resolvedGroup }, theme);
|
|
1284
1435
|
}
|
|
1285
1436
|
previousGroup = currentStep.group;
|
|
1286
1437
|
const stepIndex = visibleSteps.findIndex((s) => s.id === state.currentStepId);
|
|
1287
1438
|
const resolvedMessage = resolveTemplate(currentStep.message, state.answers);
|
|
1288
1439
|
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
|
-
|
|
1440
|
+
if (!isMock) {
|
|
1441
|
+
renderer.renderStepHeader(stepIndex, visibleSteps.length, resolvedMessage, theme, resolvedDescription);
|
|
1442
|
+
}
|
|
1443
|
+
emitEvent(renderer, { type: "step:start", stepId: currentStep.id, stepIndex, totalVisible: visibleSteps.length, step: currentStep }, theme);
|
|
1444
|
+
if (currentStep.type === "note") {
|
|
1445
|
+
emitEvent(renderer, { type: "note", title: resolvedMessage, body: resolvedDescription ?? "" }, theme);
|
|
1446
|
+
}
|
|
1447
|
+
if (options?.onBeforeStep) {
|
|
1448
|
+
await options.onBeforeStep(currentStep.id, currentStep, state);
|
|
1449
|
+
}
|
|
1450
|
+
const pluginStep = getPluginStep(currentStep.type);
|
|
1451
|
+
const resolvedStep = pluginStep ? currentStep : resolveStepDefaults(currentStep, cachedAnswers);
|
|
1452
|
+
const withTemplate = options?.templateAnswers ? applyTemplateDefaults(resolvedStep, options.templateAnswers) : resolvedStep;
|
|
1453
|
+
const templatedStep = resolveStepTemplates(withTemplate, state.answers);
|
|
1454
|
+
const mruStep = mruEnabled ? applyMruOrdering(templatedStep, config.meta.name) : templatedStep;
|
|
1455
|
+
try {
|
|
1456
|
+
const value = isMock ? getMockValue(mruStep, mockAnswers) : pluginStep ? await pluginStep.render(toStepRecord(mruStep), state, theme) : await renderStep(renderer, mruStep, state, theme);
|
|
1457
|
+
if (pluginStep?.validate) {
|
|
1458
|
+
const pluginError = pluginStep.validate(value, toStepRecord(templatedStep));
|
|
1459
|
+
if (pluginError) {
|
|
1460
|
+
if (isMock) {
|
|
1461
|
+
throw new Error(
|
|
1462
|
+
`Mock mode: validation failed for step "${currentStep.id}": ${pluginError}`
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
console.log(theme.error(`
|
|
1466
|
+
${pluginError}
|
|
1467
|
+
`));
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
const nextState = wizardReducer(state, { type: "NEXT", value }, config);
|
|
1472
|
+
if (nextState.errors[currentStep.id]) {
|
|
1473
|
+
const errorMsg = resolveTemplate(nextState.errors[currentStep.id] ?? "", state.answers);
|
|
1474
|
+
emitEvent(renderer, { type: "step:error", stepId: currentStep.id, error: errorMsg }, theme);
|
|
1304
1475
|
if (isMock) {
|
|
1305
1476
|
throw new Error(
|
|
1306
|
-
`Mock mode: validation failed for step "${currentStep.id}": ${
|
|
1477
|
+
`Mock mode: validation failed for step "${currentStep.id}": ${errorMsg}`
|
|
1307
1478
|
);
|
|
1308
1479
|
}
|
|
1309
1480
|
console.log(theme.error(`
|
|
1310
|
-
${
|
|
1481
|
+
${errorMsg}
|
|
1311
1482
|
`));
|
|
1483
|
+
state = { ...nextState, errors: {} };
|
|
1312
1484
|
continue;
|
|
1313
1485
|
}
|
|
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(`
|
|
1486
|
+
if (!isMock && options?.asyncValidate) {
|
|
1487
|
+
const asyncError = await options.asyncValidate(currentStep.id, value, nextState.answers);
|
|
1488
|
+
if (asyncError !== null) {
|
|
1489
|
+
console.log(theme.error(`
|
|
1333
1490
|
${asyncError}
|
|
1334
1491
|
`));
|
|
1335
|
-
|
|
1336
|
-
|
|
1492
|
+
state = { ...nextState, errors: {} };
|
|
1493
|
+
continue;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
if (options?.onAfterStep) {
|
|
1497
|
+
await options.onAfterStep(currentStep.id, value, nextState);
|
|
1498
|
+
}
|
|
1499
|
+
state = nextState;
|
|
1500
|
+
emitEvent(renderer, { type: "step:complete", stepId: currentStep.id, value, step: currentStep }, theme);
|
|
1501
|
+
if (mruEnabled && isSelectLikeStep(currentStep.type)) {
|
|
1502
|
+
recordSelection(config.meta.name, currentStep.id, value);
|
|
1337
1503
|
}
|
|
1504
|
+
options?.onStepComplete?.(currentStep.id, value, state);
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
if (!isMock && isUserCancel(error)) {
|
|
1507
|
+
state = wizardReducer(state, { type: "CANCEL" }, config);
|
|
1508
|
+
options?.onCancel?.(state);
|
|
1509
|
+
const passwordStepIds = config.steps.filter((s) => s.type === "password").map((s) => s.id);
|
|
1510
|
+
saveProgress(config.meta.name, {
|
|
1511
|
+
currentStepId: state.currentStepId,
|
|
1512
|
+
answers: state.answers,
|
|
1513
|
+
history: state.history
|
|
1514
|
+
}, void 0, passwordStepIds);
|
|
1515
|
+
emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: true }, theme);
|
|
1516
|
+
if (!quiet) {
|
|
1517
|
+
console.log(theme.warning("\n Wizard cancelled.\n"));
|
|
1518
|
+
}
|
|
1519
|
+
return state.answers;
|
|
1520
|
+
}
|
|
1521
|
+
throw error;
|
|
1338
1522
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1523
|
+
}
|
|
1524
|
+
if (config.meta.review && !isMock && state.status === "done") {
|
|
1525
|
+
const reviewLines = [];
|
|
1526
|
+
for (const step of config.steps) {
|
|
1527
|
+
const answer = state.answers[step.id];
|
|
1528
|
+
if (answer === void 0) continue;
|
|
1529
|
+
const display = step.type === "password" ? "****" : Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
|
|
1530
|
+
reviewLines.push(`${step.id}: ${display}`);
|
|
1341
1531
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1532
|
+
emitEvent(renderer, { type: "note", title: "Review your answers", body: reviewLines.join("\n") }, theme);
|
|
1533
|
+
console.log(`
|
|
1534
|
+
${theme.bold("Review your answers:")}
|
|
1535
|
+
`);
|
|
1536
|
+
for (const line of reviewLines) {
|
|
1537
|
+
console.log(` ${line}`);
|
|
1345
1538
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1539
|
+
console.log();
|
|
1540
|
+
const { confirm: confirmPrompt } = await import("@inquirer/prompts");
|
|
1541
|
+
const ok = await confirmPrompt({
|
|
1542
|
+
message: "Everything look right?",
|
|
1543
|
+
default: true
|
|
1544
|
+
});
|
|
1545
|
+
if (ok) {
|
|
1546
|
+
needsReview = false;
|
|
1547
|
+
} else {
|
|
1548
|
+
const { select: selectPrompt } = await import("@inquirer/prompts");
|
|
1549
|
+
const stepsWithAnswers = config.steps.filter(
|
|
1550
|
+
(s) => state.answers[s.id] !== void 0 && s.type !== "note" && s.type !== "message"
|
|
1551
|
+
);
|
|
1552
|
+
const stepToRevisit = await selectPrompt({
|
|
1553
|
+
message: "Which step would you like to change?",
|
|
1554
|
+
choices: stepsWithAnswers.map((s) => ({
|
|
1555
|
+
name: `${s.id}: ${s.type === "password" ? "****" : String(state.answers[s.id] ?? "")}`,
|
|
1556
|
+
value: s.id
|
|
1557
|
+
}))
|
|
1558
|
+
});
|
|
1559
|
+
state = {
|
|
1560
|
+
...state,
|
|
1561
|
+
currentStepId: stepToRevisit,
|
|
1562
|
+
status: "running"
|
|
1563
|
+
};
|
|
1355
1564
|
}
|
|
1356
|
-
|
|
1565
|
+
} else {
|
|
1566
|
+
needsReview = false;
|
|
1357
1567
|
}
|
|
1358
1568
|
}
|
|
1359
1569
|
if (state.status === "done" && !quiet) {
|
|
1360
1570
|
renderer.renderSummary(state.answers, config.steps, theme);
|
|
1361
1571
|
}
|
|
1362
1572
|
if (state.status === "done" && config.actions && config.actions.length > 0 && !isMock) {
|
|
1363
|
-
await executeActions(config.actions, state.answers, theme);
|
|
1573
|
+
await executeActions(config.actions, state.answers, theme, renderer);
|
|
1364
1574
|
}
|
|
1575
|
+
emitEvent(renderer, { type: "session:end", answers: state.answers, cancelled: state.status === "cancelled" }, theme);
|
|
1365
1576
|
if (state.status === "done" && cacheEnabled) {
|
|
1366
1577
|
const passwordStepIds = new Set(
|
|
1367
1578
|
config.steps.filter((s) => s.type === "password").map((s) => s.id)
|
|
@@ -1374,6 +1585,9 @@ async function runWizard(config, options) {
|
|
|
1374
1585
|
}
|
|
1375
1586
|
saveCachedAnswers(config.meta.name, answersToCache, cacheDir);
|
|
1376
1587
|
}
|
|
1588
|
+
if (state.status === "done") {
|
|
1589
|
+
clearProgress(config.meta.name);
|
|
1590
|
+
}
|
|
1377
1591
|
return state.answers;
|
|
1378
1592
|
} finally {
|
|
1379
1593
|
if (userPlugins) {
|
|
@@ -1413,6 +1627,8 @@ function renderStep(renderer, step, state, theme) {
|
|
|
1413
1627
|
case "message":
|
|
1414
1628
|
renderer.renderMessage(step, state, theme);
|
|
1415
1629
|
return Promise.resolve(true);
|
|
1630
|
+
case "note":
|
|
1631
|
+
return Promise.resolve(true);
|
|
1416
1632
|
}
|
|
1417
1633
|
}
|
|
1418
1634
|
function resolveStepDefaults(step, cachedAnswers) {
|
|
@@ -1463,6 +1679,7 @@ function resolveStepDefaults(step, cachedAnswers) {
|
|
|
1463
1679
|
}
|
|
1464
1680
|
case "password":
|
|
1465
1681
|
case "message":
|
|
1682
|
+
case "note":
|
|
1466
1683
|
return step;
|
|
1467
1684
|
}
|
|
1468
1685
|
}
|
|
@@ -1472,7 +1689,7 @@ function getCachedDefault(stepId, cachedAnswers) {
|
|
|
1472
1689
|
}
|
|
1473
1690
|
function applyTemplateDefaults(step, templateAnswers) {
|
|
1474
1691
|
if (!(step.id in templateAnswers)) return step;
|
|
1475
|
-
if (step.type === "password" || step.type === "message") return step;
|
|
1692
|
+
if (step.type === "password" || step.type === "message" || step.type === "note") return step;
|
|
1476
1693
|
const value = templateAnswers[step.id];
|
|
1477
1694
|
switch (step.type) {
|
|
1478
1695
|
case "text":
|
|
@@ -1561,13 +1778,15 @@ function resolveStepTemplates(step, answers) {
|
|
|
1561
1778
|
case "confirm":
|
|
1562
1779
|
case "toggle":
|
|
1563
1780
|
case "message":
|
|
1781
|
+
case "note":
|
|
1564
1782
|
return {
|
|
1565
1783
|
...step,
|
|
1566
1784
|
description: step.description ? resolveTemplate(step.description, answers) : void 0
|
|
1567
1785
|
};
|
|
1568
1786
|
}
|
|
1569
1787
|
}
|
|
1570
|
-
async function executeActions(actions, answers, theme) {
|
|
1788
|
+
async function executeActions(actions, answers, theme, renderer) {
|
|
1789
|
+
if (renderer) emitEvent(renderer, { type: "actions:start" }, theme);
|
|
1571
1790
|
console.log(`
|
|
1572
1791
|
${theme.bold("Running actions...")}
|
|
1573
1792
|
`);
|
|
@@ -1581,8 +1800,10 @@ async function executeActions(actions, answers, theme) {
|
|
|
1581
1800
|
try {
|
|
1582
1801
|
execSync(resolvedCommand, { stdio: "pipe" });
|
|
1583
1802
|
console.log(` ${theme.success("\u2713")} ${label}`);
|
|
1803
|
+
if (renderer) emitEvent(renderer, { type: "action:pass", name: label }, theme);
|
|
1584
1804
|
} catch {
|
|
1585
1805
|
console.log(` ${theme.error("\u2717")} ${label}`);
|
|
1806
|
+
if (renderer) emitEvent(renderer, { type: "action:fail", name: label }, theme);
|
|
1586
1807
|
throw new Error(`Action failed: ${label}`);
|
|
1587
1808
|
}
|
|
1588
1809
|
}
|
|
@@ -1606,7 +1827,7 @@ function isUserCancel(error) {
|
|
|
1606
1827
|
// src/scaffolder.ts
|
|
1607
1828
|
import { input as input2, select as select2, confirm as confirm2, number as number2 } from "@inquirer/prompts";
|
|
1608
1829
|
import { stringify } from "yaml";
|
|
1609
|
-
import { writeFileSync as
|
|
1830
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
1610
1831
|
import { relative } from "path";
|
|
1611
1832
|
var STEP_TYPE_CHOICES = [
|
|
1612
1833
|
{ name: "text", value: "text" },
|
|
@@ -1723,7 +1944,7 @@ async function scaffoldWizard(outputPath) {
|
|
|
1723
1944
|
config.theme = { tokens: { primary: primaryColor } };
|
|
1724
1945
|
}
|
|
1725
1946
|
const yamlContent = stringify(config);
|
|
1726
|
-
|
|
1947
|
+
writeFileSync4(outputPath, yamlContent, "utf-8");
|
|
1727
1948
|
const displayPath = relative(process.cwd(), outputPath);
|
|
1728
1949
|
console.log(`
|
|
1729
1950
|
\u2713 Created wizard config: ${outputPath}`);
|
|
@@ -1914,20 +2135,20 @@ complete -c grimoire -n "__fish_seen_subcommand_from completion" -f -a "fish" -d
|
|
|
1914
2135
|
}
|
|
1915
2136
|
|
|
1916
2137
|
// src/templates.ts
|
|
1917
|
-
import { mkdirSync as
|
|
1918
|
-
import { join as
|
|
1919
|
-
import { homedir as
|
|
1920
|
-
var TEMPLATES_DIR =
|
|
2138
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
|
|
2139
|
+
import { join as join4 } from "path";
|
|
2140
|
+
import { homedir as homedir4 } from "os";
|
|
2141
|
+
var TEMPLATES_DIR = join4(homedir4(), ".config", "grimoire", "templates");
|
|
1921
2142
|
function getWizardTemplateDir(wizardName) {
|
|
1922
|
-
return
|
|
2143
|
+
return join4(TEMPLATES_DIR, slugify(wizardName));
|
|
1923
2144
|
}
|
|
1924
2145
|
function getTemplateFilePath(wizardName, templateName) {
|
|
1925
|
-
return
|
|
2146
|
+
return join4(getWizardTemplateDir(wizardName), `${slugify(templateName)}.json`);
|
|
1926
2147
|
}
|
|
1927
2148
|
function loadTemplate(wizardName, templateName) {
|
|
1928
2149
|
try {
|
|
1929
2150
|
const filePath = getTemplateFilePath(wizardName, templateName);
|
|
1930
|
-
const raw =
|
|
2151
|
+
const raw = readFileSync5(filePath, "utf-8");
|
|
1931
2152
|
const parsed = JSON.parse(raw);
|
|
1932
2153
|
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1933
2154
|
return parsed;
|
|
@@ -1949,7 +2170,7 @@ function listTemplates(wizardName) {
|
|
|
1949
2170
|
function deleteTemplate(wizardName, templateName) {
|
|
1950
2171
|
try {
|
|
1951
2172
|
const filePath = getTemplateFilePath(wizardName, templateName);
|
|
1952
|
-
|
|
2173
|
+
unlinkSync3(filePath);
|
|
1953
2174
|
} catch {
|
|
1954
2175
|
}
|
|
1955
2176
|
}
|
|
@@ -2149,23 +2370,203 @@ var InkRenderer = class {
|
|
|
2149
2370
|
}
|
|
2150
2371
|
};
|
|
2151
2372
|
|
|
2373
|
+
// src/renderers/clack.ts
|
|
2374
|
+
import chalk2 from "chalk";
|
|
2375
|
+
|
|
2376
|
+
// src/renderers/symbols.ts
|
|
2377
|
+
function isUnicodeSupported() {
|
|
2378
|
+
if (process.platform === "win32") {
|
|
2379
|
+
return Boolean(process.env["WT_SESSION"]) || process.env["TERM_PROGRAM"] === "vscode";
|
|
2380
|
+
}
|
|
2381
|
+
return process.env["TERM"] !== "linux";
|
|
2382
|
+
}
|
|
2383
|
+
var unicode = isUnicodeSupported();
|
|
2384
|
+
var u = (unicodeChar, fallback) => unicode ? unicodeChar : fallback;
|
|
2385
|
+
var S_BAR_START = u("\u250C", "T");
|
|
2386
|
+
var S_BAR = u("\u2502", "|");
|
|
2387
|
+
var S_BAR_END = u("\u2514", "\u2014");
|
|
2388
|
+
var S_STEP_ACTIVE = u("\u25C6", "*");
|
|
2389
|
+
var S_STEP_SUBMIT = u("\u25C7", "o");
|
|
2390
|
+
var S_STEP_CANCEL = u("\u25A0", "x");
|
|
2391
|
+
var S_STEP_ERROR = u("\u25B2", "x");
|
|
2392
|
+
var S_CORNER_TR = u("\u256E", "+");
|
|
2393
|
+
var S_CORNER_BR = u("\u256F", "+");
|
|
2394
|
+
var S_BAR_H = u("\u2500", "-");
|
|
2395
|
+
var S_SPINNER_FRAMES = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"];
|
|
2396
|
+
|
|
2397
|
+
// src/renderers/clack.ts
|
|
2398
|
+
var ClackRenderer = class extends InquirerRenderer {
|
|
2399
|
+
spinnerInterval;
|
|
2400
|
+
spinnerFrameIndex = 0;
|
|
2401
|
+
renderStepHeader() {
|
|
2402
|
+
}
|
|
2403
|
+
renderGroupHeader() {
|
|
2404
|
+
}
|
|
2405
|
+
renderSummary(answers, steps, theme) {
|
|
2406
|
+
const entries = [];
|
|
2407
|
+
for (const step of steps) {
|
|
2408
|
+
const answer = answers[step.id];
|
|
2409
|
+
if (answer === void 0) continue;
|
|
2410
|
+
const display = Array.isArray(answer) ? answer.map(String).join(", ") : String(answer);
|
|
2411
|
+
entries.push({ id: step.id, display });
|
|
2412
|
+
}
|
|
2413
|
+
if (entries.length === 0) return;
|
|
2414
|
+
const title = "Summary";
|
|
2415
|
+
const lines = entries.map((e) => `${e.id}: ${e.display}`);
|
|
2416
|
+
this.writeNoteBox(title, lines, theme);
|
|
2417
|
+
}
|
|
2418
|
+
onEvent(event, theme) {
|
|
2419
|
+
switch (event.type) {
|
|
2420
|
+
case "session:start":
|
|
2421
|
+
this.handleSessionStart(event, theme);
|
|
2422
|
+
break;
|
|
2423
|
+
case "session:end":
|
|
2424
|
+
this.handleSessionEnd(event, theme);
|
|
2425
|
+
break;
|
|
2426
|
+
case "step:start":
|
|
2427
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2428
|
+
`);
|
|
2429
|
+
break;
|
|
2430
|
+
case "step:complete":
|
|
2431
|
+
this.handleStepComplete(event, theme);
|
|
2432
|
+
break;
|
|
2433
|
+
case "step:error":
|
|
2434
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(`${S_STEP_ERROR} ${event.error}`)}
|
|
2435
|
+
`);
|
|
2436
|
+
break;
|
|
2437
|
+
case "step:back":
|
|
2438
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.muted("\u21A9 Back")}
|
|
2439
|
+
`);
|
|
2440
|
+
break;
|
|
2441
|
+
case "group:start":
|
|
2442
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2443
|
+
`);
|
|
2444
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.accent(event.group)}
|
|
2445
|
+
`);
|
|
2446
|
+
break;
|
|
2447
|
+
case "note":
|
|
2448
|
+
this.writeNoteBox(event.title, event.body.split("\n"), theme);
|
|
2449
|
+
break;
|
|
2450
|
+
case "spinner:start":
|
|
2451
|
+
this.startSpinner(event.message, theme);
|
|
2452
|
+
break;
|
|
2453
|
+
case "spinner:stop":
|
|
2454
|
+
this.stopSpinner(event.message, theme);
|
|
2455
|
+
break;
|
|
2456
|
+
case "checks:start":
|
|
2457
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2458
|
+
`);
|
|
2459
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running checks...")}
|
|
2460
|
+
`);
|
|
2461
|
+
break;
|
|
2462
|
+
case "check:pass":
|
|
2463
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
|
|
2464
|
+
`);
|
|
2465
|
+
break;
|
|
2466
|
+
case "check:fail":
|
|
2467
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}: ${event.message}
|
|
2468
|
+
`);
|
|
2469
|
+
break;
|
|
2470
|
+
case "actions:start":
|
|
2471
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2472
|
+
`);
|
|
2473
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.bold("Running actions...")}
|
|
2474
|
+
`);
|
|
2475
|
+
break;
|
|
2476
|
+
case "action:pass":
|
|
2477
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${event.name}
|
|
2478
|
+
`);
|
|
2479
|
+
break;
|
|
2480
|
+
case "action:fail":
|
|
2481
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.error(S_STEP_ERROR)} ${event.name}
|
|
2482
|
+
`);
|
|
2483
|
+
break;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
handleSessionStart(event, theme) {
|
|
2487
|
+
process.stdout.write(`${chalk2.gray(S_BAR_START)} ${theme.bold(event.wizard)}
|
|
2488
|
+
`);
|
|
2489
|
+
if (event.description) {
|
|
2490
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${theme.muted(event.description)}
|
|
2491
|
+
`);
|
|
2492
|
+
}
|
|
2493
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2494
|
+
`);
|
|
2495
|
+
}
|
|
2496
|
+
handleSessionEnd(event, theme) {
|
|
2497
|
+
if (event.cancelled) {
|
|
2498
|
+
process.stdout.write(`${theme.warning(S_STEP_CANCEL)} Cancelled
|
|
2499
|
+
`);
|
|
2500
|
+
} else {
|
|
2501
|
+
process.stdout.write(`${chalk2.gray(S_BAR_END)} ${theme.success("You're all set!")}
|
|
2502
|
+
`);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
handleStepComplete(event, theme) {
|
|
2506
|
+
const displayValue = event.step.type === "password" ? "****" : this.formatValue(event.value);
|
|
2507
|
+
process.stdout.write(
|
|
2508
|
+
`${chalk2.gray(S_STEP_SUBMIT)} ${event.step.message} ${chalk2.gray("\xB7")} ${theme.muted(displayValue)}
|
|
2509
|
+
`
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
formatValue(value) {
|
|
2513
|
+
if (Array.isArray(value)) return value.map(String).join(", ");
|
|
2514
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
2515
|
+
return String(value);
|
|
2516
|
+
}
|
|
2517
|
+
writeNoteBox(title, lines, _theme) {
|
|
2518
|
+
const maxLen = Math.max(title.length, ...lines.map((l) => l.length));
|
|
2519
|
+
const padded = maxLen + 2;
|
|
2520
|
+
const topLine = `${S_BAR_H.repeat(padded - title.length - 1)}${S_CORNER_TR}`;
|
|
2521
|
+
const bottomLine = `${S_BAR_H.repeat(padded)}${S_CORNER_BR}`;
|
|
2522
|
+
process.stdout.write(`${chalk2.gray(S_BAR)}
|
|
2523
|
+
`);
|
|
2524
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256D${S_BAR_H} ${title} ${topLine}`)}
|
|
2525
|
+
`);
|
|
2526
|
+
for (const line of lines) {
|
|
2527
|
+
const pad = " ".repeat(maxLen - line.length);
|
|
2528
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(S_BAR)} ${line}${pad} ${chalk2.gray(S_BAR)}
|
|
2529
|
+
`);
|
|
2530
|
+
}
|
|
2531
|
+
process.stdout.write(`${chalk2.gray(S_BAR)} ${chalk2.gray(`\u256E${bottomLine}`)}
|
|
2532
|
+
`);
|
|
2533
|
+
}
|
|
2534
|
+
startSpinner(message, _theme) {
|
|
2535
|
+
this.spinnerFrameIndex = 0;
|
|
2536
|
+
this.spinnerInterval = setInterval(() => {
|
|
2537
|
+
const frame = S_SPINNER_FRAMES[this.spinnerFrameIndex % S_SPINNER_FRAMES.length];
|
|
2538
|
+
process.stdout.write(`\r${chalk2.gray(S_BAR)} ${chalk2.cyan(frame ?? "")} ${message}`);
|
|
2539
|
+
this.spinnerFrameIndex++;
|
|
2540
|
+
}, 80);
|
|
2541
|
+
}
|
|
2542
|
+
stopSpinner(message, theme) {
|
|
2543
|
+
if (this.spinnerInterval) {
|
|
2544
|
+
clearInterval(this.spinnerInterval);
|
|
2545
|
+
this.spinnerInterval = void 0;
|
|
2546
|
+
}
|
|
2547
|
+
const finalMessage = message ?? "Done";
|
|
2548
|
+
process.stdout.write(`\r${chalk2.gray(S_BAR)} ${theme.success(S_STEP_SUBMIT)} ${finalMessage}
|
|
2549
|
+
`);
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
|
|
2152
2553
|
// src/cli.ts
|
|
2153
|
-
import { writeFileSync as
|
|
2554
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
2154
2555
|
import { resolve as resolve2 } from "path";
|
|
2155
2556
|
import { fileURLToPath } from "url";
|
|
2156
2557
|
import { stringify as yamlStringify } from "yaml";
|
|
2157
2558
|
var plainMode = false;
|
|
2158
2559
|
function applyColorMode(opts) {
|
|
2159
2560
|
if (opts.plain || opts.color === false || process.env["NO_COLOR"] !== void 0) {
|
|
2160
|
-
|
|
2561
|
+
chalk3.level = 0;
|
|
2161
2562
|
plainMode = true;
|
|
2162
2563
|
}
|
|
2163
2564
|
}
|
|
2164
|
-
program.name("grimoire").description("Config-driven CLI wizard framework").version("0.
|
|
2565
|
+
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
2566
|
const globalOpts = program.opts();
|
|
2166
2567
|
applyColorMode(globalOpts);
|
|
2167
2568
|
});
|
|
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
|
|
2569
|
+
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
2570
|
try {
|
|
2170
2571
|
const fullPath = resolve2(configPath);
|
|
2171
2572
|
const config = await loadWizardConfig(fullPath);
|
|
@@ -2192,7 +2593,8 @@ program.command("run").description("Run a wizard from a config file").argument("
|
|
|
2192
2593
|
plain: plainMode,
|
|
2193
2594
|
mockAnswers,
|
|
2194
2595
|
templateAnswers,
|
|
2195
|
-
cache: opts.cache
|
|
2596
|
+
cache: opts.cache,
|
|
2597
|
+
resume: opts.resume
|
|
2196
2598
|
});
|
|
2197
2599
|
const rawOutputPath = opts.output ?? config.output?.path;
|
|
2198
2600
|
const outputPath = rawOutputPath ? resolve2(resolveTemplate(rawOutputPath, answers)) : void 0;
|
|
@@ -2208,14 +2610,14 @@ program.command("run").description("Run a wizard from a config file").argument("
|
|
|
2208
2610
|
const jsonStr = JSON.stringify(result, null, 2);
|
|
2209
2611
|
console.log(jsonStr);
|
|
2210
2612
|
if (outputPath) {
|
|
2211
|
-
|
|
2613
|
+
writeFileSync6(outputPath, jsonStr + "\n", "utf-8");
|
|
2212
2614
|
}
|
|
2213
2615
|
return;
|
|
2214
2616
|
}
|
|
2215
2617
|
if (outputPath) {
|
|
2216
2618
|
const format = opts.format ?? config.output?.format ?? "json";
|
|
2217
2619
|
const content = formatOutput(answers, format);
|
|
2218
|
-
|
|
2620
|
+
writeFileSync6(outputPath, content, "utf-8");
|
|
2219
2621
|
if (!opts.quiet) {
|
|
2220
2622
|
console.log(`
|
|
2221
2623
|
Answers written to: ${outputPath}
|
|
@@ -2276,6 +2678,7 @@ program.command("demo").description("Run a demo wizard showcasing all step types
|
|
|
2276
2678
|
"..",
|
|
2277
2679
|
"..",
|
|
2278
2680
|
"examples",
|
|
2681
|
+
"yaml",
|
|
2279
2682
|
"demo.yaml"
|
|
2280
2683
|
);
|
|
2281
2684
|
const config = await loadWizardConfig(demoPath);
|
|
@@ -2462,7 +2865,10 @@ function resolveRenderer(rendererName) {
|
|
|
2462
2865
|
if (rendererName === "ink") {
|
|
2463
2866
|
return new InkRenderer();
|
|
2464
2867
|
}
|
|
2465
|
-
|
|
2868
|
+
if (rendererName === "clack") {
|
|
2869
|
+
return new ClackRenderer();
|
|
2870
|
+
}
|
|
2871
|
+
throw new Error(`Unknown renderer: "${rendererName}". Supported: inquirer, ink, clack`);
|
|
2466
2872
|
}
|
|
2467
2873
|
function toEnvKey(key) {
|
|
2468
2874
|
return key.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase();
|