simple-scaffold 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +225 -114
- package/cmd.js +126 -8
- package/cmd.js.map +1 -1
- package/file.d.ts +2 -1
- package/ignore.d.ts +21 -0
- package/index.d.ts +1 -0
- package/index.js +4 -1
- package/index.js.map +1 -1
- package/init.d.ts +20 -0
- package/logger.d.ts +9 -1
- package/package.json +29 -9
- package/prompts.d.ts +11 -2
- package/scaffold-DOzCgpZT.js +1263 -0
- package/scaffold-DOzCgpZT.js.map +1 -0
- package/types.d.ts +76 -2
- package/validate.d.ts +96 -0
- package/scaffold-Ce-rIwy9.js +0 -2636
- package/scaffold-Ce-rIwy9.js.map +0 -1
|
@@ -0,0 +1,1263 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
//#endregion
|
|
23
|
+
let node_path = require("node:path");
|
|
24
|
+
node_path = __toESM(node_path);
|
|
25
|
+
let node_os = require("node:os");
|
|
26
|
+
node_os = __toESM(node_os);
|
|
27
|
+
let node_child_process = require("node:child_process");
|
|
28
|
+
let node_fs_promises = require("node:fs/promises");
|
|
29
|
+
node_fs_promises = __toESM(node_fs_promises);
|
|
30
|
+
let glob = require("glob");
|
|
31
|
+
let util = require("util");
|
|
32
|
+
util = __toESM(util);
|
|
33
|
+
let handlebars = require("handlebars");
|
|
34
|
+
handlebars = __toESM(handlebars);
|
|
35
|
+
let date_fns = require("date-fns");
|
|
36
|
+
let node_constants = require("node:constants");
|
|
37
|
+
let _inquirer_input = require("@inquirer/input");
|
|
38
|
+
_inquirer_input = __toESM(_inquirer_input);
|
|
39
|
+
let _inquirer_select = require("@inquirer/select");
|
|
40
|
+
_inquirer_select = __toESM(_inquirer_select);
|
|
41
|
+
let _inquirer_confirm = require("@inquirer/confirm");
|
|
42
|
+
_inquirer_confirm = __toESM(_inquirer_confirm);
|
|
43
|
+
let _inquirer_number = require("@inquirer/number");
|
|
44
|
+
_inquirer_number = __toESM(_inquirer_number);
|
|
45
|
+
let minimatch = require("minimatch");
|
|
46
|
+
let zod_v4 = require("zod/v4");
|
|
47
|
+
//#region src/colors.ts
|
|
48
|
+
/** ANSI color code mapping for terminal output. */
|
|
49
|
+
var colorMap = {
|
|
50
|
+
reset: 0,
|
|
51
|
+
dim: 2,
|
|
52
|
+
bold: 1,
|
|
53
|
+
italic: 3,
|
|
54
|
+
underline: 4,
|
|
55
|
+
red: 31,
|
|
56
|
+
green: 32,
|
|
57
|
+
yellow: 33,
|
|
58
|
+
blue: 34,
|
|
59
|
+
magenta: 35,
|
|
60
|
+
cyan: 36,
|
|
61
|
+
white: 37,
|
|
62
|
+
gray: 90
|
|
63
|
+
};
|
|
64
|
+
function _colorize(text, color) {
|
|
65
|
+
const c = colorMap[color];
|
|
66
|
+
let r;
|
|
67
|
+
if (c > 1 && c < 30) r = c + 20;
|
|
68
|
+
else if (c === 1) r = 23;
|
|
69
|
+
else r = 0;
|
|
70
|
+
return `\x1b[${c}m${text}\x1b[${r}m`;
|
|
71
|
+
}
|
|
72
|
+
function isTemplateStringArray(template) {
|
|
73
|
+
return Array.isArray(template) && typeof template[0] === "string";
|
|
74
|
+
}
|
|
75
|
+
var createColorize = (color) => (template, ...params) => {
|
|
76
|
+
return isTemplateStringArray(template) ? _colorize(template.reduce((acc, str, i) => acc + str + (params[i] ?? ""), ""), color) : _colorize(String(template), color);
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Colorize text for terminal output.
|
|
80
|
+
*
|
|
81
|
+
* Can be used as a function: `colorize("text", "red")`
|
|
82
|
+
* Or via named helpers: `colorize.red("text")` / `colorize.red\`template\``
|
|
83
|
+
*/
|
|
84
|
+
var colorize = Object.assign(_colorize, Object.entries(colorMap).reduce((acc, [key]) => {
|
|
85
|
+
acc[key] = createColorize(key);
|
|
86
|
+
return acc;
|
|
87
|
+
}, {}));
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/utils.ts
|
|
90
|
+
/** Throws the error if non-null, no-ops otherwise. */
|
|
91
|
+
function handleErr(err) {
|
|
92
|
+
if (err) throw err;
|
|
93
|
+
}
|
|
94
|
+
/** Resolves a value that may be either a static value or a function that produces one. */
|
|
95
|
+
function resolve(resolver, arg) {
|
|
96
|
+
return typeof resolver === "function" ? resolver(arg) : resolver;
|
|
97
|
+
}
|
|
98
|
+
/** Wraps a static value in a resolver function. If already a function, returns as-is. */
|
|
99
|
+
function wrapNoopResolver(value) {
|
|
100
|
+
if (typeof value === "function") return value;
|
|
101
|
+
return (_) => value;
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/types.ts
|
|
105
|
+
/**
|
|
106
|
+
* The amount of information to log when generating scaffold.
|
|
107
|
+
* When not `none`, the selected level will be the lowest level included.
|
|
108
|
+
*
|
|
109
|
+
* For example, level `info` will include `info`, `warning` and `error`, but not `debug`; and `warning` will only
|
|
110
|
+
* show `warning` and `error`, but not `info` or `debug`.
|
|
111
|
+
*
|
|
112
|
+
* @default `info`
|
|
113
|
+
*
|
|
114
|
+
* @category Logging (const)
|
|
115
|
+
*/
|
|
116
|
+
var LogLevel = {
|
|
117
|
+
none: "none",
|
|
118
|
+
debug: "debug",
|
|
119
|
+
info: "info",
|
|
120
|
+
warning: "warning",
|
|
121
|
+
error: "error"
|
|
122
|
+
};
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/logger.ts
|
|
125
|
+
/** Priority ordering for log levels (higher = more severe). */
|
|
126
|
+
var LOG_PRIORITY = {
|
|
127
|
+
[LogLevel.none]: 0,
|
|
128
|
+
[LogLevel.debug]: 1,
|
|
129
|
+
[LogLevel.info]: 2,
|
|
130
|
+
[LogLevel.warning]: 3,
|
|
131
|
+
[LogLevel.error]: 4
|
|
132
|
+
};
|
|
133
|
+
/** Maps each log level to a terminal color. */
|
|
134
|
+
var LOG_LEVEL_COLOR = {
|
|
135
|
+
[LogLevel.none]: "reset",
|
|
136
|
+
[LogLevel.debug]: "dim",
|
|
137
|
+
[LogLevel.info]: "reset",
|
|
138
|
+
[LogLevel.warning]: "yellow",
|
|
139
|
+
[LogLevel.error]: "red"
|
|
140
|
+
};
|
|
141
|
+
/** Logs a message at the given level, respecting the configured log level filter. */
|
|
142
|
+
function log(config, level, ...obj) {
|
|
143
|
+
if (config.logLevel === LogLevel.none || LOG_PRIORITY[level] < LOG_PRIORITY[config.logLevel ?? LogLevel.info]) return;
|
|
144
|
+
const colorFn = colorize[LOG_LEVEL_COLOR[level]];
|
|
145
|
+
const key = level === LogLevel.error ? "error" : level === LogLevel.warning ? "warn" : "log";
|
|
146
|
+
const logFn = console[key];
|
|
147
|
+
logFn(...obj.map((i) => i instanceof Error ? colorFn(i, JSON.stringify(i, void 0, 1), i.stack) : typeof i === "object" ? util.default.inspect(i, {
|
|
148
|
+
depth: null,
|
|
149
|
+
colors: true
|
|
150
|
+
}) : colorFn(i)));
|
|
151
|
+
}
|
|
152
|
+
/** Logs the full scaffold configuration at debug level. */
|
|
153
|
+
function logInitStep(config) {
|
|
154
|
+
log(config, LogLevel.debug, "Full config:", {
|
|
155
|
+
name: config.name,
|
|
156
|
+
templates: config.templates,
|
|
157
|
+
output: config.output,
|
|
158
|
+
subdir: config.subdir,
|
|
159
|
+
data: config.data,
|
|
160
|
+
overwrite: config.overwrite,
|
|
161
|
+
subdirHelper: config.subdirHelper,
|
|
162
|
+
helpers: Object.keys(config.helpers ?? {}),
|
|
163
|
+
logLevel: config.logLevel,
|
|
164
|
+
dryRun: config.dryRun,
|
|
165
|
+
beforeWrite: config.beforeWrite
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Logs a tree of created files, grouped by directory.
|
|
170
|
+
*/
|
|
171
|
+
function logFileTree(config, files) {
|
|
172
|
+
if (files.length === 0) return;
|
|
173
|
+
const commonDir = files.reduce((prefix, file) => {
|
|
174
|
+
while (!file.startsWith(prefix)) prefix = node_path.default.dirname(prefix);
|
|
175
|
+
return prefix;
|
|
176
|
+
}, node_path.default.dirname(files[0]));
|
|
177
|
+
log(config, LogLevel.info, "");
|
|
178
|
+
log(config, LogLevel.info, colorize.bold(`📁 ${commonDir}`));
|
|
179
|
+
const relPaths = files.map((f) => node_path.default.relative(commonDir, f)).sort();
|
|
180
|
+
for (let i = 0; i < relPaths.length; i++) {
|
|
181
|
+
const prefix = i === relPaths.length - 1 ? "└── " : "├── ";
|
|
182
|
+
log(config, LogLevel.info, colorize.dim(prefix) + relPaths[i]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Logs a final summary line with file count and elapsed time.
|
|
187
|
+
*/
|
|
188
|
+
function logSummary(config, fileCount, elapsedMs, dryRun) {
|
|
189
|
+
const timeStr = elapsedMs < 1e3 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1e3).toFixed(2)}s`;
|
|
190
|
+
log(config, LogLevel.info, "");
|
|
191
|
+
if (dryRun) log(config, LogLevel.info, colorize.yellow(`🏜️ Dry run complete — ${fileCount} file(s) would be created (${timeStr})`));
|
|
192
|
+
else if (fileCount === 0) log(config, LogLevel.info, colorize.yellow(`⚠️ No files created (${timeStr})`));
|
|
193
|
+
else log(config, LogLevel.info, colorize.green(`✅ Created ${fileCount} file(s) in ${timeStr}`));
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/parser.ts
|
|
197
|
+
var dateFns = {
|
|
198
|
+
add: date_fns.add,
|
|
199
|
+
format: date_fns.format,
|
|
200
|
+
parseISO: date_fns.parseISO
|
|
201
|
+
};
|
|
202
|
+
var defaultHelpers = {
|
|
203
|
+
camelCase,
|
|
204
|
+
snakeCase,
|
|
205
|
+
startCase,
|
|
206
|
+
kebabCase,
|
|
207
|
+
hyphenCase: kebabCase,
|
|
208
|
+
pascalCase,
|
|
209
|
+
lowerCase: (text) => text.toLowerCase(),
|
|
210
|
+
upperCase: (text) => text.toUpperCase(),
|
|
211
|
+
now: nowHelper,
|
|
212
|
+
date: dateHelper
|
|
213
|
+
};
|
|
214
|
+
function _dateHelper(date, formatString, durationDifference, durationType) {
|
|
215
|
+
if (durationType && durationDifference !== void 0) return dateFns.format(dateFns.add(date, { [durationType]: durationDifference }), formatString);
|
|
216
|
+
return dateFns.format(date, formatString);
|
|
217
|
+
}
|
|
218
|
+
function nowHelper(formatString, durationDifference, durationType) {
|
|
219
|
+
return _dateHelper(/* @__PURE__ */ new Date(), formatString, durationDifference, durationType);
|
|
220
|
+
}
|
|
221
|
+
function dateHelper(date, formatString, durationDifference, durationType) {
|
|
222
|
+
return _dateHelper(dateFns.parseISO(date), formatString, durationDifference, durationType);
|
|
223
|
+
}
|
|
224
|
+
function toWordParts(string) {
|
|
225
|
+
return string.split(/[^a-zA-Z0-9]/).flatMap((segment) => segment.split(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/)).filter((s) => s.length > 0);
|
|
226
|
+
}
|
|
227
|
+
function camelCase(s) {
|
|
228
|
+
return toWordParts(s).reduce((acc, part, i) => {
|
|
229
|
+
if (i === 0) return part.toLowerCase();
|
|
230
|
+
return acc + part[0].toUpperCase() + part.slice(1).toLowerCase();
|
|
231
|
+
}, "");
|
|
232
|
+
}
|
|
233
|
+
function snakeCase(s) {
|
|
234
|
+
return toWordParts(s).join("_").toLowerCase();
|
|
235
|
+
}
|
|
236
|
+
function kebabCase(s) {
|
|
237
|
+
return toWordParts(s).join("-").toLowerCase();
|
|
238
|
+
}
|
|
239
|
+
function startCase(s) {
|
|
240
|
+
return toWordParts(s).map((part) => part[0].toUpperCase() + part.slice(1).toLowerCase()).join(" ");
|
|
241
|
+
}
|
|
242
|
+
function pascalCase(s) {
|
|
243
|
+
return startCase(s).replace(/\s+/g, "");
|
|
244
|
+
}
|
|
245
|
+
function registerHelpers(config) {
|
|
246
|
+
const _helpers = {
|
|
247
|
+
...defaultHelpers,
|
|
248
|
+
...config.helpers
|
|
249
|
+
};
|
|
250
|
+
for (const helperName in _helpers) {
|
|
251
|
+
log(config, LogLevel.debug, `Registering helper: ${helperName}`);
|
|
252
|
+
handlebars.default.registerHelper(helperName, _helpers[helperName]);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function handlebarsParse(config, templateBuffer, { asPath = false } = {}) {
|
|
256
|
+
const { data } = config;
|
|
257
|
+
try {
|
|
258
|
+
let str = templateBuffer.toString();
|
|
259
|
+
if (asPath) str = str.replace(/\\/g, "/");
|
|
260
|
+
let outputContents = handlebars.default.compile(str, { noEscape: true })(data);
|
|
261
|
+
if (asPath && node_path.default.sep !== "/") outputContents = outputContents.replace(/\//g, "\\");
|
|
262
|
+
return Buffer.from(outputContents);
|
|
263
|
+
} catch (e) {
|
|
264
|
+
log(config, LogLevel.debug, e);
|
|
265
|
+
log(config, LogLevel.debug, "Couldn't parse file with handlebars, returning original content");
|
|
266
|
+
return Buffer.from(templateBuffer);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/fs-utils.ts
|
|
271
|
+
var { stat, access, mkdir } = node_fs_promises.default;
|
|
272
|
+
/** Recursively creates a directory and its parents if they don't exist. */
|
|
273
|
+
async function createDirIfNotExists(dir, config) {
|
|
274
|
+
if (config.dryRun) {
|
|
275
|
+
log(config, LogLevel.info, `Dry Run. Not creating dir ${dir}`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const parentDir = node_path.default.dirname(dir);
|
|
279
|
+
if (!await pathExists(parentDir)) await createDirIfNotExists(parentDir, config);
|
|
280
|
+
if (!await pathExists(dir)) try {
|
|
281
|
+
log(config, LogLevel.debug, `Creating dir ${dir}`);
|
|
282
|
+
await mkdir(dir);
|
|
283
|
+
return;
|
|
284
|
+
} catch (e) {
|
|
285
|
+
if (e && e.code !== "EEXIST") throw e;
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/** Checks whether a file or directory exists at the given path. */
|
|
290
|
+
async function pathExists(filePath) {
|
|
291
|
+
try {
|
|
292
|
+
await access(filePath, node_constants.F_OK);
|
|
293
|
+
return true;
|
|
294
|
+
} catch (e) {
|
|
295
|
+
if (e && e.code === "ENOENT") return false;
|
|
296
|
+
throw e;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/** Returns true if the given path is a directory. */
|
|
300
|
+
async function isDir(dirPath) {
|
|
301
|
+
return (await stat(dirPath)).isDirectory();
|
|
302
|
+
}
|
|
303
|
+
/** Generates a unique temporary directory path for scaffold operations. @internal */
|
|
304
|
+
function getUniqueTmpPath() {
|
|
305
|
+
return node_path.default.resolve(node_os.default.tmpdir(), `scaffold-config-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/path-utils.ts
|
|
309
|
+
/** Strips glob wildcard characters from a template path. */
|
|
310
|
+
function removeGlob(template) {
|
|
311
|
+
return node_path.default.normalize(template.replace(/\*/g, ""));
|
|
312
|
+
}
|
|
313
|
+
/** Removes a leading path separator, making the path relative. */
|
|
314
|
+
function makeRelativePath(str) {
|
|
315
|
+
return str.startsWith(node_path.default.sep) ? str.slice(1) : str;
|
|
316
|
+
}
|
|
317
|
+
/** Computes a base path relative to the current working directory. */
|
|
318
|
+
function getBasePath(relPath) {
|
|
319
|
+
return node_path.default.resolve(process.cwd(), relPath).replace(process.cwd() + node_path.default.sep, "").replace(process.cwd(), "");
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/file.ts
|
|
323
|
+
var { readFile, writeFile } = node_fs_promises.default;
|
|
324
|
+
/**
|
|
325
|
+
* Resolves a config option that may be either a static value or a per-file function.
|
|
326
|
+
* For function values, the file path is parsed through Handlebars before being passed.
|
|
327
|
+
* @internal
|
|
328
|
+
*/
|
|
329
|
+
function getOptionValueForFile(config, filePath, fn, defaultValue) {
|
|
330
|
+
if (typeof fn !== "function") return defaultValue ?? fn;
|
|
331
|
+
return fn(filePath, node_path.default.dirname(handlebarsParse(config, filePath, { asPath: true }).toString()), node_path.default.basename(handlebarsParse(config, filePath, { asPath: true }).toString()));
|
|
332
|
+
}
|
|
333
|
+
/** Expands a list of glob patterns into a flat list of matching file paths. */
|
|
334
|
+
async function getFileList(config, templates) {
|
|
335
|
+
log(config, LogLevel.debug, `Getting file list for glob list: ${templates}`);
|
|
336
|
+
return (await (0, glob.glob)(templates, {
|
|
337
|
+
dot: true,
|
|
338
|
+
nodir: true
|
|
339
|
+
})).map(node_path.default.normalize);
|
|
340
|
+
}
|
|
341
|
+
/** Analyzes a template path to determine if it's a glob, directory, or single file. */
|
|
342
|
+
async function getTemplateGlobInfo(config, template) {
|
|
343
|
+
const _isGlob = (0, glob.hasMagic)(template);
|
|
344
|
+
log(config, LogLevel.debug, "before isDir", "isGlob:", _isGlob, template);
|
|
345
|
+
let resolvedTemplate = template;
|
|
346
|
+
let baseTemplatePath = _isGlob ? removeGlob(template) : template;
|
|
347
|
+
baseTemplatePath = node_path.default.normalize(baseTemplatePath);
|
|
348
|
+
const isDirOrGlob = _isGlob ? true : await isDir(template);
|
|
349
|
+
const shouldAddGlob = !_isGlob && isDirOrGlob;
|
|
350
|
+
log(config, LogLevel.debug, "after", {
|
|
351
|
+
isDirOrGlob,
|
|
352
|
+
shouldAddGlob
|
|
353
|
+
});
|
|
354
|
+
if (shouldAddGlob) resolvedTemplate = node_path.default.join(template, "**", "*");
|
|
355
|
+
return {
|
|
356
|
+
baseTemplatePath,
|
|
357
|
+
origTemplate: template,
|
|
358
|
+
isDirOrGlob,
|
|
359
|
+
isGlob: _isGlob,
|
|
360
|
+
template: resolvedTemplate
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/** Computes the full output path and metadata for a single template file. */
|
|
364
|
+
async function getTemplateFileInfo(config, { templatePath, basePath }) {
|
|
365
|
+
const inputPath = node_path.default.resolve(process.cwd(), templatePath);
|
|
366
|
+
const outputPathOpt = getOptionValueForFile(config, inputPath, config.output);
|
|
367
|
+
const outputDir = getOutputDir(config, outputPathOpt, basePath.replace(config.tmpDir, "./"));
|
|
368
|
+
const outputPath = handlebarsParse(config, node_path.default.join(outputDir, node_path.default.basename(inputPath)), { asPath: true }).toString();
|
|
369
|
+
return {
|
|
370
|
+
inputPath,
|
|
371
|
+
outputPathOpt,
|
|
372
|
+
outputDir,
|
|
373
|
+
outputPath,
|
|
374
|
+
exists: await pathExists(outputPath)
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Reads a template file, applies Handlebars parsing, runs the beforeWrite hook,
|
|
379
|
+
* and writes the result to the output path.
|
|
380
|
+
*/
|
|
381
|
+
async function copyFileTransformed(config, { exists, overwrite, outputPath, inputPath }) {
|
|
382
|
+
if (!exists || overwrite) {
|
|
383
|
+
if (exists && overwrite) log(config, LogLevel.debug, `Overwriting ${outputPath}`);
|
|
384
|
+
log(config, LogLevel.debug, `Processing file ${inputPath}`);
|
|
385
|
+
const templateBuffer = await readFile(inputPath);
|
|
386
|
+
const unprocessedOutputContents = handlebarsParse(config, templateBuffer);
|
|
387
|
+
const finalOutputContents = await config.beforeWrite?.(unprocessedOutputContents, templateBuffer, outputPath) ?? unprocessedOutputContents;
|
|
388
|
+
if (!config.dryRun) await writeFile(outputPath, finalOutputContents);
|
|
389
|
+
else {
|
|
390
|
+
log(config, LogLevel.debug, "Dry run — output would be:");
|
|
391
|
+
log(config, LogLevel.debug, finalOutputContents.toString());
|
|
392
|
+
}
|
|
393
|
+
} else if (exists) log(config, LogLevel.debug, `Skipped ${outputPath} (already exists)`);
|
|
394
|
+
}
|
|
395
|
+
/** Computes the output directory for a file, combining the output path, base path, and optional subdir. */
|
|
396
|
+
function getOutputDir(config, outputPathOpt, basePath) {
|
|
397
|
+
return node_path.default.resolve(process.cwd(), ...[
|
|
398
|
+
outputPathOpt,
|
|
399
|
+
basePath,
|
|
400
|
+
config.subdir ? config.subdirHelper ? handlebarsParse(config, `{{ ${config.subdirHelper} name }}`).toString() : config.name : void 0
|
|
401
|
+
].filter(Boolean));
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Processes a single template file: resolves output paths, creates directories,
|
|
405
|
+
* and writes the transformed output.
|
|
406
|
+
* Returns the output path if the file was written, or null if skipped.
|
|
407
|
+
*/
|
|
408
|
+
async function handleTemplateFile(config, { templatePath, basePath }) {
|
|
409
|
+
try {
|
|
410
|
+
const { inputPath, outputPathOpt, outputDir, outputPath, exists } = await getTemplateFileInfo(config, {
|
|
411
|
+
templatePath,
|
|
412
|
+
basePath
|
|
413
|
+
});
|
|
414
|
+
const overwrite = getOptionValueForFile(config, inputPath, config.overwrite ?? false);
|
|
415
|
+
log(config, LogLevel.debug, `\nParsing ${templatePath}`, `\nBase path: ${basePath}`, `\nFull input path: ${inputPath}`, `\nOutput Path Opt: ${outputPathOpt}`, `\nFull output dir: ${outputDir}`, `\nFull output path: ${outputPath}`, `\n`);
|
|
416
|
+
await createDirIfNotExists(node_path.default.dirname(outputPath), config);
|
|
417
|
+
const shouldWrite = (!exists || overwrite) && !config.dryRun;
|
|
418
|
+
log(config, LogLevel.debug, `Writing to ${outputPath}`);
|
|
419
|
+
await copyFileTransformed(config, {
|
|
420
|
+
exists,
|
|
421
|
+
overwrite,
|
|
422
|
+
outputPath,
|
|
423
|
+
inputPath
|
|
424
|
+
});
|
|
425
|
+
return shouldWrite ? outputPath : null;
|
|
426
|
+
} catch (e) {
|
|
427
|
+
handleErr(e);
|
|
428
|
+
throw e;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
//#endregion
|
|
432
|
+
//#region src/git.ts
|
|
433
|
+
async function getGitConfig(url, file, tmpPath, logConfig) {
|
|
434
|
+
const repoUrl = `${url.protocol}//${url.host}${url.pathname}`;
|
|
435
|
+
log(logConfig, LogLevel.debug, `Cloning git repo ${repoUrl}`);
|
|
436
|
+
return new Promise((res, reject) => {
|
|
437
|
+
log(logConfig, LogLevel.debug, `Cloning git repo to ${tmpPath}`);
|
|
438
|
+
const clone = (0, node_child_process.spawn)("git", [
|
|
439
|
+
"clone",
|
|
440
|
+
"--recurse-submodules",
|
|
441
|
+
"--depth",
|
|
442
|
+
"1",
|
|
443
|
+
repoUrl,
|
|
444
|
+
tmpPath
|
|
445
|
+
]);
|
|
446
|
+
clone.on("error", reject);
|
|
447
|
+
clone.on("close", async (code) => {
|
|
448
|
+
if (code === 0) {
|
|
449
|
+
res(await loadGitConfig({
|
|
450
|
+
logConfig,
|
|
451
|
+
url: repoUrl,
|
|
452
|
+
file,
|
|
453
|
+
tmpPath
|
|
454
|
+
}));
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
reject(/* @__PURE__ */ new Error(`Git clone failed with code ${code}`));
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
/** @internal */
|
|
462
|
+
async function loadGitConfig({ logConfig, url: repoUrl, file, tmpPath }) {
|
|
463
|
+
log(logConfig, LogLevel.debug, `Loading config from git repo: ${repoUrl}`);
|
|
464
|
+
const filename = file || await findConfigFile(tmpPath);
|
|
465
|
+
const absolutePath = node_path.default.resolve(tmpPath, filename);
|
|
466
|
+
log(logConfig, LogLevel.debug, `Resolving config file: ${absolutePath}`);
|
|
467
|
+
const loadedConfig = await resolve(async () => (await import(absolutePath)).default, logConfig);
|
|
468
|
+
log(logConfig, LogLevel.debug, `Loaded config from git`);
|
|
469
|
+
log(logConfig, LogLevel.debug, `Raw config:`, loadedConfig);
|
|
470
|
+
const fixedConfig = {};
|
|
471
|
+
for (const [k, v] of Object.entries(loadedConfig)) fixedConfig[k] = {
|
|
472
|
+
...v,
|
|
473
|
+
templates: v.templates.map((t) => node_path.default.resolve(tmpPath, t))
|
|
474
|
+
};
|
|
475
|
+
return wrapNoopResolver(fixedConfig);
|
|
476
|
+
}
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/before-write.ts
|
|
479
|
+
/**
|
|
480
|
+
* Wraps a CLI beforeWrite command string into a beforeWrite callback function.
|
|
481
|
+
* The command receives the processed content via a temp file and can return modified content via stdout.
|
|
482
|
+
* @internal
|
|
483
|
+
*/
|
|
484
|
+
function wrapBeforeWrite(config, beforeWrite) {
|
|
485
|
+
return async (content, rawContent, outputFile) => {
|
|
486
|
+
const tmpDir = node_path.default.join(getUniqueTmpPath(), node_path.default.basename(outputFile));
|
|
487
|
+
await createDirIfNotExists(node_path.default.dirname(tmpDir), config);
|
|
488
|
+
const ext = node_path.default.extname(outputFile);
|
|
489
|
+
const rawTmpPath = tmpDir.replace(ext, ".raw" + ext);
|
|
490
|
+
try {
|
|
491
|
+
log(config, LogLevel.debug, "Parsing beforeWrite command", beforeWrite);
|
|
492
|
+
const cmd = await prepareBeforeWriteCmd({
|
|
493
|
+
beforeWrite,
|
|
494
|
+
tmpDir,
|
|
495
|
+
content,
|
|
496
|
+
rawTmpPath,
|
|
497
|
+
rawContent
|
|
498
|
+
});
|
|
499
|
+
return await new Promise((resolve, reject) => {
|
|
500
|
+
log(config, LogLevel.debug, "Running parsed beforeWrite command:", cmd);
|
|
501
|
+
const proc = (0, node_child_process.exec)(cmd);
|
|
502
|
+
proc.stdout.on("data", (data) => {
|
|
503
|
+
if (data.trim()) resolve(data.toString());
|
|
504
|
+
else resolve(void 0);
|
|
505
|
+
});
|
|
506
|
+
proc.stderr.on("data", (data) => {
|
|
507
|
+
reject(data.toString());
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
} catch (e) {
|
|
511
|
+
log(config, LogLevel.debug, e);
|
|
512
|
+
log(config, LogLevel.warning, "Error running beforeWrite command, returning original content");
|
|
513
|
+
return;
|
|
514
|
+
} finally {
|
|
515
|
+
await node_fs_promises.default.rm(tmpDir, { force: true });
|
|
516
|
+
await node_fs_promises.default.rm(rawTmpPath, { force: true });
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
async function prepareBeforeWriteCmd({ beforeWrite, tmpDir, content, rawTmpPath, rawContent }) {
|
|
521
|
+
let cmd = "";
|
|
522
|
+
const pathReg = /\{\{\s*path\s*\}\}/gi;
|
|
523
|
+
const rawPathReg = /\{\{\s*rawpath\s*\}\}/gi;
|
|
524
|
+
if (pathReg.test(beforeWrite)) {
|
|
525
|
+
await node_fs_promises.default.writeFile(tmpDir, content);
|
|
526
|
+
cmd = beforeWrite.replaceAll(pathReg, tmpDir);
|
|
527
|
+
}
|
|
528
|
+
if (rawPathReg.test(beforeWrite)) {
|
|
529
|
+
await node_fs_promises.default.writeFile(rawTmpPath, rawContent);
|
|
530
|
+
cmd = beforeWrite.replaceAll(rawPathReg, rawTmpPath);
|
|
531
|
+
}
|
|
532
|
+
if (!cmd) {
|
|
533
|
+
await node_fs_promises.default.writeFile(tmpDir, content);
|
|
534
|
+
cmd = [beforeWrite, tmpDir].join(" ");
|
|
535
|
+
}
|
|
536
|
+
return cmd;
|
|
537
|
+
}
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region src/config.ts
|
|
540
|
+
/** Parses CLI append-data syntax (`key=value` or `key:=jsonValue`) into a data object. @internal */
|
|
541
|
+
function parseAppendData(value, options) {
|
|
542
|
+
const data = options.data ?? {};
|
|
543
|
+
const [key, val] = value.split(/:?=/);
|
|
544
|
+
if (value.includes(":=") && !val.includes(":=")) return {
|
|
545
|
+
...data,
|
|
546
|
+
[key]: JSON.parse(val)
|
|
547
|
+
};
|
|
548
|
+
return {
|
|
549
|
+
...data,
|
|
550
|
+
[key]: isWrappedWithQuotes(val) ? val.substring(1, val.length - 1) : val
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function isWrappedWithQuotes(string) {
|
|
554
|
+
return string.startsWith("\"") && string.endsWith("\"") || string.startsWith("'") && string.endsWith("'");
|
|
555
|
+
}
|
|
556
|
+
/** Loads and resolves a config file (local or remote). @internal */
|
|
557
|
+
async function getConfigFile(config) {
|
|
558
|
+
if (config.git && !config.git.includes("://")) {
|
|
559
|
+
log(config, LogLevel.debug, `Loading config from GitHub ${config.git}`);
|
|
560
|
+
config.git = githubPartToUrl(config.git);
|
|
561
|
+
}
|
|
562
|
+
const isGit = Boolean(config.git);
|
|
563
|
+
const configFilename = config.config;
|
|
564
|
+
const configPath = isGit ? config.git : configFilename;
|
|
565
|
+
log(config, LogLevel.debug, `Loading config from file ${configFilename}`);
|
|
566
|
+
let configImport = await resolve(await (isGit ? getRemoteConfig({
|
|
567
|
+
git: configPath,
|
|
568
|
+
config: configFilename,
|
|
569
|
+
logLevel: config.logLevel,
|
|
570
|
+
tmpDir: config.tmpDir
|
|
571
|
+
}) : getLocalConfig({
|
|
572
|
+
config: configFilename,
|
|
573
|
+
logLevel: config.logLevel
|
|
574
|
+
})), config);
|
|
575
|
+
if (typeof configImport.default === "function" || configImport.default instanceof Promise) {
|
|
576
|
+
log(config, LogLevel.debug, "Config is a function or promise, resolving...");
|
|
577
|
+
configImport = await resolve(configImport.default, config);
|
|
578
|
+
}
|
|
579
|
+
return configImport;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Parses a CLI config into a full ScaffoldConfig by merging CLI args, config file values,
|
|
583
|
+
* and append-data overrides. @internal
|
|
584
|
+
*/
|
|
585
|
+
async function parseConfigFile(config) {
|
|
586
|
+
let output = {
|
|
587
|
+
name: config.name,
|
|
588
|
+
templates: config.templates ?? [],
|
|
589
|
+
output: config.output,
|
|
590
|
+
logLevel: config.logLevel,
|
|
591
|
+
dryRun: config.dryRun,
|
|
592
|
+
data: config.data,
|
|
593
|
+
subdir: config.subdir,
|
|
594
|
+
overwrite: config.overwrite,
|
|
595
|
+
subdirHelper: config.subdirHelper,
|
|
596
|
+
beforeWrite: void 0,
|
|
597
|
+
tmpDir: config.tmpDir
|
|
598
|
+
};
|
|
599
|
+
if (config.quiet) config.logLevel = LogLevel.none;
|
|
600
|
+
if (Boolean(config.config || config.git)) {
|
|
601
|
+
const key = config.key ?? "default";
|
|
602
|
+
const configImport = await getConfigFile(config);
|
|
603
|
+
if (!configImport[key]) throw new Error(`Template "${key}" not found in ${config.config}`);
|
|
604
|
+
const imported = configImport[key];
|
|
605
|
+
log(config, LogLevel.debug, "Imported result", imported);
|
|
606
|
+
output = {
|
|
607
|
+
...output,
|
|
608
|
+
...imported,
|
|
609
|
+
beforeWrite: void 0,
|
|
610
|
+
templates: config.templates || imported.templates,
|
|
611
|
+
output: config.output || imported.output,
|
|
612
|
+
data: {
|
|
613
|
+
...imported.data,
|
|
614
|
+
...config.data
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
output.data = {
|
|
619
|
+
...output.data,
|
|
620
|
+
...config.appendData
|
|
621
|
+
};
|
|
622
|
+
const cmdBeforeWrite = config.beforeWrite ? wrapBeforeWrite(config, config.beforeWrite) : void 0;
|
|
623
|
+
output.beforeWrite = cmdBeforeWrite ?? output.beforeWrite;
|
|
624
|
+
if (config.afterScaffold) output.afterScaffold = config.afterScaffold;
|
|
625
|
+
if (!output.name) throw new Error("simple-scaffold: Missing required option: name");
|
|
626
|
+
log(output, LogLevel.debug, "Parsed config", output);
|
|
627
|
+
return output;
|
|
628
|
+
}
|
|
629
|
+
/** Converts a GitHub shorthand (user/repo) to a full HTTPS git URL. @internal */
|
|
630
|
+
function githubPartToUrl(part) {
|
|
631
|
+
const gitUrl = new URL(`https://github.com/${part}`);
|
|
632
|
+
if (!gitUrl.pathname.endsWith(".git")) gitUrl.pathname += ".git";
|
|
633
|
+
return gitUrl.toString();
|
|
634
|
+
}
|
|
635
|
+
/** Loads a scaffold config from a local file or directory. @internal */
|
|
636
|
+
async function getLocalConfig(config) {
|
|
637
|
+
const { config: configFile, ...logConfig } = config;
|
|
638
|
+
const absolutePath = node_path.default.resolve(process.cwd(), configFile);
|
|
639
|
+
if (await isDir(absolutePath)) {
|
|
640
|
+
log(logConfig, LogLevel.debug, `Resolving config file from directory ${absolutePath}`);
|
|
641
|
+
const file = await findConfigFile(absolutePath);
|
|
642
|
+
if (!await pathExists(file)) throw new Error(`Could not find config file in directory ${absolutePath}`);
|
|
643
|
+
log(logConfig, LogLevel.debug, `Loading config from: ${node_path.default.resolve(absolutePath, file)}`);
|
|
644
|
+
return wrapNoopResolver(import(node_path.default.resolve(absolutePath, file)));
|
|
645
|
+
}
|
|
646
|
+
log(logConfig, LogLevel.debug, `Loading config from: ${absolutePath}`);
|
|
647
|
+
return wrapNoopResolver(import(absolutePath));
|
|
648
|
+
}
|
|
649
|
+
/** Loads a scaffold config from a remote git repository. @internal */
|
|
650
|
+
async function getRemoteConfig(config) {
|
|
651
|
+
const { config: configFile, git, tmpDir, ...logConfig } = config;
|
|
652
|
+
log(logConfig, LogLevel.debug, `Loading config from remote ${git}, config file ${configFile || "<auto-detect>"}`);
|
|
653
|
+
const url = new URL(git);
|
|
654
|
+
const isHttp = url.protocol === "http:" || url.protocol === "https:";
|
|
655
|
+
if (!(url.protocol === "git:" || isHttp && url.pathname.endsWith(".git"))) throw new Error(`Unsupported protocol ${url.protocol}`);
|
|
656
|
+
return getGitConfig(url, configFile, tmpDir, logConfig);
|
|
657
|
+
}
|
|
658
|
+
/** Searches for a scaffold config file in the given directory, trying known filenames in order. @internal */
|
|
659
|
+
async function findConfigFile(root) {
|
|
660
|
+
const allowed = [
|
|
661
|
+
"mjs",
|
|
662
|
+
"cjs",
|
|
663
|
+
"js",
|
|
664
|
+
"json"
|
|
665
|
+
].reduce((acc, ext) => {
|
|
666
|
+
acc.push(`scaffold.config.${ext}`);
|
|
667
|
+
acc.push(`scaffold.${ext}`);
|
|
668
|
+
acc.push(`.scaffold.${ext}`);
|
|
669
|
+
return acc;
|
|
670
|
+
}, []);
|
|
671
|
+
for (const file of allowed) if (await pathExists(node_path.default.resolve(root, file))) return file;
|
|
672
|
+
throw new Error(`Could not find config file in git repo`);
|
|
673
|
+
}
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region src/prompts.ts
|
|
676
|
+
/** Prompts the user for a scaffold name. */
|
|
677
|
+
async function promptForName() {
|
|
678
|
+
return (0, _inquirer_input.default)({
|
|
679
|
+
message: colorize.cyan("Scaffold name:"),
|
|
680
|
+
required: true,
|
|
681
|
+
validate: (value) => {
|
|
682
|
+
if (!value.trim()) return "Name cannot be empty";
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
/** Prompts the user to select a template key from the available config keys. */
|
|
688
|
+
async function promptForTemplateKey(configMap) {
|
|
689
|
+
const keys = Object.keys(configMap);
|
|
690
|
+
if (keys.length === 0) throw new Error("No templates found in config file");
|
|
691
|
+
if (keys.length === 1) return keys[0];
|
|
692
|
+
return (0, _inquirer_select.default)({
|
|
693
|
+
message: colorize.cyan("Select a template:"),
|
|
694
|
+
choices: keys.map((key) => ({
|
|
695
|
+
name: key,
|
|
696
|
+
value: key
|
|
697
|
+
}))
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
/** Prompts the user for an output directory path. */
|
|
701
|
+
async function promptForOutput() {
|
|
702
|
+
return (0, _inquirer_input.default)({
|
|
703
|
+
message: colorize.cyan("Output directory:"),
|
|
704
|
+
required: true,
|
|
705
|
+
default: ".",
|
|
706
|
+
validate: (value) => {
|
|
707
|
+
if (!value.trim()) return "Output directory cannot be empty";
|
|
708
|
+
return true;
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
/** Prompts the user for template paths (comma-separated). */
|
|
713
|
+
async function promptForTemplates() {
|
|
714
|
+
return (await (0, _inquirer_input.default)({
|
|
715
|
+
message: colorize.cyan("Template paths (comma-separated):"),
|
|
716
|
+
required: true,
|
|
717
|
+
validate: (value) => {
|
|
718
|
+
if (!value.trim()) return "At least one template path is required";
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
})).split(",").map((t) => t.trim()).filter(Boolean);
|
|
722
|
+
}
|
|
723
|
+
/** Prompts for a single input based on its type. */
|
|
724
|
+
async function promptSingleInput(key, def) {
|
|
725
|
+
const type = def.type ?? "text";
|
|
726
|
+
const message = colorize.cyan(def.message ?? `${key}:`);
|
|
727
|
+
switch (type) {
|
|
728
|
+
case "text": return (0, _inquirer_input.default)({
|
|
729
|
+
message,
|
|
730
|
+
required: def.required,
|
|
731
|
+
default: def.default,
|
|
732
|
+
validate: def.required ? (value) => {
|
|
733
|
+
if (!value.trim()) return `${key} is required`;
|
|
734
|
+
return true;
|
|
735
|
+
} : void 0
|
|
736
|
+
});
|
|
737
|
+
case "select": {
|
|
738
|
+
const choices = (def.options ?? []).map((opt) => typeof opt === "string" ? {
|
|
739
|
+
name: opt,
|
|
740
|
+
value: opt
|
|
741
|
+
} : opt);
|
|
742
|
+
if (choices.length === 0) throw new Error(`Input "${key}" has type "select" but no options defined`);
|
|
743
|
+
return (0, _inquirer_select.default)({
|
|
744
|
+
message,
|
|
745
|
+
choices,
|
|
746
|
+
default: def.default
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
case "confirm": return (0, _inquirer_confirm.default)({
|
|
750
|
+
message,
|
|
751
|
+
default: def.default ?? false
|
|
752
|
+
});
|
|
753
|
+
case "number": return await (0, _inquirer_number.default)({
|
|
754
|
+
message,
|
|
755
|
+
required: def.required,
|
|
756
|
+
default: def.default
|
|
757
|
+
}) ?? def.default;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Prompts the user for any required scaffold inputs that are not already provided in data.
|
|
762
|
+
* Also applies default values for optional inputs that have one.
|
|
763
|
+
* Returns the merged data object.
|
|
764
|
+
*/
|
|
765
|
+
async function promptForInputs(inputs, existingData = {}) {
|
|
766
|
+
const data = { ...existingData };
|
|
767
|
+
for (const [key, def] of Object.entries(inputs)) {
|
|
768
|
+
if (key in data && data[key] !== void 0 && data[key] !== "") continue;
|
|
769
|
+
if (def.required || def.type === "select" || def.type === "confirm") data[key] = await promptSingleInput(key, def);
|
|
770
|
+
else if (def.default !== void 0 && !(key in data)) data[key] = def.default;
|
|
771
|
+
}
|
|
772
|
+
return data;
|
|
773
|
+
}
|
|
774
|
+
/** Returns true if the process is running in an interactive terminal. */
|
|
775
|
+
function isInteractive() {
|
|
776
|
+
return Boolean(process.stdin.isTTY);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Prompts for name and template key before the config file is parsed.
|
|
780
|
+
* These are needed by parseConfigFile to know which template to load.
|
|
781
|
+
*/
|
|
782
|
+
async function promptBeforeConfig(config, configMap) {
|
|
783
|
+
if (!isInteractive()) return config;
|
|
784
|
+
if (!config.name) config.name = await promptForName();
|
|
785
|
+
if (configMap && !config.key) {
|
|
786
|
+
if (Object.keys(configMap).length > 1) config.key = await promptForTemplateKey(configMap);
|
|
787
|
+
}
|
|
788
|
+
return config;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Prompts for any values still missing after the config file has been parsed.
|
|
792
|
+
* Only prompts in interactive mode.
|
|
793
|
+
*/
|
|
794
|
+
async function promptAfterConfig(config) {
|
|
795
|
+
if (!isInteractive()) return config;
|
|
796
|
+
if (!config.output || typeof config.output === "string" && !config.output) config.output = await promptForOutput();
|
|
797
|
+
if (!config.templates || config.templates.length === 0) config.templates = await promptForTemplates();
|
|
798
|
+
return config;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Prompts for any required inputs defined in the scaffold config and merges them into data.
|
|
802
|
+
* Only prompts in interactive mode; in non-interactive mode, only applies defaults.
|
|
803
|
+
*/
|
|
804
|
+
async function resolveInputs(config) {
|
|
805
|
+
if (!config.inputs) return config;
|
|
806
|
+
if (isInteractive()) config.data = await promptForInputs(config.inputs, config.data);
|
|
807
|
+
else {
|
|
808
|
+
const data = { ...config.data };
|
|
809
|
+
for (const [key, def] of Object.entries(config.inputs)) if (def.default !== void 0 && !(key in data)) data[key] = def.default;
|
|
810
|
+
config.data = data;
|
|
811
|
+
}
|
|
812
|
+
return config;
|
|
813
|
+
}
|
|
814
|
+
//#endregion
|
|
815
|
+
//#region src/ignore.ts
|
|
816
|
+
var IGNORE_FILENAME = ".scaffoldignore";
|
|
817
|
+
/**
|
|
818
|
+
* Reads a `.scaffoldignore` file from the given directory and returns
|
|
819
|
+
* the parsed patterns for filtering.
|
|
820
|
+
*
|
|
821
|
+
* Lines starting with `#` are comments. Empty lines are skipped.
|
|
822
|
+
*
|
|
823
|
+
* @param dir The directory to search for `.scaffoldignore`
|
|
824
|
+
* @returns Array of glob patterns to ignore
|
|
825
|
+
*/
|
|
826
|
+
async function loadIgnorePatterns(dir) {
|
|
827
|
+
const ignorePath = node_path.default.resolve(dir, IGNORE_FILENAME);
|
|
828
|
+
if (!await pathExists(ignorePath)) return [];
|
|
829
|
+
return parseIgnoreFile(await node_fs_promises.default.readFile(ignorePath, "utf-8"));
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Parses the contents of a `.scaffoldignore` file into glob patterns.
|
|
833
|
+
* @internal
|
|
834
|
+
*/
|
|
835
|
+
function parseIgnoreFile(content) {
|
|
836
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Filters a list of file paths, removing any that match the ignore patterns.
|
|
840
|
+
* Patterns are matched against the relative path from baseDir.
|
|
841
|
+
* Also always excludes `.scaffoldignore` itself.
|
|
842
|
+
*/
|
|
843
|
+
function filterIgnoredFiles(files, ignorePatterns, baseDir) {
|
|
844
|
+
return files.filter((file) => {
|
|
845
|
+
const basename = node_path.default.basename(file);
|
|
846
|
+
if (basename === IGNORE_FILENAME) return false;
|
|
847
|
+
const relPath = node_path.default.relative(baseDir, file);
|
|
848
|
+
for (const pattern of ignorePatterns) if ((0, minimatch.minimatch)(relPath, pattern, { dot: true }) || (0, minimatch.minimatch)(basename, pattern, { dot: true })) return false;
|
|
849
|
+
return true;
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
//#endregion
|
|
853
|
+
//#region src/validate.ts
|
|
854
|
+
/** Schema for a JavaScript function value. */
|
|
855
|
+
var functionSchema = zod_v4.z.any().refine((v) => typeof v === "function", { message: "Expected a function" });
|
|
856
|
+
/** Schema for a value that can be either a string or a function. */
|
|
857
|
+
var stringOrFunctionSchema = zod_v4.z.union([zod_v4.z.string(), functionSchema]);
|
|
858
|
+
/** Schema for a value that can be either a boolean or a function. */
|
|
859
|
+
var booleanOrFunctionSchema = zod_v4.z.union([zod_v4.z.boolean(), functionSchema]);
|
|
860
|
+
/** Schema for a select input option — either a plain string or a `{ name, value }` object. */
|
|
861
|
+
var selectOptionSchema = zod_v4.z.union([zod_v4.z.string(), zod_v4.z.object({
|
|
862
|
+
name: zod_v4.z.string(),
|
|
863
|
+
value: zod_v4.z.string()
|
|
864
|
+
})]);
|
|
865
|
+
/** Schema for the input type enum. */
|
|
866
|
+
var inputTypeSchema = zod_v4.z.enum([
|
|
867
|
+
"text",
|
|
868
|
+
"select",
|
|
869
|
+
"confirm",
|
|
870
|
+
"number"
|
|
871
|
+
]);
|
|
872
|
+
/** Schema for the log level enum. */
|
|
873
|
+
var logLevelSchema = zod_v4.z.enum([
|
|
874
|
+
"none",
|
|
875
|
+
"debug",
|
|
876
|
+
"info",
|
|
877
|
+
"warning",
|
|
878
|
+
"error"
|
|
879
|
+
]);
|
|
880
|
+
/** Zod schema for a single scaffold input definition. */
|
|
881
|
+
var scaffoldInputSchema = zod_v4.z.object({
|
|
882
|
+
type: inputTypeSchema.optional(),
|
|
883
|
+
message: zod_v4.z.string().optional(),
|
|
884
|
+
required: zod_v4.z.boolean().optional(),
|
|
885
|
+
default: zod_v4.z.union([
|
|
886
|
+
zod_v4.z.string(),
|
|
887
|
+
zod_v4.z.boolean(),
|
|
888
|
+
zod_v4.z.number()
|
|
889
|
+
]).optional(),
|
|
890
|
+
options: zod_v4.z.array(selectOptionSchema).optional()
|
|
891
|
+
});
|
|
892
|
+
function validateInputSemantics(key, input) {
|
|
893
|
+
const issues = [];
|
|
894
|
+
if (input.type === "select" && (!input.options || input.options.length === 0)) issues.push({
|
|
895
|
+
path: [
|
|
896
|
+
"inputs",
|
|
897
|
+
key,
|
|
898
|
+
"options"
|
|
899
|
+
],
|
|
900
|
+
message: "select input must have a non-empty options array"
|
|
901
|
+
});
|
|
902
|
+
if (input.type === "confirm" && input.default !== void 0 && typeof input.default !== "boolean") issues.push({
|
|
903
|
+
path: [
|
|
904
|
+
"inputs",
|
|
905
|
+
key,
|
|
906
|
+
"default"
|
|
907
|
+
],
|
|
908
|
+
message: "confirm input default must be a boolean"
|
|
909
|
+
});
|
|
910
|
+
if (input.type === "number" && input.default !== void 0 && typeof input.default !== "number") issues.push({
|
|
911
|
+
path: [
|
|
912
|
+
"inputs",
|
|
913
|
+
key,
|
|
914
|
+
"default"
|
|
915
|
+
],
|
|
916
|
+
message: "number input default must be a number"
|
|
917
|
+
});
|
|
918
|
+
return issues;
|
|
919
|
+
}
|
|
920
|
+
/** Zod schema for ScaffoldConfig. */
|
|
921
|
+
var scaffoldConfigSchema = zod_v4.z.object({
|
|
922
|
+
name: zod_v4.z.string().min(1, "name is required"),
|
|
923
|
+
templates: zod_v4.z.array(zod_v4.z.string()).min(1, "templates must contain at least one entry"),
|
|
924
|
+
output: stringOrFunctionSchema,
|
|
925
|
+
subdir: zod_v4.z.boolean().optional(),
|
|
926
|
+
data: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).optional(),
|
|
927
|
+
overwrite: booleanOrFunctionSchema.optional(),
|
|
928
|
+
logLevel: logLevelSchema.optional(),
|
|
929
|
+
dryRun: zod_v4.z.boolean().optional(),
|
|
930
|
+
helpers: zod_v4.z.record(zod_v4.z.string(), functionSchema).optional(),
|
|
931
|
+
subdirHelper: zod_v4.z.string().optional(),
|
|
932
|
+
inputs: zod_v4.z.record(zod_v4.z.string(), scaffoldInputSchema).optional(),
|
|
933
|
+
beforeWrite: functionSchema.optional(),
|
|
934
|
+
afterScaffold: stringOrFunctionSchema.optional(),
|
|
935
|
+
tmpDir: zod_v4.z.string().optional()
|
|
936
|
+
}).check((ctx) => {
|
|
937
|
+
const config = ctx.value;
|
|
938
|
+
if (config.subdirHelper && !config.subdir) ctx.issues.push({
|
|
939
|
+
code: "custom",
|
|
940
|
+
message: "subdirHelper is set but subdir is not enabled",
|
|
941
|
+
path: ["subdirHelper"],
|
|
942
|
+
input: config
|
|
943
|
+
});
|
|
944
|
+
if (config.inputs) for (const [key, val] of Object.entries(config.inputs)) for (const issue of validateInputSemantics(key, val)) ctx.issues.push({
|
|
945
|
+
code: "custom",
|
|
946
|
+
...issue,
|
|
947
|
+
input: config
|
|
948
|
+
});
|
|
949
|
+
});
|
|
950
|
+
/**
|
|
951
|
+
* Validates a scaffold config and returns a list of human-readable errors.
|
|
952
|
+
* Returns an empty array if the config is valid.
|
|
953
|
+
*/
|
|
954
|
+
function validateConfig(config) {
|
|
955
|
+
const result = scaffoldConfigSchema.safeParse(config);
|
|
956
|
+
if (result.success) return [];
|
|
957
|
+
return result.error.issues.map((issue) => {
|
|
958
|
+
return `${issue.path.length > 0 ? issue.path.join(".") : "(root)"}: ${issue.message}`;
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Validates template paths exist on disk.
|
|
963
|
+
* Only checks non-glob, non-negation paths.
|
|
964
|
+
*/
|
|
965
|
+
async function validateTemplatePaths(templates) {
|
|
966
|
+
const errors = [];
|
|
967
|
+
for (const tpl of templates) {
|
|
968
|
+
if (tpl.startsWith("!") || tpl.includes("*")) continue;
|
|
969
|
+
if (!await pathExists(tpl)) errors.push(`templates: path does not exist: ${tpl}`);
|
|
970
|
+
}
|
|
971
|
+
return errors;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Validates the config and throws a formatted error if any issues are found.
|
|
975
|
+
* Checks both schema validity and template path existence.
|
|
976
|
+
*/
|
|
977
|
+
async function assertConfigValid(config) {
|
|
978
|
+
const schemaErrors = validateConfig(config);
|
|
979
|
+
const pathErrors = config && typeof config === "object" && "templates" in config && Array.isArray(config.templates) ? await validateTemplatePaths(config.templates) : [];
|
|
980
|
+
const allErrors = [...schemaErrors, ...pathErrors];
|
|
981
|
+
if (allErrors.length > 0) {
|
|
982
|
+
const lines = allErrors.map((e) => ` - ${e}`);
|
|
983
|
+
throw new Error(`Invalid scaffold config:\n${lines.join("\n")}`);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
//#endregion
|
|
987
|
+
//#region src/scaffold.ts
|
|
988
|
+
/**
|
|
989
|
+
* @module
|
|
990
|
+
* Simple Scaffold
|
|
991
|
+
*
|
|
992
|
+
* See [readme](README.md)
|
|
993
|
+
*/
|
|
994
|
+
/**
|
|
995
|
+
* Create a scaffold using given `options`.
|
|
996
|
+
*
|
|
997
|
+
* #### Create files
|
|
998
|
+
* To create a file structure to output, use any directory and file structure you would like.
|
|
999
|
+
* Inside folder names, file names or file contents, you may place `{{ var }}` where `var` is either
|
|
1000
|
+
* `name` which is the scaffold name you provided or one of the keys you provided in the `data` option.
|
|
1001
|
+
*
|
|
1002
|
+
* The contents and names will be replaced with the transformed values so you can use your original structure as a
|
|
1003
|
+
* boilerplate for other projects, components, modules, or even single files.
|
|
1004
|
+
*
|
|
1005
|
+
* The files will maintain their structure, starting from the directory containing the template (or the template itself
|
|
1006
|
+
* if it is already a directory), and will output from that directory into the directory defined by `config.output`.
|
|
1007
|
+
*
|
|
1008
|
+
* #### Helpers
|
|
1009
|
+
* Helpers are functions you can use to transform your `{{ var }}` contents into other values without having to
|
|
1010
|
+
* pre-define the data and use a duplicated key.
|
|
1011
|
+
*
|
|
1012
|
+
* Any functions you provide in `helpers` option will also be available to you to make custom formatting as you see fit
|
|
1013
|
+
* (for example, formatting a date)
|
|
1014
|
+
*
|
|
1015
|
+
* For available default values, see {@link DefaultHelpers}.
|
|
1016
|
+
*
|
|
1017
|
+
* @param {ScaffoldConfig} config The main configuration object
|
|
1018
|
+
* @return {Promise<void>} A promise that resolves when the scaffold is complete
|
|
1019
|
+
*
|
|
1020
|
+
* @see {@link DefaultHelpers}
|
|
1021
|
+
* @see {@link CaseHelpers}
|
|
1022
|
+
* @see {@link DateHelpers}
|
|
1023
|
+
*
|
|
1024
|
+
* @category Main
|
|
1025
|
+
*/
|
|
1026
|
+
async function Scaffold(config) {
|
|
1027
|
+
config.output ??= process.cwd();
|
|
1028
|
+
await assertConfigValid(config);
|
|
1029
|
+
config = await resolveInputs(config);
|
|
1030
|
+
registerHelpers(config);
|
|
1031
|
+
const startTime = performance.now();
|
|
1032
|
+
const writtenFiles = [];
|
|
1033
|
+
try {
|
|
1034
|
+
config.data = {
|
|
1035
|
+
name: config.name,
|
|
1036
|
+
...config.data
|
|
1037
|
+
};
|
|
1038
|
+
logInitStep(config);
|
|
1039
|
+
log(config, LogLevel.info, `Scaffolding "${config.name}"...`);
|
|
1040
|
+
const excludes = config.templates.filter((t) => t.startsWith("!"));
|
|
1041
|
+
const includes = config.templates.filter((t) => !t.startsWith("!"));
|
|
1042
|
+
const templates = await resolveTemplateGlobs(config, includes);
|
|
1043
|
+
for (const tpl of templates) {
|
|
1044
|
+
const files = await processTemplateGlob(config, tpl, excludes);
|
|
1045
|
+
writtenFiles.push(...files);
|
|
1046
|
+
}
|
|
1047
|
+
} catch (e) {
|
|
1048
|
+
log(config, LogLevel.error, e);
|
|
1049
|
+
throw e;
|
|
1050
|
+
}
|
|
1051
|
+
const elapsed = performance.now() - startTime;
|
|
1052
|
+
logFileTree(config, writtenFiles);
|
|
1053
|
+
logSummary(config, writtenFiles.length, elapsed, config.dryRun);
|
|
1054
|
+
if (config.afterScaffold) await runAfterScaffoldHook(config, writtenFiles);
|
|
1055
|
+
}
|
|
1056
|
+
/** Resolves included template paths into GlobInfo objects. */
|
|
1057
|
+
async function resolveTemplateGlobs(config, includes) {
|
|
1058
|
+
const templates = [];
|
|
1059
|
+
for (const includedTemplate of includes) try {
|
|
1060
|
+
templates.push(await getTemplateGlobInfo(config, includedTemplate));
|
|
1061
|
+
} catch (e) {
|
|
1062
|
+
handleErr(e);
|
|
1063
|
+
}
|
|
1064
|
+
return templates;
|
|
1065
|
+
}
|
|
1066
|
+
/** Processes all files matching a single template glob pattern. Returns paths of written files. */
|
|
1067
|
+
async function processTemplateGlob(config, tpl, excludes) {
|
|
1068
|
+
const written = [];
|
|
1069
|
+
const ignorePatterns = await loadIgnorePatterns(tpl.baseTemplatePath);
|
|
1070
|
+
if (ignorePatterns.length > 0) log(config, LogLevel.debug, `Loaded .scaffoldignore patterns:`, ignorePatterns);
|
|
1071
|
+
const files = filterIgnoredFiles(await getFileList(config, [tpl.template, ...excludes]), ignorePatterns, tpl.baseTemplatePath);
|
|
1072
|
+
for (const file of files) {
|
|
1073
|
+
if (await isDir(file)) continue;
|
|
1074
|
+
log(config, LogLevel.debug, "Iterating files", {
|
|
1075
|
+
files,
|
|
1076
|
+
file
|
|
1077
|
+
});
|
|
1078
|
+
const relPath = makeRelativePath(node_path.default.dirname(removeGlob(file).replace(tpl.baseTemplatePath, "")));
|
|
1079
|
+
const basePath = getBasePath(relPath);
|
|
1080
|
+
log(config, LogLevel.debug, {
|
|
1081
|
+
originalTemplate: tpl.origTemplate,
|
|
1082
|
+
relativePath: relPath,
|
|
1083
|
+
parsedTemplate: tpl.template,
|
|
1084
|
+
inputFilePath: file,
|
|
1085
|
+
baseTemplatePath: tpl.baseTemplatePath,
|
|
1086
|
+
basePath,
|
|
1087
|
+
isDirOrGlob: tpl.isDirOrGlob,
|
|
1088
|
+
isGlob: tpl.isGlob
|
|
1089
|
+
});
|
|
1090
|
+
const outputPath = await handleTemplateFile(config, {
|
|
1091
|
+
templatePath: file,
|
|
1092
|
+
basePath
|
|
1093
|
+
});
|
|
1094
|
+
if (outputPath) written.push(outputPath);
|
|
1095
|
+
}
|
|
1096
|
+
return written;
|
|
1097
|
+
}
|
|
1098
|
+
/** Executes the afterScaffold hook — either a function or a shell command string. */
|
|
1099
|
+
async function runAfterScaffoldHook(config, files) {
|
|
1100
|
+
const hook = config.afterScaffold;
|
|
1101
|
+
if (typeof hook === "function") {
|
|
1102
|
+
log(config, LogLevel.debug, "Running afterScaffold function hook");
|
|
1103
|
+
await hook({
|
|
1104
|
+
config,
|
|
1105
|
+
files
|
|
1106
|
+
});
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const outputDir = typeof config.output === "string" ? config.output : process.cwd();
|
|
1110
|
+
const cwd = node_path.default.resolve(process.cwd(), outputDir);
|
|
1111
|
+
log(config, LogLevel.info, `Running afterScaffold command: ${hook}`);
|
|
1112
|
+
await new Promise((resolve, reject) => {
|
|
1113
|
+
const proc = (0, node_child_process.exec)(hook, { cwd });
|
|
1114
|
+
proc.stdout?.on("data", (data) => {
|
|
1115
|
+
log(config, LogLevel.info, data.toString().trimEnd());
|
|
1116
|
+
});
|
|
1117
|
+
proc.stderr?.on("data", (data) => {
|
|
1118
|
+
log(config, LogLevel.warning, data.toString().trimEnd());
|
|
1119
|
+
});
|
|
1120
|
+
proc.on("close", (code) => {
|
|
1121
|
+
if (code === 0) resolve();
|
|
1122
|
+
else reject(/* @__PURE__ */ new Error(`afterScaffold command exited with code ${code}`));
|
|
1123
|
+
});
|
|
1124
|
+
proc.on("error", reject);
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Create a scaffold based on a config file or URL.
|
|
1129
|
+
*
|
|
1130
|
+
* @param {string} pathOrUrl The path or URL to the config file
|
|
1131
|
+
* @param {Record<string, string>} config Information needed before loading the config
|
|
1132
|
+
* @param {Partial<Omit<ScaffoldConfig, 'name'>>} overrides Any overrides to the loaded config
|
|
1133
|
+
*
|
|
1134
|
+
* @see {@link Scaffold}
|
|
1135
|
+
* @category Main
|
|
1136
|
+
* @return {Promise<void>} A promise that resolves when the scaffold is complete
|
|
1137
|
+
*/
|
|
1138
|
+
Scaffold.fromConfig = async function(pathOrUrl, config, overrides) {
|
|
1139
|
+
const tmpPath = node_path.default.resolve(node_os.default.tmpdir(), `scaffold-config-${Date.now()}`);
|
|
1140
|
+
const _cmdConfig = {
|
|
1141
|
+
dryRun: false,
|
|
1142
|
+
output: process.cwd(),
|
|
1143
|
+
logLevel: LogLevel.info,
|
|
1144
|
+
overwrite: false,
|
|
1145
|
+
templates: [],
|
|
1146
|
+
subdir: false,
|
|
1147
|
+
quiet: false,
|
|
1148
|
+
config: pathOrUrl,
|
|
1149
|
+
version: false,
|
|
1150
|
+
tmpDir: tmpPath,
|
|
1151
|
+
...config
|
|
1152
|
+
};
|
|
1153
|
+
const _overrides = resolve(overrides, _cmdConfig);
|
|
1154
|
+
return Scaffold({
|
|
1155
|
+
...await parseConfigFile(_cmdConfig),
|
|
1156
|
+
..._overrides
|
|
1157
|
+
});
|
|
1158
|
+
};
|
|
1159
|
+
//#endregion
|
|
1160
|
+
Object.defineProperty(exports, "LogLevel", {
|
|
1161
|
+
enumerable: true,
|
|
1162
|
+
get: function() {
|
|
1163
|
+
return LogLevel;
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
Object.defineProperty(exports, "Scaffold", {
|
|
1167
|
+
enumerable: true,
|
|
1168
|
+
get: function() {
|
|
1169
|
+
return Scaffold;
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
Object.defineProperty(exports, "__toESM", {
|
|
1173
|
+
enumerable: true,
|
|
1174
|
+
get: function() {
|
|
1175
|
+
return __toESM;
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
Object.defineProperty(exports, "assertConfigValid", {
|
|
1179
|
+
enumerable: true,
|
|
1180
|
+
get: function() {
|
|
1181
|
+
return assertConfigValid;
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
Object.defineProperty(exports, "colorize", {
|
|
1185
|
+
enumerable: true,
|
|
1186
|
+
get: function() {
|
|
1187
|
+
return colorize;
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
Object.defineProperty(exports, "findConfigFile", {
|
|
1191
|
+
enumerable: true,
|
|
1192
|
+
get: function() {
|
|
1193
|
+
return findConfigFile;
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
Object.defineProperty(exports, "getConfigFile", {
|
|
1197
|
+
enumerable: true,
|
|
1198
|
+
get: function() {
|
|
1199
|
+
return getConfigFile;
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
Object.defineProperty(exports, "getUniqueTmpPath", {
|
|
1203
|
+
enumerable: true,
|
|
1204
|
+
get: function() {
|
|
1205
|
+
return getUniqueTmpPath;
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
Object.defineProperty(exports, "log", {
|
|
1209
|
+
enumerable: true,
|
|
1210
|
+
get: function() {
|
|
1211
|
+
return log;
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
Object.defineProperty(exports, "parseAppendData", {
|
|
1215
|
+
enumerable: true,
|
|
1216
|
+
get: function() {
|
|
1217
|
+
return parseAppendData;
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
Object.defineProperty(exports, "parseConfigFile", {
|
|
1221
|
+
enumerable: true,
|
|
1222
|
+
get: function() {
|
|
1223
|
+
return parseConfigFile;
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
Object.defineProperty(exports, "pathExists", {
|
|
1227
|
+
enumerable: true,
|
|
1228
|
+
get: function() {
|
|
1229
|
+
return pathExists;
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
Object.defineProperty(exports, "promptAfterConfig", {
|
|
1233
|
+
enumerable: true,
|
|
1234
|
+
get: function() {
|
|
1235
|
+
return promptAfterConfig;
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
Object.defineProperty(exports, "promptBeforeConfig", {
|
|
1239
|
+
enumerable: true,
|
|
1240
|
+
get: function() {
|
|
1241
|
+
return promptBeforeConfig;
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
Object.defineProperty(exports, "resolveInputs", {
|
|
1245
|
+
enumerable: true,
|
|
1246
|
+
get: function() {
|
|
1247
|
+
return resolveInputs;
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
Object.defineProperty(exports, "scaffoldConfigSchema", {
|
|
1251
|
+
enumerable: true,
|
|
1252
|
+
get: function() {
|
|
1253
|
+
return scaffoldConfigSchema;
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
Object.defineProperty(exports, "validateConfig", {
|
|
1257
|
+
enumerable: true,
|
|
1258
|
+
get: function() {
|
|
1259
|
+
return validateConfig;
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
//# sourceMappingURL=scaffold-DOzCgpZT.js.map
|