@tiveor/scg 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +188 -41
  2. package/dist/chunk-XKYTW4HW.js +589 -0
  3. package/dist/chunk-XKYTW4HW.js.map +1 -0
  4. package/dist/cli.cjs +779 -0
  5. package/dist/cli.cjs.map +1 -0
  6. package/dist/cli.d.cts +1 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +173 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/index.cjs +991 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +653 -0
  13. package/dist/index.d.ts +653 -0
  14. package/dist/index.js +373 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +50 -7
  17. package/templates/express-route/controller.ejs +35 -0
  18. package/templates/express-route/routes.ejs +10 -0
  19. package/templates/express-route/scaffold.json +13 -0
  20. package/templates/express-route/service.ejs +16 -0
  21. package/templates/github-action/scaffold.json +12 -0
  22. package/templates/github-action/workflow.ejs +33 -0
  23. package/templates/react-component/component.ejs +25 -0
  24. package/templates/react-component/index.ejs +2 -0
  25. package/templates/react-component/scaffold.json +15 -0
  26. package/templates/react-component/styles.ejs +4 -0
  27. package/templates/react-component/test.ejs +14 -0
  28. package/templates/vue-component/component.ejs +19 -0
  29. package/templates/vue-component/scaffold.json +12 -0
  30. package/templates/vue-component/test.ejs +16 -0
  31. package/.prettierignore +0 -1
  32. package/.prettierrc +0 -10
  33. package/.travis.yml +0 -9
  34. package/example/ejs/conditional.ejs +0 -9
  35. package/example/ejs/hello.ejs +0 -8
  36. package/example/handlebars/conditional.handlebars +0 -9
  37. package/example/handlebars/hello.handlebars +0 -6
  38. package/example/index.js +0 -180
  39. package/example/pug/conditional.pug +0 -4
  40. package/example/pug/hello.pug +0 -3
  41. package/index.js +0 -15
  42. package/src/command_helper.js +0 -41
  43. package/src/ejs_helper.js +0 -30
  44. package/src/file_helper.js +0 -140
  45. package/src/handlebars_helper.js +0 -32
  46. package/src/param_helper.js +0 -25
  47. package/src/pug_helper.js +0 -28
  48. package/src/string_helper.js +0 -18
  49. package/src/template_builder.js +0 -38
  50. package/src/template_handlers.js +0 -7
  51. package/test/test.js +0 -394
package/dist/cli.cjs ADDED
@@ -0,0 +1,779 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var import_fs3 = __toESM(require("fs"), 1);
28
+ var import_path3 = __toESM(require("path"), 1);
29
+
30
+ // src/handlebars_helper.ts
31
+ var import_handlebars = __toESM(require("handlebars"), 1);
32
+
33
+ // src/file_helper.ts
34
+ var import_fs = __toESM(require("fs"), 1);
35
+ var import_path = __toESM(require("path"), 1);
36
+ var import_readline = __toESM(require("readline"), 1);
37
+
38
+ // src/string_helper.ts
39
+ var StringHelper = class _StringHelper {
40
+ /**
41
+ * Escapes all regex special characters in a string.
42
+ *
43
+ * @param string - The string to escape
44
+ * @returns The escaped string safe for use in `new RegExp()`
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * StringHelper.escapeRegex('$100.00 (test)');
49
+ * // => "\\$100\\.00 \\(test\\)"
50
+ * ```
51
+ */
52
+ static escapeRegex(string) {
53
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
54
+ }
55
+ /**
56
+ * Replaces all occurrences of a token in a string. The token is treated
57
+ * as a literal string (regex special characters are escaped automatically).
58
+ *
59
+ * @param line - The source string to search in. Returns `''` if not a string.
60
+ * @param token - The token to search for. Returns `line` unchanged if not a string.
61
+ * @param value - The replacement value
62
+ * @returns The string with all occurrences replaced
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * StringHelper.replace('Price: $10.00', '$10.00', '$20.00');
67
+ * // => "Price: $20.00"
68
+ * ```
69
+ */
70
+ static replace(line, token, value) {
71
+ if (typeof line !== "string") return "";
72
+ if (typeof token !== "string") return line;
73
+ return line.replace(
74
+ new RegExp(_StringHelper.escapeRegex(token), "g"),
75
+ value
76
+ );
77
+ }
78
+ /**
79
+ * Capitalizes the first character of a string.
80
+ *
81
+ * @param s - The string to capitalize. Returns `''` if not a string.
82
+ * @returns The string with the first character uppercased
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * StringHelper.capitalize('hello world');
87
+ * // => "Hello world"
88
+ * ```
89
+ */
90
+ static capitalize(s) {
91
+ if (typeof s !== "string") return "";
92
+ return s.charAt(0).toUpperCase() + s.slice(1);
93
+ }
94
+ };
95
+
96
+ // src/file_helper.ts
97
+ var FileHelper = class _FileHelper {
98
+ // --- Sync methods ---
99
+ /**
100
+ * Reads a file synchronously and returns its content as a UTF-8 string.
101
+ *
102
+ * @param fileName - Path to the file to read
103
+ * @returns The file content as a string
104
+ */
105
+ static readFileToString(fileName) {
106
+ return import_fs.default.readFileSync(fileName, "utf8");
107
+ }
108
+ /**
109
+ * Reads a JSON file synchronously and parses it into an object.
110
+ *
111
+ * @typeParam T - The expected type of the parsed object
112
+ * @param fileName - Path to the JSON file
113
+ * @returns The parsed object
114
+ */
115
+ static convertJsonFileToObject(fileName) {
116
+ const rawString = _FileHelper.readFileToString(fileName);
117
+ return JSON.parse(rawString);
118
+ }
119
+ /**
120
+ * Creates a directory (and any parent directories) if it doesn't exist.
121
+ *
122
+ * @param folderName - Path to the directory to create
123
+ */
124
+ static createFolder(folderName) {
125
+ const resolved = import_path.default.resolve(folderName);
126
+ if (!import_fs.default.existsSync(resolved)) {
127
+ import_fs.default.mkdirSync(resolved, { recursive: true });
128
+ }
129
+ }
130
+ /**
131
+ * Removes a directory and all its contents recursively.
132
+ *
133
+ * @param folderName - Path to the directory to remove
134
+ */
135
+ static removeFolder(folderName) {
136
+ const resolved = import_path.default.resolve(folderName);
137
+ if (import_fs.default.existsSync(resolved)) {
138
+ import_fs.default.rmSync(resolved, { recursive: true, force: true });
139
+ }
140
+ }
141
+ /**
142
+ * Removes a file if it exists.
143
+ *
144
+ * @param filename - Path to the file to remove
145
+ */
146
+ static removeFile(filename) {
147
+ const resolved = import_path.default.resolve(filename);
148
+ if (import_fs.default.existsSync(resolved)) {
149
+ import_fs.default.rmSync(resolved, { force: true });
150
+ }
151
+ }
152
+ // --- Async methods ---
153
+ /**
154
+ * Reads a file asynchronously and returns its content as a UTF-8 string.
155
+ *
156
+ * @param fileName - Path to the file to read
157
+ * @returns A promise resolving to the file content
158
+ */
159
+ static async readFileAsync(fileName) {
160
+ return import_fs.default.promises.readFile(fileName, "utf8");
161
+ }
162
+ /**
163
+ * Reads a JSON file asynchronously and parses it into an object.
164
+ *
165
+ * @typeParam T - The expected type of the parsed object
166
+ * @param fileName - Path to the JSON file
167
+ * @returns A promise resolving to the parsed object
168
+ */
169
+ static async readJsonFileAsync(fileName) {
170
+ const raw = await import_fs.default.promises.readFile(fileName, "utf8");
171
+ return JSON.parse(raw);
172
+ }
173
+ /**
174
+ * Writes content to a file asynchronously, creating parent directories as needed.
175
+ *
176
+ * @param fileName - Path to the file to write
177
+ * @param content - The string content to write
178
+ */
179
+ static async writeFileAsync(fileName, content) {
180
+ const dir = import_path.default.dirname(fileName);
181
+ await import_fs.default.promises.mkdir(dir, { recursive: true });
182
+ await import_fs.default.promises.writeFile(fileName, content, "utf8");
183
+ }
184
+ /**
185
+ * Creates a directory asynchronously (with recursive parent creation).
186
+ *
187
+ * @param folderName - Path to the directory to create
188
+ */
189
+ static async createFolderAsync(folderName) {
190
+ const resolved = import_path.default.resolve(folderName);
191
+ await import_fs.default.promises.mkdir(resolved, { recursive: true });
192
+ }
193
+ /**
194
+ * Removes a directory and its contents recursively (async).
195
+ *
196
+ * @param folderName - Path to the directory to remove
197
+ */
198
+ static async removeFolderAsync(folderName) {
199
+ const resolved = import_path.default.resolve(folderName);
200
+ await import_fs.default.promises.rm(resolved, { recursive: true, force: true });
201
+ }
202
+ /**
203
+ * Removes a file asynchronously.
204
+ *
205
+ * @param filename - Path to the file to remove
206
+ */
207
+ static async removeFileAsync(filename) {
208
+ const resolved = import_path.default.resolve(filename);
209
+ await import_fs.default.promises.rm(resolved, { force: true });
210
+ }
211
+ /**
212
+ * Checks whether a file or directory exists asynchronously.
213
+ *
214
+ * @param filePath - Path to check
215
+ * @returns `true` if the path exists, `false` otherwise
216
+ */
217
+ static async existsAsync(filePath) {
218
+ try {
219
+ await import_fs.default.promises.access(filePath);
220
+ return true;
221
+ } catch {
222
+ return false;
223
+ }
224
+ }
225
+ // --- Template processing methods ---
226
+ /**
227
+ * Performs a simple token replacement on a single line.
228
+ *
229
+ * @param line - The source line
230
+ * @param replacement - The replacement definition (uses `token` and `value`)
231
+ * @returns The line with the token replaced
232
+ */
233
+ static simpleReplace(line, replacement) {
234
+ return StringHelper.replace(line, replacement.token, replacement.value);
235
+ }
236
+ /**
237
+ * Performs dynamic replacement using a sub-template and variables.
238
+ * Reads the sub-template line by line and applies all variable replacements.
239
+ *
240
+ * @param replacement - The replacement definition with `template` and `variables`
241
+ * @returns A promise resolving to the processed string
242
+ */
243
+ static async dynamicReplace(replacement) {
244
+ let res = "";
245
+ for (const v in replacement.variables) {
246
+ const properties = replacement.variables[v];
247
+ await _FileHelper.readLineByLine(
248
+ replacement.template,
249
+ (line) => {
250
+ let newLine = line;
251
+ for (const x in properties) {
252
+ const property = properties[x];
253
+ if (line.indexOf(property.token) >= 0) {
254
+ if (property.value) {
255
+ newLine = _FileHelper.simpleReplace(newLine, property);
256
+ }
257
+ }
258
+ }
259
+ res += newLine;
260
+ }
261
+ );
262
+ }
263
+ return res;
264
+ }
265
+ /**
266
+ * Creates a new file from a template file, applying variable replacements line by line.
267
+ * The special token `@date` is automatically replaced with the current UTC date.
268
+ *
269
+ * @param options - The template path, output path, and variables to apply
270
+ *
271
+ * @example
272
+ * ```typescript
273
+ * await FileHelper.createFileFromFile({
274
+ * template: 'templates/component.txt',
275
+ * newFile: 'output/Button.tsx',
276
+ * variables: [
277
+ * { token: '{{name}}', value: 'Button' },
278
+ * { token: '{{style}}', value: 'primary' }
279
+ * ]
280
+ * });
281
+ * ```
282
+ */
283
+ static async createFileFromFile({
284
+ template,
285
+ newFile,
286
+ variables
287
+ }) {
288
+ const writer = _FileHelper.writer(newFile);
289
+ await _FileHelper.readLineByLine(template, async (line) => {
290
+ let newLine = StringHelper.replace(
291
+ line,
292
+ "@date",
293
+ (/* @__PURE__ */ new Date()).toUTCString()
294
+ );
295
+ for (const v in variables) {
296
+ const replacement = variables[v];
297
+ if (newLine.indexOf(replacement.token) > 0) {
298
+ if (replacement.template) {
299
+ newLine = await _FileHelper.dynamicReplace(replacement);
300
+ break;
301
+ } else if (replacement.value) {
302
+ newLine = _FileHelper.simpleReplace(newLine, replacement);
303
+ }
304
+ }
305
+ }
306
+ await _FileHelper.writeLineByLine(writer, newLine);
307
+ });
308
+ writer.end();
309
+ }
310
+ /**
311
+ * Creates a string from a template file, applying variable replacements line by line.
312
+ * The special token `@date` is automatically replaced with the current UTC date.
313
+ *
314
+ * @param options - The template path and variables to apply
315
+ * @returns A promise resolving to the processed string
316
+ */
317
+ static async createStringFromFile({
318
+ template,
319
+ variables
320
+ }) {
321
+ let res = "";
322
+ await _FileHelper.readLineByLine(template, async (line) => {
323
+ let newLine = StringHelper.replace(
324
+ line,
325
+ "@date",
326
+ (/* @__PURE__ */ new Date()).toUTCString()
327
+ );
328
+ for (const v in variables) {
329
+ const replacement = variables[v];
330
+ if (newLine.indexOf(replacement.token) > 0) {
331
+ if (replacement.template) {
332
+ newLine = await _FileHelper.dynamicReplace(replacement);
333
+ break;
334
+ } else if (replacement.value) {
335
+ newLine = _FileHelper.simpleReplace(newLine, replacement);
336
+ }
337
+ }
338
+ }
339
+ res += newLine + "\n";
340
+ });
341
+ return res;
342
+ }
343
+ /**
344
+ * Reads a file line by line and invokes a callback for each line.
345
+ *
346
+ * @param fileName - Path to the file to read
347
+ * @param callback - Function called for each line
348
+ */
349
+ static async readLineByLine(fileName, callback) {
350
+ const fileStream = import_fs.default.createReadStream(fileName);
351
+ const rl = import_readline.default.createInterface({
352
+ input: fileStream,
353
+ crlfDelay: Infinity
354
+ });
355
+ for await (const line of rl) {
356
+ await callback(line);
357
+ }
358
+ }
359
+ /**
360
+ * Creates a writable stream for appending to a file.
361
+ *
362
+ * @param filename - Path to the file
363
+ * @returns A writable stream in append mode
364
+ */
365
+ static writer(filename) {
366
+ return import_fs.default.createWriteStream(filename, {
367
+ flags: "a"
368
+ });
369
+ }
370
+ /**
371
+ * Writes a single line to a writable stream with CRLF line ending.
372
+ *
373
+ * @param writer - The writable stream
374
+ * @param newLine - The line content to write
375
+ */
376
+ static async writeLineByLine(writer, newLine) {
377
+ writer.write(`${newLine}\r
378
+ `);
379
+ }
380
+ };
381
+
382
+ // src/handlebars_helper.ts
383
+ var HandlebarsHelper = class {
384
+ static render(source, data, options) {
385
+ return new Promise((resolve, reject) => {
386
+ try {
387
+ const template = import_handlebars.default.compile(source, options);
388
+ const res = template(data);
389
+ resolve(res);
390
+ } catch (error) {
391
+ reject(error);
392
+ }
393
+ });
394
+ }
395
+ static renderFile(fileName, data, options) {
396
+ return new Promise((resolve, reject) => {
397
+ try {
398
+ const source = FileHelper.readFileToString(fileName);
399
+ const template = import_handlebars.default.compile(source, options);
400
+ const res = template(data);
401
+ resolve(res);
402
+ } catch (error) {
403
+ reject(error);
404
+ }
405
+ });
406
+ }
407
+ };
408
+
409
+ // src/ejs_helper.ts
410
+ var import_ejs = __toESM(require("ejs"), 1);
411
+ var EjsHelper = class {
412
+ static render(source, data, options) {
413
+ return new Promise((resolve, reject) => {
414
+ try {
415
+ const template = import_ejs.default.render(source, data, options);
416
+ resolve(template);
417
+ } catch (error) {
418
+ reject(error);
419
+ }
420
+ });
421
+ }
422
+ static renderFile(fileName, data, options) {
423
+ return new Promise((resolve, reject) => {
424
+ import_ejs.default.renderFile(fileName, data, options ?? {}, (err, str) => {
425
+ if (err) {
426
+ reject(err);
427
+ return;
428
+ }
429
+ resolve(str);
430
+ });
431
+ });
432
+ }
433
+ };
434
+
435
+ // src/pug_helper.ts
436
+ var import_pug = __toESM(require("pug"), 1);
437
+ var PugHelper = class {
438
+ static render(source, data, options) {
439
+ return new Promise((resolve, reject) => {
440
+ try {
441
+ const template = import_pug.default.compile(source, options);
442
+ resolve(template(data));
443
+ } catch (error) {
444
+ reject(error);
445
+ }
446
+ });
447
+ }
448
+ static renderFile(fileName, data, options) {
449
+ return new Promise((resolve, reject) => {
450
+ try {
451
+ const template = import_pug.default.compileFile(fileName, options);
452
+ resolve(template(data));
453
+ } catch (error) {
454
+ reject(error);
455
+ }
456
+ });
457
+ }
458
+ };
459
+
460
+ // src/template_handlers.ts
461
+ var TEMPLATE_HANDLERS = {
462
+ HANDLEBARS: "HANDLEBARS",
463
+ EJS: "EJS",
464
+ PUG: "PUG"
465
+ };
466
+
467
+ // src/template_builder.ts
468
+ var builtInEngines = {
469
+ [TEMPLATE_HANDLERS.HANDLEBARS]: HandlebarsHelper,
470
+ [TEMPLATE_HANDLERS.EJS]: EjsHelper,
471
+ [TEMPLATE_HANDLERS.PUG]: PugHelper
472
+ };
473
+ var customEngines = {};
474
+ var TemplateBuilder = class {
475
+ templateHandler;
476
+ /**
477
+ * Creates a new TemplateBuilder for the specified engine.
478
+ *
479
+ * @param templateHandler - The engine name (e.g., `'EJS'`, `'HANDLEBARS'`, `'PUG'`, or a custom name)
480
+ */
481
+ constructor(templateHandler) {
482
+ this.templateHandler = templateHandler;
483
+ }
484
+ /**
485
+ * Registers a custom template engine that can be used with `new TemplateBuilder(name)`.
486
+ *
487
+ * @param name - A unique name for the engine
488
+ * @param engine - An object implementing the {@link TemplateEngine} interface
489
+ * @throws If `name` is empty or `engine` doesn't implement `render()` and `renderFile()`
490
+ *
491
+ * @example
492
+ * ```typescript
493
+ * TemplateBuilder.registerEngine('nunjucks', {
494
+ * render: async (source, data) => nunjucks.renderString(source, data),
495
+ * renderFile: async (file, data) => nunjucks.render(file, data),
496
+ * });
497
+ * ```
498
+ */
499
+ static registerEngine(name, engine) {
500
+ if (!name || typeof name !== "string") {
501
+ throw new Error("engine name must be a non-empty string");
502
+ }
503
+ if (!engine || typeof engine.render !== "function" || typeof engine.renderFile !== "function") {
504
+ throw new Error("engine must implement render() and renderFile() methods");
505
+ }
506
+ customEngines[name] = engine;
507
+ }
508
+ /**
509
+ * Retrieves a registered engine by name (custom engines take precedence over built-in).
510
+ *
511
+ * @param name - The engine name to look up
512
+ * @returns The engine implementation, or `undefined` if not found
513
+ */
514
+ static getEngine(name) {
515
+ return customEngines[name] ?? builtInEngines[name];
516
+ }
517
+ /**
518
+ * Returns an array of all registered engine names (built-in + custom).
519
+ *
520
+ * @returns Array of engine name strings
521
+ *
522
+ * @example
523
+ * ```typescript
524
+ * TemplateBuilder.getRegisteredEngines();
525
+ * // => ['HANDLEBARS', 'EJS', 'PUG']
526
+ * ```
527
+ */
528
+ static getRegisteredEngines() {
529
+ return [
530
+ ...Object.keys(builtInEngines),
531
+ ...Object.keys(customEngines)
532
+ ];
533
+ }
534
+ resolveEngine() {
535
+ const engine = customEngines[this.templateHandler] ?? builtInEngines[this.templateHandler] ?? builtInEngines[TEMPLATE_HANDLERS.HANDLEBARS];
536
+ return engine;
537
+ }
538
+ /**
539
+ * Renders a template from a source string.
540
+ *
541
+ * @param source - The template source string
542
+ * @param data - Data object to pass to the template
543
+ * @param options - Optional engine-specific options
544
+ * @returns A promise resolving to the rendered string
545
+ */
546
+ render(source, data, options) {
547
+ return this.resolveEngine().render(source, data, options);
548
+ }
549
+ /**
550
+ * Renders a template from a file path.
551
+ *
552
+ * @param fileName - Path to the template file
553
+ * @param data - Data object to pass to the template
554
+ * @param options - Optional engine-specific options
555
+ * @returns A promise resolving to the rendered string
556
+ */
557
+ renderFile(fileName, data, options) {
558
+ return this.resolveEngine().renderFile(fileName, data, options);
559
+ }
560
+ };
561
+
562
+ // src/scaffold.ts
563
+ var import_fs2 = __toESM(require("fs"), 1);
564
+ var import_path2 = __toESM(require("path"), 1);
565
+ var Scaffold = class {
566
+ /**
567
+ * Generates files from a scaffold manifest.
568
+ *
569
+ * @param options - The scaffold configuration
570
+ * @returns A promise resolving to a {@link ScaffoldResult} with the list of created files
571
+ */
572
+ static async from(options) {
573
+ const {
574
+ engine,
575
+ templateDir,
576
+ outputDir,
577
+ variables,
578
+ structure,
579
+ dryRun = false
580
+ } = options;
581
+ const builder = new TemplateBuilder(engine);
582
+ const resolvedOutputDir = resolveVariables(outputDir, variables);
583
+ const createdFiles = [];
584
+ for (const file of structure) {
585
+ const templatePath = import_path2.default.join(templateDir, file.template);
586
+ const outputFileName = resolveVariables(file.output, variables);
587
+ const outputPath = import_path2.default.join(resolvedOutputDir, outputFileName);
588
+ if (dryRun) {
589
+ createdFiles.push(outputPath);
590
+ continue;
591
+ }
592
+ const dir = import_path2.default.dirname(outputPath);
593
+ if (!import_fs2.default.existsSync(dir)) {
594
+ import_fs2.default.mkdirSync(dir, { recursive: true });
595
+ }
596
+ const content = await builder.renderFile(
597
+ templatePath,
598
+ variables,
599
+ {}
600
+ );
601
+ import_fs2.default.writeFileSync(outputPath, content, "utf8");
602
+ createdFiles.push(outputPath);
603
+ }
604
+ return { files: createdFiles, dryRun };
605
+ }
606
+ };
607
+ function resolveVariables(template, variables) {
608
+ let result = template;
609
+ for (const [key, value] of Object.entries(variables)) {
610
+ result = StringHelper.replace(result, `{{${key}}}`, value);
611
+ }
612
+ return result;
613
+ }
614
+
615
+ // src/cli.ts
616
+ var args = process.argv.slice(2);
617
+ var command = args[0];
618
+ function parseFlags(args2) {
619
+ const flags = {};
620
+ for (const arg of args2) {
621
+ const match = arg.match(/^--([a-zA-Z0-9_-]+)=(.*)$/);
622
+ if (match) {
623
+ flags[match[1]] = match[2].replace(/^['"]/, "").replace(/['"]$/, "");
624
+ }
625
+ }
626
+ return flags;
627
+ }
628
+ function printHelp() {
629
+ console.log(`
630
+ SCG - Simple Code Generator
631
+
632
+ Usage:
633
+ scg <command> [options]
634
+
635
+ Commands:
636
+ init Initialize a scaffold manifest (scaffold.json)
637
+ generate Generate files from a scaffold manifest
638
+ render <template> Render a single template file
639
+
640
+ Options:
641
+ --manifest=<path> Path to scaffold manifest (default: scaffold.json)
642
+ --vars=<key=val,...> Comma-separated variables (e.g. name=Button,style=module)
643
+ --data=<json> JSON data for template rendering
644
+ --engine=<name> Template engine (EJS, HANDLEBARS, PUG)
645
+ --output=<path> Output file path (for render command)
646
+ --dry-run Preview without writing files
647
+
648
+ Examples:
649
+ scg init
650
+ scg generate --manifest=scaffold.json --vars=name=Button
651
+ scg render template.ejs --data='{"name": "World"}'
652
+ scg render template.ejs --data='{"name": "World"}' --output=output.html
653
+ `);
654
+ }
655
+ function parseVars(varsStr) {
656
+ const vars = {};
657
+ for (const pair of varsStr.split(",")) {
658
+ const [key, ...rest] = pair.split("=");
659
+ if (key && rest.length > 0) {
660
+ vars[key.trim()] = rest.join("=").trim();
661
+ }
662
+ }
663
+ return vars;
664
+ }
665
+ async function cmdInit() {
666
+ const manifest = {
667
+ engine: "EJS",
668
+ templateDir: "./templates",
669
+ outputDir: "./generated/{{name}}",
670
+ variables: { name: "MyComponent" },
671
+ structure: [
672
+ { template: "component.ejs", output: "{{name}}.tsx" },
673
+ { template: "test.ejs", output: "{{name}}.test.tsx" },
674
+ { template: "index.ejs", output: "index.ts" }
675
+ ]
676
+ };
677
+ const filePath = "scaffold.json";
678
+ import_fs3.default.writeFileSync(filePath, JSON.stringify(manifest, null, 2), "utf8");
679
+ console.log(`Created ${filePath}`);
680
+ console.log("Edit the manifest and run: scg generate");
681
+ }
682
+ async function cmdGenerate() {
683
+ const flags = parseFlags(args);
684
+ const manifestPath = flags["manifest"] ?? "scaffold.json";
685
+ const dryRun = args.includes("--dry-run");
686
+ if (!import_fs3.default.existsSync(manifestPath)) {
687
+ console.error(`Manifest not found: ${manifestPath}`);
688
+ console.error('Run "scg init" to create one.');
689
+ process.exit(1);
690
+ }
691
+ const manifest = JSON.parse(
692
+ import_fs3.default.readFileSync(manifestPath, "utf8")
693
+ );
694
+ if (flags["vars"]) {
695
+ Object.assign(manifest.variables, parseVars(flags["vars"]));
696
+ }
697
+ manifest.dryRun = dryRun;
698
+ const result = await Scaffold.from(manifest);
699
+ if (dryRun) {
700
+ console.log("Dry run - files that would be created:");
701
+ } else {
702
+ console.log("Generated files:");
703
+ }
704
+ for (const file of result.files) {
705
+ console.log(` ${file}`);
706
+ }
707
+ }
708
+ async function cmdRender() {
709
+ const templatePath = args[1];
710
+ if (!templatePath) {
711
+ console.error("Usage: scg render <template> [--data=<json>] [--engine=<name>] [--output=<path>]");
712
+ process.exit(1);
713
+ }
714
+ const flags = parseFlags(args);
715
+ const engine = flags["engine"] ?? extToEngine(templatePath);
716
+ const dataStr = flags["data"] ?? "{}";
717
+ const outputPath = flags["output"];
718
+ let data;
719
+ try {
720
+ data = JSON.parse(dataStr);
721
+ } catch {
722
+ console.error("Invalid JSON in --data flag");
723
+ process.exit(1);
724
+ }
725
+ const builder = new TemplateBuilder(engine);
726
+ const result = await builder.renderFile(templatePath, data, {});
727
+ if (outputPath) {
728
+ const dir = import_path3.default.dirname(outputPath);
729
+ if (!import_fs3.default.existsSync(dir)) {
730
+ import_fs3.default.mkdirSync(dir, { recursive: true });
731
+ }
732
+ import_fs3.default.writeFileSync(outputPath, result, "utf8");
733
+ console.log(`Written to ${outputPath}`);
734
+ } else {
735
+ console.log(result);
736
+ }
737
+ }
738
+ function extToEngine(filePath) {
739
+ const ext = import_path3.default.extname(filePath).toLowerCase();
740
+ switch (ext) {
741
+ case ".ejs":
742
+ return "EJS";
743
+ case ".hbs":
744
+ case ".handlebars":
745
+ return "HANDLEBARS";
746
+ case ".pug":
747
+ case ".jade":
748
+ return "PUG";
749
+ default:
750
+ return "HANDLEBARS";
751
+ }
752
+ }
753
+ async function main() {
754
+ switch (command) {
755
+ case "init":
756
+ await cmdInit();
757
+ break;
758
+ case "generate":
759
+ await cmdGenerate();
760
+ break;
761
+ case "render":
762
+ await cmdRender();
763
+ break;
764
+ case "--help":
765
+ case "-h":
766
+ case void 0:
767
+ printHelp();
768
+ break;
769
+ default:
770
+ console.error(`Unknown command: ${command}`);
771
+ printHelp();
772
+ process.exit(1);
773
+ }
774
+ }
775
+ main().catch((err) => {
776
+ console.error(err.message);
777
+ process.exit(1);
778
+ });
779
+ //# sourceMappingURL=cli.cjs.map