@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.
- package/README.md +188 -41
- 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 +50 -7
- 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 -10
- package/.travis.yml +0 -9
- 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 -41
- package/src/ejs_helper.js +0 -30
- package/src/file_helper.js +0 -140
- 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 -18
- package/src/template_builder.js +0 -38
- package/src/template_handlers.js +0 -7
- package/test/test.js +0 -394
package/dist/index.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FileHelper,
|
|
3
|
+
Scaffold,
|
|
4
|
+
StringHelper,
|
|
5
|
+
TEMPLATE_HANDLERS,
|
|
6
|
+
TemplateBuilder
|
|
7
|
+
} from "./chunk-XKYTW4HW.js";
|
|
8
|
+
|
|
9
|
+
// src/command_helper.ts
|
|
10
|
+
import { exec } from "child_process";
|
|
11
|
+
var FORBIDDEN_PATTERNS = [
|
|
12
|
+
/[;&|`$]/,
|
|
13
|
+
// shell metacharacters
|
|
14
|
+
/\$\(/,
|
|
15
|
+
// command substitution
|
|
16
|
+
/>\s*\//,
|
|
17
|
+
// redirect to absolute path
|
|
18
|
+
/\.\.\//
|
|
19
|
+
// path traversal
|
|
20
|
+
];
|
|
21
|
+
var CommandHelper = class _CommandHelper {
|
|
22
|
+
/**
|
|
23
|
+
* Executes one or more shell commands in the given directory.
|
|
24
|
+
* Multiple commands are joined with `&&` (sequential execution).
|
|
25
|
+
*
|
|
26
|
+
* @param directory - Working directory for command execution
|
|
27
|
+
* @param command - One or more command strings to execute
|
|
28
|
+
* @returns A promise resolving to the command's stdout (or stderr if stdout is empty)
|
|
29
|
+
* @throws If the directory is empty, no commands are provided, or a command matches a forbidden pattern
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const output = await CommandHelper.run('.', 'echo hello');
|
|
34
|
+
* // => "hello\n"
|
|
35
|
+
*
|
|
36
|
+
* const result = await CommandHelper.run('.', 'git add .', 'git status');
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
static run(directory, ...command) {
|
|
40
|
+
if (!directory) {
|
|
41
|
+
return Promise.reject(new Error("directory is required"));
|
|
42
|
+
}
|
|
43
|
+
if (command.length === 0) {
|
|
44
|
+
return Promise.reject(new Error("at least one command is required"));
|
|
45
|
+
}
|
|
46
|
+
for (const cmd of command) {
|
|
47
|
+
for (const pattern of FORBIDDEN_PATTERNS) {
|
|
48
|
+
if (pattern.test(cmd)) {
|
|
49
|
+
return Promise.reject(
|
|
50
|
+
new Error(
|
|
51
|
+
`potentially unsafe command rejected: "${cmd}" matches forbidden pattern ${pattern}`
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const cmd = command.join(" && ");
|
|
59
|
+
exec(
|
|
60
|
+
cmd,
|
|
61
|
+
{ cwd: directory, maxBuffer: 1024 * 1024 * 100 },
|
|
62
|
+
(err, stdout, stderr) => {
|
|
63
|
+
if (err) {
|
|
64
|
+
reject(err);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (stderr) {
|
|
68
|
+
resolve(stderr);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
resolve(stdout);
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Executes commands and returns the output with newlines stripped.
|
|
78
|
+
*
|
|
79
|
+
* @param folder - Working directory for command execution
|
|
80
|
+
* @param command - One or more command strings to execute
|
|
81
|
+
* @returns A promise resolving to the cleaned output (no newlines)
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const version = await CommandHelper.runClean('.', 'node --version');
|
|
86
|
+
* // => "v20.10.0"
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
static runClean(folder, ...command) {
|
|
90
|
+
return _CommandHelper.run(folder, ...command).then((result) => {
|
|
91
|
+
return result ? result.replace(/\r?\n|\r/g, "") : "";
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/param_helper.ts
|
|
97
|
+
var ParamHelper = class {
|
|
98
|
+
/**
|
|
99
|
+
* Appends a custom parameter string to `process.argv`.
|
|
100
|
+
*
|
|
101
|
+
* @param customParam - The parameter to add (e.g., `'--env=production'`)
|
|
102
|
+
*/
|
|
103
|
+
static addCustomParam(customParam) {
|
|
104
|
+
process.argv.push(customParam);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Retrieves the command-line argument at the given index.
|
|
108
|
+
*
|
|
109
|
+
* @param index - Zero-based index into `process.argv`
|
|
110
|
+
* @returns The argument at the given index, or `''` if out of bounds
|
|
111
|
+
*/
|
|
112
|
+
static getCommandByIndex(index) {
|
|
113
|
+
return process.argv.length > index ? process.argv[index] : "";
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Parses all `--key=value` parameters from `process.argv` into an object.
|
|
117
|
+
* Surrounding quotes on values are stripped automatically.
|
|
118
|
+
*
|
|
119
|
+
* @returns An object mapping parameter names to their string values
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* // node script.js --name=Alice --port=3000
|
|
124
|
+
* ParamHelper.getParams();
|
|
125
|
+
* // => { name: "Alice", port: "3000" }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
static getParams() {
|
|
129
|
+
const paramsObj = {};
|
|
130
|
+
process.argv.slice(2).forEach((element) => {
|
|
131
|
+
const matches = element.match("--([a-zA-Z0-9]+)=(.*)");
|
|
132
|
+
if (matches) {
|
|
133
|
+
paramsObj[matches[1]] = matches[2].replace(/^['"]/, "").replace(/['"]$/, "");
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return paramsObj;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/pipeline.ts
|
|
141
|
+
import fs from "fs";
|
|
142
|
+
import path from "path";
|
|
143
|
+
var Pipeline = class {
|
|
144
|
+
content = null;
|
|
145
|
+
contentPromise = null;
|
|
146
|
+
transforms = [];
|
|
147
|
+
/**
|
|
148
|
+
* Sets the pipeline content from a raw string.
|
|
149
|
+
*
|
|
150
|
+
* @param source - The string content
|
|
151
|
+
* @returns This pipeline instance for chaining
|
|
152
|
+
*/
|
|
153
|
+
fromString(source) {
|
|
154
|
+
this.content = source;
|
|
155
|
+
this.contentPromise = null;
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Sets the pipeline content by reading a file synchronously.
|
|
160
|
+
*
|
|
161
|
+
* @param filePath - Path to the file to read
|
|
162
|
+
* @returns This pipeline instance for chaining
|
|
163
|
+
*/
|
|
164
|
+
fromFile(filePath) {
|
|
165
|
+
this.content = fs.readFileSync(filePath, "utf8");
|
|
166
|
+
this.contentPromise = null;
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Sets the pipeline content by rendering a template file.
|
|
171
|
+
* If no engine is specified, it is auto-detected from the file extension.
|
|
172
|
+
*
|
|
173
|
+
* @param templatePath - Path to the template file
|
|
174
|
+
* @param data - Data to pass to the template
|
|
175
|
+
* @param engine - Template engine name (auto-detected if omitted)
|
|
176
|
+
* @param options - Optional engine-specific options
|
|
177
|
+
* @returns This pipeline instance for chaining
|
|
178
|
+
*/
|
|
179
|
+
fromTemplate(templatePath, data, engine, options) {
|
|
180
|
+
const ext = engine ?? extToEngine(templatePath);
|
|
181
|
+
const builder = new TemplateBuilder(ext);
|
|
182
|
+
this.contentPromise = builder.renderFile(templatePath, data, options);
|
|
183
|
+
this.content = null;
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Sets the pipeline content by rendering a template from a source string.
|
|
188
|
+
*
|
|
189
|
+
* @param source - The template source string
|
|
190
|
+
* @param data - Data to pass to the template
|
|
191
|
+
* @param engine - The template engine name
|
|
192
|
+
* @param options - Optional engine-specific options
|
|
193
|
+
* @returns This pipeline instance for chaining
|
|
194
|
+
*/
|
|
195
|
+
fromTemplateString(source, data, engine, options) {
|
|
196
|
+
const builder = new TemplateBuilder(engine);
|
|
197
|
+
this.contentPromise = builder.render(source, data, options);
|
|
198
|
+
this.content = null;
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Adds a transformation function to the pipeline.
|
|
203
|
+
* Transforms are executed sequentially in the order they are added.
|
|
204
|
+
*
|
|
205
|
+
* @param fn - A sync or async function that receives and returns a string
|
|
206
|
+
* @returns This pipeline instance for chaining
|
|
207
|
+
*/
|
|
208
|
+
transform(fn) {
|
|
209
|
+
this.transforms.push(fn);
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Executes the pipeline: resolves the content and applies all transforms.
|
|
214
|
+
*
|
|
215
|
+
* @returns A promise resolving to the final transformed string
|
|
216
|
+
* @throws If no content source has been set
|
|
217
|
+
*/
|
|
218
|
+
async execute() {
|
|
219
|
+
let result;
|
|
220
|
+
if (this.contentPromise) {
|
|
221
|
+
result = await this.contentPromise;
|
|
222
|
+
} else if (this.content !== null) {
|
|
223
|
+
result = this.content;
|
|
224
|
+
} else {
|
|
225
|
+
throw new Error(
|
|
226
|
+
"Pipeline has no content. Call fromString(), fromFile(), or fromTemplate() first."
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
for (const fn of this.transforms) {
|
|
230
|
+
result = await fn(result);
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Executes the pipeline and writes the result to a file.
|
|
236
|
+
* Creates parent directories automatically if they don't exist.
|
|
237
|
+
*
|
|
238
|
+
* @param filePath - Path to the output file
|
|
239
|
+
* @returns A promise resolving to the written content
|
|
240
|
+
*/
|
|
241
|
+
async writeTo(filePath) {
|
|
242
|
+
const result = await this.execute();
|
|
243
|
+
const dir = path.dirname(filePath);
|
|
244
|
+
if (!fs.existsSync(dir)) {
|
|
245
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
246
|
+
}
|
|
247
|
+
fs.writeFileSync(filePath, result, "utf8");
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
function extToEngine(filePath) {
|
|
252
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
253
|
+
switch (ext) {
|
|
254
|
+
case ".ejs":
|
|
255
|
+
return "EJS";
|
|
256
|
+
case ".hbs":
|
|
257
|
+
case ".handlebars":
|
|
258
|
+
return "HANDLEBARS";
|
|
259
|
+
case ".pug":
|
|
260
|
+
case ".jade":
|
|
261
|
+
return "PUG";
|
|
262
|
+
default:
|
|
263
|
+
return "HANDLEBARS";
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/watcher.ts
|
|
268
|
+
import fs2 from "fs";
|
|
269
|
+
import path2 from "path";
|
|
270
|
+
var Watcher = class {
|
|
271
|
+
options;
|
|
272
|
+
abortController = null;
|
|
273
|
+
/**
|
|
274
|
+
* Creates a new Watcher instance.
|
|
275
|
+
*
|
|
276
|
+
* @param options - Watcher configuration
|
|
277
|
+
*/
|
|
278
|
+
constructor(options) {
|
|
279
|
+
this.options = {
|
|
280
|
+
extensions: [".ejs", ".hbs", ".handlebars", ".pug"],
|
|
281
|
+
...options
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Starts watching the template directory for changes.
|
|
286
|
+
* Does nothing if the watcher is already running.
|
|
287
|
+
*/
|
|
288
|
+
start() {
|
|
289
|
+
if (this.abortController) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.abortController = new AbortController();
|
|
293
|
+
const { signal } = this.abortController;
|
|
294
|
+
if (!fs2.existsSync(this.options.outputDir)) {
|
|
295
|
+
fs2.mkdirSync(this.options.outputDir, { recursive: true });
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const watcher = fs2.watch(this.options.templateDir, {
|
|
299
|
+
recursive: true,
|
|
300
|
+
signal
|
|
301
|
+
});
|
|
302
|
+
watcher.on("change", (_eventType, filename) => {
|
|
303
|
+
if (!filename) return;
|
|
304
|
+
const filePath = path2.join(
|
|
305
|
+
this.options.templateDir,
|
|
306
|
+
filename.toString()
|
|
307
|
+
);
|
|
308
|
+
this.handleChange(filePath);
|
|
309
|
+
});
|
|
310
|
+
watcher.on("error", (err) => {
|
|
311
|
+
if (err.code !== "ABORT_ERR") {
|
|
312
|
+
this.options.onError?.(err, "");
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Stops watching for changes and cleans up resources.
|
|
320
|
+
*/
|
|
321
|
+
stop() {
|
|
322
|
+
if (this.abortController) {
|
|
323
|
+
this.abortController.abort();
|
|
324
|
+
this.abortController = null;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Whether the watcher is currently active.
|
|
329
|
+
*/
|
|
330
|
+
get isRunning() {
|
|
331
|
+
return this.abortController !== null;
|
|
332
|
+
}
|
|
333
|
+
async handleChange(filePath) {
|
|
334
|
+
const ext = path2.extname(filePath).toLowerCase();
|
|
335
|
+
if (!this.options.extensions.includes(ext)) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (!fs2.existsSync(filePath)) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const builder = new TemplateBuilder(this.options.engine);
|
|
343
|
+
const content = await builder.renderFile(
|
|
344
|
+
filePath,
|
|
345
|
+
this.options.variables,
|
|
346
|
+
{}
|
|
347
|
+
);
|
|
348
|
+
const relativePath = path2.relative(this.options.templateDir, filePath);
|
|
349
|
+
const outputName = relativePath.replace(ext, ".html");
|
|
350
|
+
const outputPath = path2.join(this.options.outputDir, outputName);
|
|
351
|
+
const outputDir = path2.dirname(outputPath);
|
|
352
|
+
if (!fs2.existsSync(outputDir)) {
|
|
353
|
+
fs2.mkdirSync(outputDir, { recursive: true });
|
|
354
|
+
}
|
|
355
|
+
fs2.writeFileSync(outputPath, content, "utf8");
|
|
356
|
+
this.options.onRebuild?.(outputPath);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
this.options.onError?.(error, filePath);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
export {
|
|
363
|
+
CommandHelper,
|
|
364
|
+
FileHelper,
|
|
365
|
+
ParamHelper,
|
|
366
|
+
Pipeline,
|
|
367
|
+
Scaffold,
|
|
368
|
+
StringHelper,
|
|
369
|
+
TEMPLATE_HANDLERS,
|
|
370
|
+
TemplateBuilder,
|
|
371
|
+
Watcher
|
|
372
|
+
};
|
|
373
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/command_helper.ts","../src/param_helper.ts","../src/pipeline.ts","../src/watcher.ts"],"sourcesContent":["import { exec } from 'child_process';\n\n/** @internal Patterns that indicate potentially unsafe shell commands. */\nconst FORBIDDEN_PATTERNS = [\n /[;&|`$]/, // shell metacharacters\n /\\$\\(/, // command substitution\n />\\s*\\//, // redirect to absolute path\n /\\.\\.\\// // path traversal\n];\n\n/**\n * Utility class for executing shell commands as promises with input sanitization.\n *\n * Commands are validated against a set of forbidden patterns (shell metacharacters,\n * command substitution, path traversal) before execution.\n *\n * @example\n * ```typescript\n * const output = await CommandHelper.run('.', 'echo hello');\n * const version = await CommandHelper.runClean('.', 'node --version');\n * ```\n */\nexport class CommandHelper {\n /**\n * Executes one or more shell commands in the given directory.\n * Multiple commands are joined with `&&` (sequential execution).\n *\n * @param directory - Working directory for command execution\n * @param command - One or more command strings to execute\n * @returns A promise resolving to the command's stdout (or stderr if stdout is empty)\n * @throws If the directory is empty, no commands are provided, or a command matches a forbidden pattern\n *\n * @example\n * ```typescript\n * const output = await CommandHelper.run('.', 'echo hello');\n * // => \"hello\\n\"\n *\n * const result = await CommandHelper.run('.', 'git add .', 'git status');\n * ```\n */\n static run(directory: string, ...command: string[]): Promise<string> {\n if (!directory) {\n return Promise.reject(new Error('directory is required'));\n }\n if (command.length === 0) {\n return Promise.reject(new Error('at least one command is required'));\n }\n\n for (const cmd of command) {\n for (const pattern of FORBIDDEN_PATTERNS) {\n if (pattern.test(cmd)) {\n return Promise.reject(\n new Error(\n `potentially unsafe command rejected: \"${cmd}\" matches forbidden pattern ${pattern}`\n )\n );\n }\n }\n }\n\n return new Promise((resolve, reject) => {\n const cmd = command.join(' && ');\n exec(\n cmd,\n { cwd: directory, maxBuffer: 1024 * 1024 * 100 },\n (err, stdout, stderr) => {\n if (err) {\n reject(err);\n return;\n }\n\n if (stderr) {\n resolve(stderr);\n return;\n }\n\n resolve(stdout);\n }\n );\n });\n }\n\n /**\n * Executes commands and returns the output with newlines stripped.\n *\n * @param folder - Working directory for command execution\n * @param command - One or more command strings to execute\n * @returns A promise resolving to the cleaned output (no newlines)\n *\n * @example\n * ```typescript\n * const version = await CommandHelper.runClean('.', 'node --version');\n * // => \"v20.10.0\"\n * ```\n */\n static runClean(folder: string, ...command: string[]): Promise<string> {\n return CommandHelper.run(folder, ...command).then((result) => {\n return result ? result.replace(/\\r?\\n|\\r/g, '') : '';\n });\n }\n}\n","/**\n * Utility class for parsing CLI parameters from `process.argv`.\n *\n * @example\n * ```typescript\n * // node script.js --name=Alice --port=3000\n * const params = ParamHelper.getParams();\n * // => { name: \"Alice\", port: \"3000\" }\n * ```\n */\nexport class ParamHelper {\n /**\n * Appends a custom parameter string to `process.argv`.\n *\n * @param customParam - The parameter to add (e.g., `'--env=production'`)\n */\n static addCustomParam(customParam: string): void {\n process.argv.push(customParam);\n }\n\n /**\n * Retrieves the command-line argument at the given index.\n *\n * @param index - Zero-based index into `process.argv`\n * @returns The argument at the given index, or `''` if out of bounds\n */\n static getCommandByIndex(index: number): string {\n return process.argv.length > index ? process.argv[index] : '';\n }\n\n /**\n * Parses all `--key=value` parameters from `process.argv` into an object.\n * Surrounding quotes on values are stripped automatically.\n *\n * @returns An object mapping parameter names to their string values\n *\n * @example\n * ```typescript\n * // node script.js --name=Alice --port=3000\n * ParamHelper.getParams();\n * // => { name: \"Alice\", port: \"3000\" }\n * ```\n */\n static getParams(): Record<string, string> {\n const paramsObj: Record<string, string> = {};\n process.argv.slice(2).forEach((element) => {\n const matches = element.match('--([a-zA-Z0-9]+)=(.*)');\n if (matches) {\n paramsObj[matches[1]] = matches[2]\n .replace(/^['\"]/, '')\n .replace(/['\"]$/, '');\n }\n });\n return paramsObj;\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { TemplateBuilder } from './template_builder.js';\nimport { type TemplateHandler } from './template_handlers.js';\n\n/** A function that transforms a string, optionally returning a promise. */\nexport type TransformFn = (content: string) => string | Promise<string>;\n\n/**\n * Chainable pipeline for composing template rendering and string transformations.\n *\n * Supports multiple input sources (string, file, template) and sequential\n * transforms before writing the result to a file or returning it.\n *\n * @example\n * ```typescript\n * const result = await new Pipeline()\n * .fromTemplate('component.ejs', { name: 'Button' }, 'EJS')\n * .transform((content) => content.toUpperCase())\n * .writeTo('src/components/Button.tsx');\n * ```\n */\nexport class Pipeline {\n private content: string | null = null;\n private contentPromise: Promise<string> | null = null;\n private transforms: TransformFn[] = [];\n\n /**\n * Sets the pipeline content from a raw string.\n *\n * @param source - The string content\n * @returns This pipeline instance for chaining\n */\n fromString(source: string): Pipeline {\n this.content = source;\n this.contentPromise = null;\n return this;\n }\n\n /**\n * Sets the pipeline content by reading a file synchronously.\n *\n * @param filePath - Path to the file to read\n * @returns This pipeline instance for chaining\n */\n fromFile(filePath: string): Pipeline {\n this.content = fs.readFileSync(filePath, 'utf8');\n this.contentPromise = null;\n return this;\n }\n\n /**\n * Sets the pipeline content by rendering a template file.\n * If no engine is specified, it is auto-detected from the file extension.\n *\n * @param templatePath - Path to the template file\n * @param data - Data to pass to the template\n * @param engine - Template engine name (auto-detected if omitted)\n * @param options - Optional engine-specific options\n * @returns This pipeline instance for chaining\n */\n fromTemplate(\n templatePath: string,\n data: Record<string, unknown>,\n engine?: TemplateHandler | string,\n options?: Record<string, unknown>\n ): Pipeline {\n const ext = engine ?? extToEngine(templatePath);\n const builder = new TemplateBuilder(ext);\n this.contentPromise = builder.renderFile(templatePath, data, options);\n this.content = null;\n return this;\n }\n\n /**\n * Sets the pipeline content by rendering a template from a source string.\n *\n * @param source - The template source string\n * @param data - Data to pass to the template\n * @param engine - The template engine name\n * @param options - Optional engine-specific options\n * @returns This pipeline instance for chaining\n */\n fromTemplateString(\n source: string,\n data: Record<string, unknown>,\n engine: TemplateHandler | string,\n options?: Record<string, unknown>\n ): Pipeline {\n const builder = new TemplateBuilder(engine);\n this.contentPromise = builder.render(source, data, options);\n this.content = null;\n return this;\n }\n\n /**\n * Adds a transformation function to the pipeline.\n * Transforms are executed sequentially in the order they are added.\n *\n * @param fn - A sync or async function that receives and returns a string\n * @returns This pipeline instance for chaining\n */\n transform(fn: TransformFn): Pipeline {\n this.transforms.push(fn);\n return this;\n }\n\n /**\n * Executes the pipeline: resolves the content and applies all transforms.\n *\n * @returns A promise resolving to the final transformed string\n * @throws If no content source has been set\n */\n async execute(): Promise<string> {\n let result: string;\n\n if (this.contentPromise) {\n result = await this.contentPromise;\n } else if (this.content !== null) {\n result = this.content;\n } else {\n throw new Error(\n 'Pipeline has no content. Call fromString(), fromFile(), or fromTemplate() first.'\n );\n }\n\n for (const fn of this.transforms) {\n result = await fn(result);\n }\n\n return result;\n }\n\n /**\n * Executes the pipeline and writes the result to a file.\n * Creates parent directories automatically if they don't exist.\n *\n * @param filePath - Path to the output file\n * @returns A promise resolving to the written content\n */\n async writeTo(filePath: string): Promise<string> {\n const result = await this.execute();\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, result, 'utf8');\n return result;\n }\n}\n\n/** @internal Maps file extensions to engine names. */\nfunction extToEngine(filePath: string): string {\n const ext = path.extname(filePath).toLowerCase();\n switch (ext) {\n case '.ejs':\n return 'EJS';\n case '.hbs':\n case '.handlebars':\n return 'HANDLEBARS';\n case '.pug':\n case '.jade':\n return 'PUG';\n default:\n return 'HANDLEBARS';\n }\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { TemplateBuilder } from './template_builder.js';\n\n/** Configuration options for the file watcher. */\nexport interface WatcherOptions {\n /** Directory containing template files to watch. */\n templateDir: string;\n /** Directory where rendered output files are written. */\n outputDir: string;\n /** Template engine to use for rendering (e.g., `'EJS'`, `'HANDLEBARS'`, `'PUG'`). */\n engine: string;\n /** Variables to pass to the template engine during rendering. */\n variables: Record<string, unknown>;\n /** File extensions to watch (defaults to `['.ejs', '.hbs', '.handlebars', '.pug']`). */\n extensions?: string[];\n /** Callback invoked after a file is successfully rebuilt. */\n onRebuild?: (file: string) => void;\n /** Callback invoked when a rebuild error occurs. */\n onError?: (error: Error, file: string) => void;\n}\n\n/**\n * Watches a template directory and automatically re-renders templates when they change.\n *\n * Uses `fs.watch()` with `AbortController` for clean start/stop lifecycle.\n *\n * @example\n * ```typescript\n * const watcher = new Watcher({\n * templateDir: './templates',\n * outputDir: './generated',\n * engine: 'EJS',\n * variables: { project: 'MyApp' },\n * onRebuild: (file) => console.log(`Rebuilt: ${file}`),\n * onError: (err, file) => console.error(`Error in ${file}: ${err.message}`),\n * });\n *\n * watcher.start();\n * // Later: watcher.stop();\n * ```\n */\nexport class Watcher {\n private options: WatcherOptions;\n private abortController: AbortController | null = null;\n\n /**\n * Creates a new Watcher instance.\n *\n * @param options - Watcher configuration\n */\n constructor(options: WatcherOptions) {\n this.options = {\n extensions: ['.ejs', '.hbs', '.handlebars', '.pug'],\n ...options\n };\n }\n\n /**\n * Starts watching the template directory for changes.\n * Does nothing if the watcher is already running.\n */\n start(): void {\n if (this.abortController) {\n return;\n }\n\n this.abortController = new AbortController();\n const { signal } = this.abortController;\n\n if (!fs.existsSync(this.options.outputDir)) {\n fs.mkdirSync(this.options.outputDir, { recursive: true });\n }\n\n try {\n const watcher = fs.watch(this.options.templateDir, {\n recursive: true,\n signal\n });\n\n watcher.on('change', (_eventType, filename) => {\n if (!filename) return;\n const filePath = path.join(\n this.options.templateDir,\n filename.toString()\n );\n this.handleChange(filePath);\n });\n\n watcher.on('error', (err) => {\n if ((err as NodeJS.ErrnoException).code !== 'ABORT_ERR') {\n this.options.onError?.(err, '');\n }\n });\n } catch {\n // fs.watch not available\n }\n }\n\n /**\n * Stops watching for changes and cleans up resources.\n */\n stop(): void {\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n }\n }\n\n /**\n * Whether the watcher is currently active.\n */\n get isRunning(): boolean {\n return this.abortController !== null;\n }\n\n private async handleChange(filePath: string): Promise<void> {\n const ext = path.extname(filePath).toLowerCase();\n if (!this.options.extensions!.includes(ext)) {\n return;\n }\n\n if (!fs.existsSync(filePath)) {\n return;\n }\n\n try {\n const builder = new TemplateBuilder(this.options.engine);\n const content = await builder.renderFile(\n filePath,\n this.options.variables,\n {}\n );\n\n const relativePath = path.relative(this.options.templateDir, filePath);\n const outputName = relativePath.replace(ext, '.html');\n const outputPath = path.join(this.options.outputDir, outputName);\n\n const outputDir = path.dirname(outputPath);\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true });\n }\n\n fs.writeFileSync(outputPath, content, 'utf8');\n this.options.onRebuild?.(outputPath);\n } catch (error) {\n this.options.onError?.(error as Error, filePath);\n }\n }\n}\n"],"mappings":"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","names":["fs","path","fs","path"]}
|
package/package.json
CHANGED
|
@@ -1,14 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiveor/scg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Simple Code Generator - A utility library for code generation and template processing",
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"scg": "./dist/cli.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"templates"
|
|
27
|
+
],
|
|
6
28
|
"scripts": {
|
|
7
|
-
"
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"dev": "tsup --watch",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"lint": "eslint src/",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"format": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
|
|
36
|
+
"format:check": "prettier --check 'src/**/*.ts' 'test/**/*.ts'",
|
|
37
|
+
"docs": "typedoc",
|
|
38
|
+
"prepublishOnly": "npm run build",
|
|
8
39
|
"example": "node example/index.js"
|
|
9
40
|
},
|
|
10
41
|
"engines": {
|
|
11
|
-
"node": ">=
|
|
42
|
+
"node": ">=18.0.0"
|
|
12
43
|
},
|
|
13
44
|
"keywords": [
|
|
14
45
|
"code-generator",
|
|
@@ -18,7 +49,8 @@
|
|
|
18
49
|
"handlebars",
|
|
19
50
|
"pug",
|
|
20
51
|
"file-helper",
|
|
21
|
-
"string-helper"
|
|
52
|
+
"string-helper",
|
|
53
|
+
"typescript"
|
|
22
54
|
],
|
|
23
55
|
"repository": {
|
|
24
56
|
"type": "git",
|
|
@@ -31,8 +63,19 @@
|
|
|
31
63
|
},
|
|
32
64
|
"homepage": "https://github.com/tiveor/scg#readme",
|
|
33
65
|
"devDependencies": {
|
|
34
|
-
"
|
|
35
|
-
"
|
|
66
|
+
"@eslint/js": "^9.39.2",
|
|
67
|
+
"@types/ejs": "^3.1.5",
|
|
68
|
+
"@types/node": "^25.2.3",
|
|
69
|
+
"@types/pug": "^2.0.10",
|
|
70
|
+
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
|
71
|
+
"@typescript-eslint/parser": "^8.55.0",
|
|
72
|
+
"eslint": "^9.39.2",
|
|
73
|
+
"prettier": "^2.8.8",
|
|
74
|
+
"tsup": "^8.5.1",
|
|
75
|
+
"typedoc": "^0.28.16",
|
|
76
|
+
"typescript": "^5.9.3",
|
|
77
|
+
"typescript-eslint": "^8.55.0",
|
|
78
|
+
"vitest": "^4.0.18"
|
|
36
79
|
},
|
|
37
80
|
"dependencies": {
|
|
38
81
|
"ejs": "^3.1.6",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { <%= name %>Service } from './<%= name %>.service';
|
|
3
|
+
|
|
4
|
+
const service = new <%= name %>Service();
|
|
5
|
+
|
|
6
|
+
export async function getAll(req: Request, res: Response, next: NextFunction) {
|
|
7
|
+
try {
|
|
8
|
+
const items = await service.findAll();
|
|
9
|
+
res.json(items);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
next(error);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function getById(req: Request, res: Response, next: NextFunction) {
|
|
16
|
+
try {
|
|
17
|
+
const item = await service.findById(req.params.id);
|
|
18
|
+
if (!item) {
|
|
19
|
+
res.status(404).json({ message: '<%= name %> not found' });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
res.json(item);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
next(error);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function create(req: Request, res: Response, next: NextFunction) {
|
|
29
|
+
try {
|
|
30
|
+
const item = await service.create(req.body);
|
|
31
|
+
res.status(201).json(item);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
next(error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"engine": "EJS",
|
|
3
|
+
"templateDir": "./templates/express-route",
|
|
4
|
+
"outputDir": "./src/routes/{{name}}",
|
|
5
|
+
"variables": {
|
|
6
|
+
"name": "User"
|
|
7
|
+
},
|
|
8
|
+
"structure": [
|
|
9
|
+
{ "template": "routes.ejs", "output": "{{name}}.routes.ts" },
|
|
10
|
+
{ "template": "controller.ejs", "output": "{{name}}.controller.ts" },
|
|
11
|
+
{ "template": "service.ejs", "output": "{{name}}.service.ts" }
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class <%= name %>Service {
|
|
2
|
+
async findAll(): Promise<unknown[]> {
|
|
3
|
+
// TODO: Implement data fetching
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async findById(id: string): Promise<unknown | null> {
|
|
8
|
+
// TODO: Implement find by id
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async create(data: unknown): Promise<unknown> {
|
|
13
|
+
// TODO: Implement create
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: <%= name %>
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [<%= branch %>]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [<%= branch %>]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
node-version: [18, 20, 22]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: ${{ matrix.node-version }}
|
|
24
|
+
cache: 'npm'
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: npm ci
|
|
28
|
+
|
|
29
|
+
- name: Test
|
|
30
|
+
run: npm test
|
|
31
|
+
|
|
32
|
+
- name: Build
|
|
33
|
+
run: npm run build
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
<% if (style === 'module') { %>
|
|
3
|
+
import styles from './<%= name %>.module.css';
|
|
4
|
+
<% } %>
|
|
5
|
+
|
|
6
|
+
export interface <%= name %>Props {
|
|
7
|
+
className?: string;
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const <%= name %>: React.FC<<%= name %>Props> = ({ className, children }) => {
|
|
12
|
+
<% if (style === 'module') { %>
|
|
13
|
+
return (
|
|
14
|
+
<div className={`${styles.root} ${className ?? ''}`}>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
<% } else { %>
|
|
19
|
+
return (
|
|
20
|
+
<div className={className}>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
<% } %>
|
|
25
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"engine": "EJS",
|
|
3
|
+
"templateDir": "./templates/react-component",
|
|
4
|
+
"outputDir": "./src/components/{{name}}",
|
|
5
|
+
"variables": {
|
|
6
|
+
"name": "MyComponent",
|
|
7
|
+
"style": "module"
|
|
8
|
+
},
|
|
9
|
+
"structure": [
|
|
10
|
+
{ "template": "component.ejs", "output": "{{name}}.tsx" },
|
|
11
|
+
{ "template": "test.ejs", "output": "{{name}}.test.tsx" },
|
|
12
|
+
{ "template": "styles.ejs", "output": "{{name}}.module.css" },
|
|
13
|
+
{ "template": "index.ejs", "output": "index.ts" }
|
|
14
|
+
]
|
|
15
|
+
}
|