@tiveor/scg 0.1.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +303 -49
- package/dist/chunk-XKYTW4HW.js +589 -0
- package/dist/chunk-XKYTW4HW.js.map +1 -0
- package/dist/cli.cjs +779 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +173 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +991 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +653 -0
- package/dist/index.d.ts +653 -0
- package/dist/index.js +373 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -6
- package/templates/express-route/controller.ejs +35 -0
- package/templates/express-route/routes.ejs +10 -0
- package/templates/express-route/scaffold.json +13 -0
- package/templates/express-route/service.ejs +16 -0
- package/templates/github-action/scaffold.json +12 -0
- package/templates/github-action/workflow.ejs +33 -0
- package/templates/react-component/component.ejs +25 -0
- package/templates/react-component/index.ejs +2 -0
- package/templates/react-component/scaffold.json +15 -0
- package/templates/react-component/styles.ejs +4 -0
- package/templates/react-component/test.ejs +14 -0
- package/templates/vue-component/component.ejs +19 -0
- package/templates/vue-component/scaffold.json +12 -0
- package/templates/vue-component/test.ejs +16 -0
- package/.prettierignore +0 -1
- package/.prettierrc +0 -13
- package/.travis.yml +0 -9
- package/.vscode/settings.json +0 -5
- package/example/ejs/conditional.ejs +0 -9
- package/example/ejs/hello.ejs +0 -8
- package/example/handlebars/conditional.handlebars +0 -9
- package/example/handlebars/hello.handlebars +0 -6
- package/example/index.js +0 -180
- package/example/pug/conditional.pug +0 -4
- package/example/pug/hello.pug +0 -3
- package/index.js +0 -15
- package/src/command_helper.js +0 -42
- package/src/ejs_helper.js +0 -30
- package/src/file_helper.js +0 -142
- package/src/handlebars_helper.js +0 -32
- package/src/param_helper.js +0 -25
- package/src/pug_helper.js +0 -28
- package/src/string_helper.js +0 -12
- package/src/template_builder.js +0 -38
- package/src/template_handlers.js +0 -7
- package/test/test.js +0 -11
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,991 @@
|
|
|
1
|
+
"use strict";
|
|
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 __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
CommandHelper: () => CommandHelper,
|
|
34
|
+
FileHelper: () => FileHelper,
|
|
35
|
+
ParamHelper: () => ParamHelper,
|
|
36
|
+
Pipeline: () => Pipeline,
|
|
37
|
+
Scaffold: () => Scaffold,
|
|
38
|
+
StringHelper: () => StringHelper,
|
|
39
|
+
TEMPLATE_HANDLERS: () => TEMPLATE_HANDLERS,
|
|
40
|
+
TemplateBuilder: () => TemplateBuilder,
|
|
41
|
+
Watcher: () => Watcher
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(index_exports);
|
|
44
|
+
|
|
45
|
+
// src/string_helper.ts
|
|
46
|
+
var StringHelper = class _StringHelper {
|
|
47
|
+
/**
|
|
48
|
+
* Escapes all regex special characters in a string.
|
|
49
|
+
*
|
|
50
|
+
* @param string - The string to escape
|
|
51
|
+
* @returns The escaped string safe for use in `new RegExp()`
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* StringHelper.escapeRegex('$100.00 (test)');
|
|
56
|
+
* // => "\\$100\\.00 \\(test\\)"
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
static escapeRegex(string) {
|
|
60
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Replaces all occurrences of a token in a string. The token is treated
|
|
64
|
+
* as a literal string (regex special characters are escaped automatically).
|
|
65
|
+
*
|
|
66
|
+
* @param line - The source string to search in. Returns `''` if not a string.
|
|
67
|
+
* @param token - The token to search for. Returns `line` unchanged if not a string.
|
|
68
|
+
* @param value - The replacement value
|
|
69
|
+
* @returns The string with all occurrences replaced
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* StringHelper.replace('Price: $10.00', '$10.00', '$20.00');
|
|
74
|
+
* // => "Price: $20.00"
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
static replace(line, token, value) {
|
|
78
|
+
if (typeof line !== "string") return "";
|
|
79
|
+
if (typeof token !== "string") return line;
|
|
80
|
+
return line.replace(
|
|
81
|
+
new RegExp(_StringHelper.escapeRegex(token), "g"),
|
|
82
|
+
value
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Capitalizes the first character of a string.
|
|
87
|
+
*
|
|
88
|
+
* @param s - The string to capitalize. Returns `''` if not a string.
|
|
89
|
+
* @returns The string with the first character uppercased
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* StringHelper.capitalize('hello world');
|
|
94
|
+
* // => "Hello world"
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
static capitalize(s) {
|
|
98
|
+
if (typeof s !== "string") return "";
|
|
99
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/file_helper.ts
|
|
104
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
105
|
+
var import_path = __toESM(require("path"), 1);
|
|
106
|
+
var import_readline = __toESM(require("readline"), 1);
|
|
107
|
+
var FileHelper = class _FileHelper {
|
|
108
|
+
// --- Sync methods ---
|
|
109
|
+
/**
|
|
110
|
+
* Reads a file synchronously and returns its content as a UTF-8 string.
|
|
111
|
+
*
|
|
112
|
+
* @param fileName - Path to the file to read
|
|
113
|
+
* @returns The file content as a string
|
|
114
|
+
*/
|
|
115
|
+
static readFileToString(fileName) {
|
|
116
|
+
return import_fs.default.readFileSync(fileName, "utf8");
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Reads a JSON file synchronously and parses it into an object.
|
|
120
|
+
*
|
|
121
|
+
* @typeParam T - The expected type of the parsed object
|
|
122
|
+
* @param fileName - Path to the JSON file
|
|
123
|
+
* @returns The parsed object
|
|
124
|
+
*/
|
|
125
|
+
static convertJsonFileToObject(fileName) {
|
|
126
|
+
const rawString = _FileHelper.readFileToString(fileName);
|
|
127
|
+
return JSON.parse(rawString);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Creates a directory (and any parent directories) if it doesn't exist.
|
|
131
|
+
*
|
|
132
|
+
* @param folderName - Path to the directory to create
|
|
133
|
+
*/
|
|
134
|
+
static createFolder(folderName) {
|
|
135
|
+
const resolved = import_path.default.resolve(folderName);
|
|
136
|
+
if (!import_fs.default.existsSync(resolved)) {
|
|
137
|
+
import_fs.default.mkdirSync(resolved, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Removes a directory and all its contents recursively.
|
|
142
|
+
*
|
|
143
|
+
* @param folderName - Path to the directory to remove
|
|
144
|
+
*/
|
|
145
|
+
static removeFolder(folderName) {
|
|
146
|
+
const resolved = import_path.default.resolve(folderName);
|
|
147
|
+
if (import_fs.default.existsSync(resolved)) {
|
|
148
|
+
import_fs.default.rmSync(resolved, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Removes a file if it exists.
|
|
153
|
+
*
|
|
154
|
+
* @param filename - Path to the file to remove
|
|
155
|
+
*/
|
|
156
|
+
static removeFile(filename) {
|
|
157
|
+
const resolved = import_path.default.resolve(filename);
|
|
158
|
+
if (import_fs.default.existsSync(resolved)) {
|
|
159
|
+
import_fs.default.rmSync(resolved, { force: true });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// --- Async methods ---
|
|
163
|
+
/**
|
|
164
|
+
* Reads a file asynchronously and returns its content as a UTF-8 string.
|
|
165
|
+
*
|
|
166
|
+
* @param fileName - Path to the file to read
|
|
167
|
+
* @returns A promise resolving to the file content
|
|
168
|
+
*/
|
|
169
|
+
static async readFileAsync(fileName) {
|
|
170
|
+
return import_fs.default.promises.readFile(fileName, "utf8");
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Reads a JSON file asynchronously and parses it into an object.
|
|
174
|
+
*
|
|
175
|
+
* @typeParam T - The expected type of the parsed object
|
|
176
|
+
* @param fileName - Path to the JSON file
|
|
177
|
+
* @returns A promise resolving to the parsed object
|
|
178
|
+
*/
|
|
179
|
+
static async readJsonFileAsync(fileName) {
|
|
180
|
+
const raw = await import_fs.default.promises.readFile(fileName, "utf8");
|
|
181
|
+
return JSON.parse(raw);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Writes content to a file asynchronously, creating parent directories as needed.
|
|
185
|
+
*
|
|
186
|
+
* @param fileName - Path to the file to write
|
|
187
|
+
* @param content - The string content to write
|
|
188
|
+
*/
|
|
189
|
+
static async writeFileAsync(fileName, content) {
|
|
190
|
+
const dir = import_path.default.dirname(fileName);
|
|
191
|
+
await import_fs.default.promises.mkdir(dir, { recursive: true });
|
|
192
|
+
await import_fs.default.promises.writeFile(fileName, content, "utf8");
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Creates a directory asynchronously (with recursive parent creation).
|
|
196
|
+
*
|
|
197
|
+
* @param folderName - Path to the directory to create
|
|
198
|
+
*/
|
|
199
|
+
static async createFolderAsync(folderName) {
|
|
200
|
+
const resolved = import_path.default.resolve(folderName);
|
|
201
|
+
await import_fs.default.promises.mkdir(resolved, { recursive: true });
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Removes a directory and its contents recursively (async).
|
|
205
|
+
*
|
|
206
|
+
* @param folderName - Path to the directory to remove
|
|
207
|
+
*/
|
|
208
|
+
static async removeFolderAsync(folderName) {
|
|
209
|
+
const resolved = import_path.default.resolve(folderName);
|
|
210
|
+
await import_fs.default.promises.rm(resolved, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Removes a file asynchronously.
|
|
214
|
+
*
|
|
215
|
+
* @param filename - Path to the file to remove
|
|
216
|
+
*/
|
|
217
|
+
static async removeFileAsync(filename) {
|
|
218
|
+
const resolved = import_path.default.resolve(filename);
|
|
219
|
+
await import_fs.default.promises.rm(resolved, { force: true });
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Checks whether a file or directory exists asynchronously.
|
|
223
|
+
*
|
|
224
|
+
* @param filePath - Path to check
|
|
225
|
+
* @returns `true` if the path exists, `false` otherwise
|
|
226
|
+
*/
|
|
227
|
+
static async existsAsync(filePath) {
|
|
228
|
+
try {
|
|
229
|
+
await import_fs.default.promises.access(filePath);
|
|
230
|
+
return true;
|
|
231
|
+
} catch {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// --- Template processing methods ---
|
|
236
|
+
/**
|
|
237
|
+
* Performs a simple token replacement on a single line.
|
|
238
|
+
*
|
|
239
|
+
* @param line - The source line
|
|
240
|
+
* @param replacement - The replacement definition (uses `token` and `value`)
|
|
241
|
+
* @returns The line with the token replaced
|
|
242
|
+
*/
|
|
243
|
+
static simpleReplace(line, replacement) {
|
|
244
|
+
return StringHelper.replace(line, replacement.token, replacement.value);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Performs dynamic replacement using a sub-template and variables.
|
|
248
|
+
* Reads the sub-template line by line and applies all variable replacements.
|
|
249
|
+
*
|
|
250
|
+
* @param replacement - The replacement definition with `template` and `variables`
|
|
251
|
+
* @returns A promise resolving to the processed string
|
|
252
|
+
*/
|
|
253
|
+
static async dynamicReplace(replacement) {
|
|
254
|
+
let res = "";
|
|
255
|
+
for (const v in replacement.variables) {
|
|
256
|
+
const properties = replacement.variables[v];
|
|
257
|
+
await _FileHelper.readLineByLine(
|
|
258
|
+
replacement.template,
|
|
259
|
+
(line) => {
|
|
260
|
+
let newLine = line;
|
|
261
|
+
for (const x in properties) {
|
|
262
|
+
const property = properties[x];
|
|
263
|
+
if (line.indexOf(property.token) >= 0) {
|
|
264
|
+
if (property.value) {
|
|
265
|
+
newLine = _FileHelper.simpleReplace(newLine, property);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
res += newLine;
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return res;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Creates a new file from a template file, applying variable replacements line by line.
|
|
277
|
+
* The special token `@date` is automatically replaced with the current UTC date.
|
|
278
|
+
*
|
|
279
|
+
* @param options - The template path, output path, and variables to apply
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```typescript
|
|
283
|
+
* await FileHelper.createFileFromFile({
|
|
284
|
+
* template: 'templates/component.txt',
|
|
285
|
+
* newFile: 'output/Button.tsx',
|
|
286
|
+
* variables: [
|
|
287
|
+
* { token: '{{name}}', value: 'Button' },
|
|
288
|
+
* { token: '{{style}}', value: 'primary' }
|
|
289
|
+
* ]
|
|
290
|
+
* });
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
static async createFileFromFile({
|
|
294
|
+
template,
|
|
295
|
+
newFile,
|
|
296
|
+
variables
|
|
297
|
+
}) {
|
|
298
|
+
const writer = _FileHelper.writer(newFile);
|
|
299
|
+
await _FileHelper.readLineByLine(template, async (line) => {
|
|
300
|
+
let newLine = StringHelper.replace(
|
|
301
|
+
line,
|
|
302
|
+
"@date",
|
|
303
|
+
(/* @__PURE__ */ new Date()).toUTCString()
|
|
304
|
+
);
|
|
305
|
+
for (const v in variables) {
|
|
306
|
+
const replacement = variables[v];
|
|
307
|
+
if (newLine.indexOf(replacement.token) > 0) {
|
|
308
|
+
if (replacement.template) {
|
|
309
|
+
newLine = await _FileHelper.dynamicReplace(replacement);
|
|
310
|
+
break;
|
|
311
|
+
} else if (replacement.value) {
|
|
312
|
+
newLine = _FileHelper.simpleReplace(newLine, replacement);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
await _FileHelper.writeLineByLine(writer, newLine);
|
|
317
|
+
});
|
|
318
|
+
writer.end();
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Creates a string from a template file, applying variable replacements line by line.
|
|
322
|
+
* The special token `@date` is automatically replaced with the current UTC date.
|
|
323
|
+
*
|
|
324
|
+
* @param options - The template path and variables to apply
|
|
325
|
+
* @returns A promise resolving to the processed string
|
|
326
|
+
*/
|
|
327
|
+
static async createStringFromFile({
|
|
328
|
+
template,
|
|
329
|
+
variables
|
|
330
|
+
}) {
|
|
331
|
+
let res = "";
|
|
332
|
+
await _FileHelper.readLineByLine(template, async (line) => {
|
|
333
|
+
let newLine = StringHelper.replace(
|
|
334
|
+
line,
|
|
335
|
+
"@date",
|
|
336
|
+
(/* @__PURE__ */ new Date()).toUTCString()
|
|
337
|
+
);
|
|
338
|
+
for (const v in variables) {
|
|
339
|
+
const replacement = variables[v];
|
|
340
|
+
if (newLine.indexOf(replacement.token) > 0) {
|
|
341
|
+
if (replacement.template) {
|
|
342
|
+
newLine = await _FileHelper.dynamicReplace(replacement);
|
|
343
|
+
break;
|
|
344
|
+
} else if (replacement.value) {
|
|
345
|
+
newLine = _FileHelper.simpleReplace(newLine, replacement);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
res += newLine + "\n";
|
|
350
|
+
});
|
|
351
|
+
return res;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Reads a file line by line and invokes a callback for each line.
|
|
355
|
+
*
|
|
356
|
+
* @param fileName - Path to the file to read
|
|
357
|
+
* @param callback - Function called for each line
|
|
358
|
+
*/
|
|
359
|
+
static async readLineByLine(fileName, callback) {
|
|
360
|
+
const fileStream = import_fs.default.createReadStream(fileName);
|
|
361
|
+
const rl = import_readline.default.createInterface({
|
|
362
|
+
input: fileStream,
|
|
363
|
+
crlfDelay: Infinity
|
|
364
|
+
});
|
|
365
|
+
for await (const line of rl) {
|
|
366
|
+
await callback(line);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Creates a writable stream for appending to a file.
|
|
371
|
+
*
|
|
372
|
+
* @param filename - Path to the file
|
|
373
|
+
* @returns A writable stream in append mode
|
|
374
|
+
*/
|
|
375
|
+
static writer(filename) {
|
|
376
|
+
return import_fs.default.createWriteStream(filename, {
|
|
377
|
+
flags: "a"
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Writes a single line to a writable stream with CRLF line ending.
|
|
382
|
+
*
|
|
383
|
+
* @param writer - The writable stream
|
|
384
|
+
* @param newLine - The line content to write
|
|
385
|
+
*/
|
|
386
|
+
static async writeLineByLine(writer, newLine) {
|
|
387
|
+
writer.write(`${newLine}\r
|
|
388
|
+
`);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/command_helper.ts
|
|
393
|
+
var import_child_process = require("child_process");
|
|
394
|
+
var FORBIDDEN_PATTERNS = [
|
|
395
|
+
/[;&|`$]/,
|
|
396
|
+
// shell metacharacters
|
|
397
|
+
/\$\(/,
|
|
398
|
+
// command substitution
|
|
399
|
+
/>\s*\//,
|
|
400
|
+
// redirect to absolute path
|
|
401
|
+
/\.\.\//
|
|
402
|
+
// path traversal
|
|
403
|
+
];
|
|
404
|
+
var CommandHelper = class _CommandHelper {
|
|
405
|
+
/**
|
|
406
|
+
* Executes one or more shell commands in the given directory.
|
|
407
|
+
* Multiple commands are joined with `&&` (sequential execution).
|
|
408
|
+
*
|
|
409
|
+
* @param directory - Working directory for command execution
|
|
410
|
+
* @param command - One or more command strings to execute
|
|
411
|
+
* @returns A promise resolving to the command's stdout (or stderr if stdout is empty)
|
|
412
|
+
* @throws If the directory is empty, no commands are provided, or a command matches a forbidden pattern
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```typescript
|
|
416
|
+
* const output = await CommandHelper.run('.', 'echo hello');
|
|
417
|
+
* // => "hello\n"
|
|
418
|
+
*
|
|
419
|
+
* const result = await CommandHelper.run('.', 'git add .', 'git status');
|
|
420
|
+
* ```
|
|
421
|
+
*/
|
|
422
|
+
static run(directory, ...command) {
|
|
423
|
+
if (!directory) {
|
|
424
|
+
return Promise.reject(new Error("directory is required"));
|
|
425
|
+
}
|
|
426
|
+
if (command.length === 0) {
|
|
427
|
+
return Promise.reject(new Error("at least one command is required"));
|
|
428
|
+
}
|
|
429
|
+
for (const cmd of command) {
|
|
430
|
+
for (const pattern of FORBIDDEN_PATTERNS) {
|
|
431
|
+
if (pattern.test(cmd)) {
|
|
432
|
+
return Promise.reject(
|
|
433
|
+
new Error(
|
|
434
|
+
`potentially unsafe command rejected: "${cmd}" matches forbidden pattern ${pattern}`
|
|
435
|
+
)
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return new Promise((resolve, reject) => {
|
|
441
|
+
const cmd = command.join(" && ");
|
|
442
|
+
(0, import_child_process.exec)(
|
|
443
|
+
cmd,
|
|
444
|
+
{ cwd: directory, maxBuffer: 1024 * 1024 * 100 },
|
|
445
|
+
(err, stdout, stderr) => {
|
|
446
|
+
if (err) {
|
|
447
|
+
reject(err);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (stderr) {
|
|
451
|
+
resolve(stderr);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
resolve(stdout);
|
|
455
|
+
}
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Executes commands and returns the output with newlines stripped.
|
|
461
|
+
*
|
|
462
|
+
* @param folder - Working directory for command execution
|
|
463
|
+
* @param command - One or more command strings to execute
|
|
464
|
+
* @returns A promise resolving to the cleaned output (no newlines)
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* ```typescript
|
|
468
|
+
* const version = await CommandHelper.runClean('.', 'node --version');
|
|
469
|
+
* // => "v20.10.0"
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
static runClean(folder, ...command) {
|
|
473
|
+
return _CommandHelper.run(folder, ...command).then((result) => {
|
|
474
|
+
return result ? result.replace(/\r?\n|\r/g, "") : "";
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
// src/param_helper.ts
|
|
480
|
+
var ParamHelper = class {
|
|
481
|
+
/**
|
|
482
|
+
* Appends a custom parameter string to `process.argv`.
|
|
483
|
+
*
|
|
484
|
+
* @param customParam - The parameter to add (e.g., `'--env=production'`)
|
|
485
|
+
*/
|
|
486
|
+
static addCustomParam(customParam) {
|
|
487
|
+
process.argv.push(customParam);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Retrieves the command-line argument at the given index.
|
|
491
|
+
*
|
|
492
|
+
* @param index - Zero-based index into `process.argv`
|
|
493
|
+
* @returns The argument at the given index, or `''` if out of bounds
|
|
494
|
+
*/
|
|
495
|
+
static getCommandByIndex(index) {
|
|
496
|
+
return process.argv.length > index ? process.argv[index] : "";
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Parses all `--key=value` parameters from `process.argv` into an object.
|
|
500
|
+
* Surrounding quotes on values are stripped automatically.
|
|
501
|
+
*
|
|
502
|
+
* @returns An object mapping parameter names to their string values
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* ```typescript
|
|
506
|
+
* // node script.js --name=Alice --port=3000
|
|
507
|
+
* ParamHelper.getParams();
|
|
508
|
+
* // => { name: "Alice", port: "3000" }
|
|
509
|
+
* ```
|
|
510
|
+
*/
|
|
511
|
+
static getParams() {
|
|
512
|
+
const paramsObj = {};
|
|
513
|
+
process.argv.slice(2).forEach((element) => {
|
|
514
|
+
const matches = element.match("--([a-zA-Z0-9]+)=(.*)");
|
|
515
|
+
if (matches) {
|
|
516
|
+
paramsObj[matches[1]] = matches[2].replace(/^['"]/, "").replace(/['"]$/, "");
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
return paramsObj;
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// src/handlebars_helper.ts
|
|
524
|
+
var import_handlebars = __toESM(require("handlebars"), 1);
|
|
525
|
+
var HandlebarsHelper = class {
|
|
526
|
+
static render(source, data, options) {
|
|
527
|
+
return new Promise((resolve, reject) => {
|
|
528
|
+
try {
|
|
529
|
+
const template = import_handlebars.default.compile(source, options);
|
|
530
|
+
const res = template(data);
|
|
531
|
+
resolve(res);
|
|
532
|
+
} catch (error) {
|
|
533
|
+
reject(error);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
static renderFile(fileName, data, options) {
|
|
538
|
+
return new Promise((resolve, reject) => {
|
|
539
|
+
try {
|
|
540
|
+
const source = FileHelper.readFileToString(fileName);
|
|
541
|
+
const template = import_handlebars.default.compile(source, options);
|
|
542
|
+
const res = template(data);
|
|
543
|
+
resolve(res);
|
|
544
|
+
} catch (error) {
|
|
545
|
+
reject(error);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
// src/ejs_helper.ts
|
|
552
|
+
var import_ejs = __toESM(require("ejs"), 1);
|
|
553
|
+
var EjsHelper = class {
|
|
554
|
+
static render(source, data, options) {
|
|
555
|
+
return new Promise((resolve, reject) => {
|
|
556
|
+
try {
|
|
557
|
+
const template = import_ejs.default.render(source, data, options);
|
|
558
|
+
resolve(template);
|
|
559
|
+
} catch (error) {
|
|
560
|
+
reject(error);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
static renderFile(fileName, data, options) {
|
|
565
|
+
return new Promise((resolve, reject) => {
|
|
566
|
+
import_ejs.default.renderFile(fileName, data, options ?? {}, (err, str) => {
|
|
567
|
+
if (err) {
|
|
568
|
+
reject(err);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
resolve(str);
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// src/pug_helper.ts
|
|
578
|
+
var import_pug = __toESM(require("pug"), 1);
|
|
579
|
+
var PugHelper = class {
|
|
580
|
+
static render(source, data, options) {
|
|
581
|
+
return new Promise((resolve, reject) => {
|
|
582
|
+
try {
|
|
583
|
+
const template = import_pug.default.compile(source, options);
|
|
584
|
+
resolve(template(data));
|
|
585
|
+
} catch (error) {
|
|
586
|
+
reject(error);
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
static renderFile(fileName, data, options) {
|
|
591
|
+
return new Promise((resolve, reject) => {
|
|
592
|
+
try {
|
|
593
|
+
const template = import_pug.default.compileFile(fileName, options);
|
|
594
|
+
resolve(template(data));
|
|
595
|
+
} catch (error) {
|
|
596
|
+
reject(error);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
// src/template_handlers.ts
|
|
603
|
+
var TEMPLATE_HANDLERS = {
|
|
604
|
+
HANDLEBARS: "HANDLEBARS",
|
|
605
|
+
EJS: "EJS",
|
|
606
|
+
PUG: "PUG"
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// src/template_builder.ts
|
|
610
|
+
var builtInEngines = {
|
|
611
|
+
[TEMPLATE_HANDLERS.HANDLEBARS]: HandlebarsHelper,
|
|
612
|
+
[TEMPLATE_HANDLERS.EJS]: EjsHelper,
|
|
613
|
+
[TEMPLATE_HANDLERS.PUG]: PugHelper
|
|
614
|
+
};
|
|
615
|
+
var customEngines = {};
|
|
616
|
+
var TemplateBuilder = class {
|
|
617
|
+
templateHandler;
|
|
618
|
+
/**
|
|
619
|
+
* Creates a new TemplateBuilder for the specified engine.
|
|
620
|
+
*
|
|
621
|
+
* @param templateHandler - The engine name (e.g., `'EJS'`, `'HANDLEBARS'`, `'PUG'`, or a custom name)
|
|
622
|
+
*/
|
|
623
|
+
constructor(templateHandler) {
|
|
624
|
+
this.templateHandler = templateHandler;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Registers a custom template engine that can be used with `new TemplateBuilder(name)`.
|
|
628
|
+
*
|
|
629
|
+
* @param name - A unique name for the engine
|
|
630
|
+
* @param engine - An object implementing the {@link TemplateEngine} interface
|
|
631
|
+
* @throws If `name` is empty or `engine` doesn't implement `render()` and `renderFile()`
|
|
632
|
+
*
|
|
633
|
+
* @example
|
|
634
|
+
* ```typescript
|
|
635
|
+
* TemplateBuilder.registerEngine('nunjucks', {
|
|
636
|
+
* render: async (source, data) => nunjucks.renderString(source, data),
|
|
637
|
+
* renderFile: async (file, data) => nunjucks.render(file, data),
|
|
638
|
+
* });
|
|
639
|
+
* ```
|
|
640
|
+
*/
|
|
641
|
+
static registerEngine(name, engine) {
|
|
642
|
+
if (!name || typeof name !== "string") {
|
|
643
|
+
throw new Error("engine name must be a non-empty string");
|
|
644
|
+
}
|
|
645
|
+
if (!engine || typeof engine.render !== "function" || typeof engine.renderFile !== "function") {
|
|
646
|
+
throw new Error("engine must implement render() and renderFile() methods");
|
|
647
|
+
}
|
|
648
|
+
customEngines[name] = engine;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Retrieves a registered engine by name (custom engines take precedence over built-in).
|
|
652
|
+
*
|
|
653
|
+
* @param name - The engine name to look up
|
|
654
|
+
* @returns The engine implementation, or `undefined` if not found
|
|
655
|
+
*/
|
|
656
|
+
static getEngine(name) {
|
|
657
|
+
return customEngines[name] ?? builtInEngines[name];
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Returns an array of all registered engine names (built-in + custom).
|
|
661
|
+
*
|
|
662
|
+
* @returns Array of engine name strings
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```typescript
|
|
666
|
+
* TemplateBuilder.getRegisteredEngines();
|
|
667
|
+
* // => ['HANDLEBARS', 'EJS', 'PUG']
|
|
668
|
+
* ```
|
|
669
|
+
*/
|
|
670
|
+
static getRegisteredEngines() {
|
|
671
|
+
return [
|
|
672
|
+
...Object.keys(builtInEngines),
|
|
673
|
+
...Object.keys(customEngines)
|
|
674
|
+
];
|
|
675
|
+
}
|
|
676
|
+
resolveEngine() {
|
|
677
|
+
const engine = customEngines[this.templateHandler] ?? builtInEngines[this.templateHandler] ?? builtInEngines[TEMPLATE_HANDLERS.HANDLEBARS];
|
|
678
|
+
return engine;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Renders a template from a source string.
|
|
682
|
+
*
|
|
683
|
+
* @param source - The template source string
|
|
684
|
+
* @param data - Data object to pass to the template
|
|
685
|
+
* @param options - Optional engine-specific options
|
|
686
|
+
* @returns A promise resolving to the rendered string
|
|
687
|
+
*/
|
|
688
|
+
render(source, data, options) {
|
|
689
|
+
return this.resolveEngine().render(source, data, options);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Renders a template from a file path.
|
|
693
|
+
*
|
|
694
|
+
* @param fileName - Path to the template file
|
|
695
|
+
* @param data - Data object to pass to the template
|
|
696
|
+
* @param options - Optional engine-specific options
|
|
697
|
+
* @returns A promise resolving to the rendered string
|
|
698
|
+
*/
|
|
699
|
+
renderFile(fileName, data, options) {
|
|
700
|
+
return this.resolveEngine().renderFile(fileName, data, options);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// src/pipeline.ts
|
|
705
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
706
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
707
|
+
var Pipeline = class {
|
|
708
|
+
content = null;
|
|
709
|
+
contentPromise = null;
|
|
710
|
+
transforms = [];
|
|
711
|
+
/**
|
|
712
|
+
* Sets the pipeline content from a raw string.
|
|
713
|
+
*
|
|
714
|
+
* @param source - The string content
|
|
715
|
+
* @returns This pipeline instance for chaining
|
|
716
|
+
*/
|
|
717
|
+
fromString(source) {
|
|
718
|
+
this.content = source;
|
|
719
|
+
this.contentPromise = null;
|
|
720
|
+
return this;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Sets the pipeline content by reading a file synchronously.
|
|
724
|
+
*
|
|
725
|
+
* @param filePath - Path to the file to read
|
|
726
|
+
* @returns This pipeline instance for chaining
|
|
727
|
+
*/
|
|
728
|
+
fromFile(filePath) {
|
|
729
|
+
this.content = import_fs2.default.readFileSync(filePath, "utf8");
|
|
730
|
+
this.contentPromise = null;
|
|
731
|
+
return this;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Sets the pipeline content by rendering a template file.
|
|
735
|
+
* If no engine is specified, it is auto-detected from the file extension.
|
|
736
|
+
*
|
|
737
|
+
* @param templatePath - Path to the template file
|
|
738
|
+
* @param data - Data to pass to the template
|
|
739
|
+
* @param engine - Template engine name (auto-detected if omitted)
|
|
740
|
+
* @param options - Optional engine-specific options
|
|
741
|
+
* @returns This pipeline instance for chaining
|
|
742
|
+
*/
|
|
743
|
+
fromTemplate(templatePath, data, engine, options) {
|
|
744
|
+
const ext = engine ?? extToEngine(templatePath);
|
|
745
|
+
const builder = new TemplateBuilder(ext);
|
|
746
|
+
this.contentPromise = builder.renderFile(templatePath, data, options);
|
|
747
|
+
this.content = null;
|
|
748
|
+
return this;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Sets the pipeline content by rendering a template from a source string.
|
|
752
|
+
*
|
|
753
|
+
* @param source - The template source string
|
|
754
|
+
* @param data - Data to pass to the template
|
|
755
|
+
* @param engine - The template engine name
|
|
756
|
+
* @param options - Optional engine-specific options
|
|
757
|
+
* @returns This pipeline instance for chaining
|
|
758
|
+
*/
|
|
759
|
+
fromTemplateString(source, data, engine, options) {
|
|
760
|
+
const builder = new TemplateBuilder(engine);
|
|
761
|
+
this.contentPromise = builder.render(source, data, options);
|
|
762
|
+
this.content = null;
|
|
763
|
+
return this;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Adds a transformation function to the pipeline.
|
|
767
|
+
* Transforms are executed sequentially in the order they are added.
|
|
768
|
+
*
|
|
769
|
+
* @param fn - A sync or async function that receives and returns a string
|
|
770
|
+
* @returns This pipeline instance for chaining
|
|
771
|
+
*/
|
|
772
|
+
transform(fn) {
|
|
773
|
+
this.transforms.push(fn);
|
|
774
|
+
return this;
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Executes the pipeline: resolves the content and applies all transforms.
|
|
778
|
+
*
|
|
779
|
+
* @returns A promise resolving to the final transformed string
|
|
780
|
+
* @throws If no content source has been set
|
|
781
|
+
*/
|
|
782
|
+
async execute() {
|
|
783
|
+
let result;
|
|
784
|
+
if (this.contentPromise) {
|
|
785
|
+
result = await this.contentPromise;
|
|
786
|
+
} else if (this.content !== null) {
|
|
787
|
+
result = this.content;
|
|
788
|
+
} else {
|
|
789
|
+
throw new Error(
|
|
790
|
+
"Pipeline has no content. Call fromString(), fromFile(), or fromTemplate() first."
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
for (const fn of this.transforms) {
|
|
794
|
+
result = await fn(result);
|
|
795
|
+
}
|
|
796
|
+
return result;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Executes the pipeline and writes the result to a file.
|
|
800
|
+
* Creates parent directories automatically if they don't exist.
|
|
801
|
+
*
|
|
802
|
+
* @param filePath - Path to the output file
|
|
803
|
+
* @returns A promise resolving to the written content
|
|
804
|
+
*/
|
|
805
|
+
async writeTo(filePath) {
|
|
806
|
+
const result = await this.execute();
|
|
807
|
+
const dir = import_path2.default.dirname(filePath);
|
|
808
|
+
if (!import_fs2.default.existsSync(dir)) {
|
|
809
|
+
import_fs2.default.mkdirSync(dir, { recursive: true });
|
|
810
|
+
}
|
|
811
|
+
import_fs2.default.writeFileSync(filePath, result, "utf8");
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
function extToEngine(filePath) {
|
|
816
|
+
const ext = import_path2.default.extname(filePath).toLowerCase();
|
|
817
|
+
switch (ext) {
|
|
818
|
+
case ".ejs":
|
|
819
|
+
return "EJS";
|
|
820
|
+
case ".hbs":
|
|
821
|
+
case ".handlebars":
|
|
822
|
+
return "HANDLEBARS";
|
|
823
|
+
case ".pug":
|
|
824
|
+
case ".jade":
|
|
825
|
+
return "PUG";
|
|
826
|
+
default:
|
|
827
|
+
return "HANDLEBARS";
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/scaffold.ts
|
|
832
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
833
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
834
|
+
var Scaffold = class {
|
|
835
|
+
/**
|
|
836
|
+
* Generates files from a scaffold manifest.
|
|
837
|
+
*
|
|
838
|
+
* @param options - The scaffold configuration
|
|
839
|
+
* @returns A promise resolving to a {@link ScaffoldResult} with the list of created files
|
|
840
|
+
*/
|
|
841
|
+
static async from(options) {
|
|
842
|
+
const {
|
|
843
|
+
engine,
|
|
844
|
+
templateDir,
|
|
845
|
+
outputDir,
|
|
846
|
+
variables,
|
|
847
|
+
structure,
|
|
848
|
+
dryRun = false
|
|
849
|
+
} = options;
|
|
850
|
+
const builder = new TemplateBuilder(engine);
|
|
851
|
+
const resolvedOutputDir = resolveVariables(outputDir, variables);
|
|
852
|
+
const createdFiles = [];
|
|
853
|
+
for (const file of structure) {
|
|
854
|
+
const templatePath = import_path3.default.join(templateDir, file.template);
|
|
855
|
+
const outputFileName = resolveVariables(file.output, variables);
|
|
856
|
+
const outputPath = import_path3.default.join(resolvedOutputDir, outputFileName);
|
|
857
|
+
if (dryRun) {
|
|
858
|
+
createdFiles.push(outputPath);
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
const dir = import_path3.default.dirname(outputPath);
|
|
862
|
+
if (!import_fs3.default.existsSync(dir)) {
|
|
863
|
+
import_fs3.default.mkdirSync(dir, { recursive: true });
|
|
864
|
+
}
|
|
865
|
+
const content = await builder.renderFile(
|
|
866
|
+
templatePath,
|
|
867
|
+
variables,
|
|
868
|
+
{}
|
|
869
|
+
);
|
|
870
|
+
import_fs3.default.writeFileSync(outputPath, content, "utf8");
|
|
871
|
+
createdFiles.push(outputPath);
|
|
872
|
+
}
|
|
873
|
+
return { files: createdFiles, dryRun };
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
function resolveVariables(template, variables) {
|
|
877
|
+
let result = template;
|
|
878
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
879
|
+
result = StringHelper.replace(result, `{{${key}}}`, value);
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// src/watcher.ts
|
|
885
|
+
var import_fs4 = __toESM(require("fs"), 1);
|
|
886
|
+
var import_path4 = __toESM(require("path"), 1);
|
|
887
|
+
var Watcher = class {
|
|
888
|
+
options;
|
|
889
|
+
abortController = null;
|
|
890
|
+
/**
|
|
891
|
+
* Creates a new Watcher instance.
|
|
892
|
+
*
|
|
893
|
+
* @param options - Watcher configuration
|
|
894
|
+
*/
|
|
895
|
+
constructor(options) {
|
|
896
|
+
this.options = {
|
|
897
|
+
extensions: [".ejs", ".hbs", ".handlebars", ".pug"],
|
|
898
|
+
...options
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Starts watching the template directory for changes.
|
|
903
|
+
* Does nothing if the watcher is already running.
|
|
904
|
+
*/
|
|
905
|
+
start() {
|
|
906
|
+
if (this.abortController) {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
this.abortController = new AbortController();
|
|
910
|
+
const { signal } = this.abortController;
|
|
911
|
+
if (!import_fs4.default.existsSync(this.options.outputDir)) {
|
|
912
|
+
import_fs4.default.mkdirSync(this.options.outputDir, { recursive: true });
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
const watcher = import_fs4.default.watch(this.options.templateDir, {
|
|
916
|
+
recursive: true,
|
|
917
|
+
signal
|
|
918
|
+
});
|
|
919
|
+
watcher.on("change", (_eventType, filename) => {
|
|
920
|
+
if (!filename) return;
|
|
921
|
+
const filePath = import_path4.default.join(
|
|
922
|
+
this.options.templateDir,
|
|
923
|
+
filename.toString()
|
|
924
|
+
);
|
|
925
|
+
this.handleChange(filePath);
|
|
926
|
+
});
|
|
927
|
+
watcher.on("error", (err) => {
|
|
928
|
+
if (err.code !== "ABORT_ERR") {
|
|
929
|
+
this.options.onError?.(err, "");
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
} catch {
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Stops watching for changes and cleans up resources.
|
|
937
|
+
*/
|
|
938
|
+
stop() {
|
|
939
|
+
if (this.abortController) {
|
|
940
|
+
this.abortController.abort();
|
|
941
|
+
this.abortController = null;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Whether the watcher is currently active.
|
|
946
|
+
*/
|
|
947
|
+
get isRunning() {
|
|
948
|
+
return this.abortController !== null;
|
|
949
|
+
}
|
|
950
|
+
async handleChange(filePath) {
|
|
951
|
+
const ext = import_path4.default.extname(filePath).toLowerCase();
|
|
952
|
+
if (!this.options.extensions.includes(ext)) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (!import_fs4.default.existsSync(filePath)) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
try {
|
|
959
|
+
const builder = new TemplateBuilder(this.options.engine);
|
|
960
|
+
const content = await builder.renderFile(
|
|
961
|
+
filePath,
|
|
962
|
+
this.options.variables,
|
|
963
|
+
{}
|
|
964
|
+
);
|
|
965
|
+
const relativePath = import_path4.default.relative(this.options.templateDir, filePath);
|
|
966
|
+
const outputName = relativePath.replace(ext, ".html");
|
|
967
|
+
const outputPath = import_path4.default.join(this.options.outputDir, outputName);
|
|
968
|
+
const outputDir = import_path4.default.dirname(outputPath);
|
|
969
|
+
if (!import_fs4.default.existsSync(outputDir)) {
|
|
970
|
+
import_fs4.default.mkdirSync(outputDir, { recursive: true });
|
|
971
|
+
}
|
|
972
|
+
import_fs4.default.writeFileSync(outputPath, content, "utf8");
|
|
973
|
+
this.options.onRebuild?.(outputPath);
|
|
974
|
+
} catch (error) {
|
|
975
|
+
this.options.onError?.(error, filePath);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
980
|
+
0 && (module.exports = {
|
|
981
|
+
CommandHelper,
|
|
982
|
+
FileHelper,
|
|
983
|
+
ParamHelper,
|
|
984
|
+
Pipeline,
|
|
985
|
+
Scaffold,
|
|
986
|
+
StringHelper,
|
|
987
|
+
TEMPLATE_HANDLERS,
|
|
988
|
+
TemplateBuilder,
|
|
989
|
+
Watcher
|
|
990
|
+
});
|
|
991
|
+
//# sourceMappingURL=index.cjs.map
|