@dawitworku/projectcli 0.1.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/LICENSE +21 -0
- package/README.md +75 -0
- package/bin/projectcli.js +9 -0
- package/package.json +32 -0
- package/src/add.js +263 -0
- package/src/detect.js +44 -0
- package/src/index.js +513 -0
- package/src/libraries.js +72 -0
- package/src/pm.js +44 -0
- package/src/registry.js +515 -0
- package/src/run.js +77 -0
package/src/registry.js
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry of (language -> framework -> generator).
|
|
3
|
+
* Each generator provides:
|
|
4
|
+
* - id: stable id
|
|
5
|
+
* - label: display name
|
|
6
|
+
* - commands: array of steps (each step is a program + args)
|
|
7
|
+
* - notes: optional help text shown before running
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
function getProjectName(ctx) {
|
|
11
|
+
return typeof ctx === "string" ? ctx : ctx?.projectName;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getPackageManager(ctx) {
|
|
15
|
+
const pm = typeof ctx === "object" ? ctx?.packageManager : undefined;
|
|
16
|
+
return pm || "npm";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { pmExecCommand, pmInstallCommand } = require("./pm");
|
|
20
|
+
|
|
21
|
+
function pmExec(pm, pkg, pkgArgs, opts = {}) {
|
|
22
|
+
const cmd = pmExecCommand(pm, pkg, pkgArgs);
|
|
23
|
+
return { type: "command", ...cmd, ...opts };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function pmInstall(pm, opts = {}) {
|
|
27
|
+
const cmd = pmInstallCommand(pm);
|
|
28
|
+
return { type: "command", ...cmd, ...opts };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const REGISTRY = {
|
|
32
|
+
JavaScript: {
|
|
33
|
+
"Vite (Vanilla)": {
|
|
34
|
+
id: "js.vite.vanilla",
|
|
35
|
+
label: "Vite (Vanilla)",
|
|
36
|
+
commands: (ctx) => {
|
|
37
|
+
const projectName = getProjectName(ctx);
|
|
38
|
+
const pm = getPackageManager(ctx);
|
|
39
|
+
return [
|
|
40
|
+
pmExec(pm, "create-vite@latest", [
|
|
41
|
+
projectName,
|
|
42
|
+
"--template",
|
|
43
|
+
"vanilla",
|
|
44
|
+
]),
|
|
45
|
+
pmInstall(pm, { cwdFromProjectRoot: true }),
|
|
46
|
+
];
|
|
47
|
+
},
|
|
48
|
+
notes: "Vite (JS)",
|
|
49
|
+
},
|
|
50
|
+
"Vite (React)": {
|
|
51
|
+
id: "js.vite.react",
|
|
52
|
+
label: "Vite (React)",
|
|
53
|
+
commands: (ctx) => {
|
|
54
|
+
const projectName = getProjectName(ctx);
|
|
55
|
+
const pm = getPackageManager(ctx);
|
|
56
|
+
return [
|
|
57
|
+
pmExec(pm, "create-vite@latest", [
|
|
58
|
+
projectName,
|
|
59
|
+
"--template",
|
|
60
|
+
"react",
|
|
61
|
+
]),
|
|
62
|
+
pmInstall(pm, { cwdFromProjectRoot: true }),
|
|
63
|
+
];
|
|
64
|
+
},
|
|
65
|
+
notes: "Vite + React (JS)",
|
|
66
|
+
},
|
|
67
|
+
"Vite (Vue)": {
|
|
68
|
+
id: "js.vite.vue",
|
|
69
|
+
label: "Vite (Vue)",
|
|
70
|
+
commands: (ctx) => {
|
|
71
|
+
const projectName = getProjectName(ctx);
|
|
72
|
+
const pm = getPackageManager(ctx);
|
|
73
|
+
return [
|
|
74
|
+
pmExec(pm, "create-vite@latest", [projectName, "--template", "vue"]),
|
|
75
|
+
pmInstall(pm, { cwdFromProjectRoot: true }),
|
|
76
|
+
];
|
|
77
|
+
},
|
|
78
|
+
notes: "Vite + Vue (JS)",
|
|
79
|
+
},
|
|
80
|
+
"Vite (Svelte)": {
|
|
81
|
+
id: "js.vite.svelte",
|
|
82
|
+
label: "Vite (Svelte)",
|
|
83
|
+
commands: (ctx) => {
|
|
84
|
+
const projectName = getProjectName(ctx);
|
|
85
|
+
const pm = getPackageManager(ctx);
|
|
86
|
+
return [
|
|
87
|
+
pmExec(pm, "create-vite@latest", [
|
|
88
|
+
projectName,
|
|
89
|
+
"--template",
|
|
90
|
+
"svelte",
|
|
91
|
+
]),
|
|
92
|
+
pmInstall(pm, { cwdFromProjectRoot: true }),
|
|
93
|
+
];
|
|
94
|
+
},
|
|
95
|
+
notes: "Vite + Svelte (JS)",
|
|
96
|
+
},
|
|
97
|
+
"Next.js": {
|
|
98
|
+
id: "js.nextjs",
|
|
99
|
+
label: "Next.js",
|
|
100
|
+
commands: (ctx) => {
|
|
101
|
+
const projectName = getProjectName(ctx);
|
|
102
|
+
const pm = getPackageManager(ctx);
|
|
103
|
+
return [pmExec(pm, "create-next-app@latest", [projectName])];
|
|
104
|
+
},
|
|
105
|
+
notes: "Next.js app (wizard)",
|
|
106
|
+
},
|
|
107
|
+
Astro: {
|
|
108
|
+
id: "js.astro",
|
|
109
|
+
label: "Astro",
|
|
110
|
+
commands: (ctx) => {
|
|
111
|
+
const projectName = getProjectName(ctx);
|
|
112
|
+
const pm = getPackageManager(ctx);
|
|
113
|
+
// Astro docs recommend npm/pnpm/yarn create astro@latest; using the underlying package works too.
|
|
114
|
+
return [pmExec(pm, "create-astro@latest", [projectName])];
|
|
115
|
+
},
|
|
116
|
+
notes: "Astro site (wizard)",
|
|
117
|
+
},
|
|
118
|
+
SvelteKit: {
|
|
119
|
+
id: "js.sveltekit",
|
|
120
|
+
label: "SvelteKit",
|
|
121
|
+
commands: (ctx) => {
|
|
122
|
+
const projectName = getProjectName(ctx);
|
|
123
|
+
const pm = getPackageManager(ctx);
|
|
124
|
+
return [pmExec(pm, "create-svelte@latest", [projectName])];
|
|
125
|
+
},
|
|
126
|
+
notes: "SvelteKit app (wizard)",
|
|
127
|
+
},
|
|
128
|
+
Remix: {
|
|
129
|
+
id: "js.remix",
|
|
130
|
+
label: "Remix",
|
|
131
|
+
commands: (ctx) => {
|
|
132
|
+
const projectName = getProjectName(ctx);
|
|
133
|
+
const pm = getPackageManager(ctx);
|
|
134
|
+
return [pmExec(pm, "create-remix@latest", [projectName])];
|
|
135
|
+
},
|
|
136
|
+
notes: "Remix app (wizard)",
|
|
137
|
+
},
|
|
138
|
+
Nuxt: {
|
|
139
|
+
id: "js.nuxt",
|
|
140
|
+
label: "Nuxt",
|
|
141
|
+
commands: (ctx) => {
|
|
142
|
+
const projectName = getProjectName(ctx);
|
|
143
|
+
const pm = getPackageManager(ctx);
|
|
144
|
+
return [
|
|
145
|
+
pmExec(pm, "nuxi@latest", ["init", projectName]),
|
|
146
|
+
pmInstall(pm, { cwdFromProjectRoot: true }),
|
|
147
|
+
];
|
|
148
|
+
},
|
|
149
|
+
notes: "Nuxt via nuxi init",
|
|
150
|
+
},
|
|
151
|
+
"Node.js (basic)": {
|
|
152
|
+
id: "js.node.basic",
|
|
153
|
+
label: "Node.js (basic)",
|
|
154
|
+
commands: (ctx) => {
|
|
155
|
+
const projectName = getProjectName(ctx);
|
|
156
|
+
return [
|
|
157
|
+
{ type: "mkdir", path: "." },
|
|
158
|
+
{
|
|
159
|
+
type: "writeFile",
|
|
160
|
+
path: "package.json",
|
|
161
|
+
content: JSON.stringify(
|
|
162
|
+
{
|
|
163
|
+
name: projectName,
|
|
164
|
+
version: "0.1.0",
|
|
165
|
+
private: true,
|
|
166
|
+
type: "commonjs",
|
|
167
|
+
scripts: { start: "node index.js" },
|
|
168
|
+
},
|
|
169
|
+
null,
|
|
170
|
+
2
|
|
171
|
+
).concat("\n"),
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: "writeFile",
|
|
175
|
+
path: "index.js",
|
|
176
|
+
content: "console.log('hello');\n",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
type: "writeFile",
|
|
180
|
+
path: ".gitignore",
|
|
181
|
+
content: "node_modules\n.DS_Store\n.env\n",
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
},
|
|
185
|
+
notes: "No external generator required",
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
TypeScript: {
|
|
190
|
+
"Vite (React + TS)": {
|
|
191
|
+
id: "ts.vite.react",
|
|
192
|
+
label: "Vite (React + TS)",
|
|
193
|
+
commands: (ctx) => {
|
|
194
|
+
const projectName = getProjectName(ctx);
|
|
195
|
+
const pm = getPackageManager(ctx);
|
|
196
|
+
return [
|
|
197
|
+
pmExec(pm, "create-vite@latest", [
|
|
198
|
+
projectName,
|
|
199
|
+
"--template",
|
|
200
|
+
"react-ts",
|
|
201
|
+
]),
|
|
202
|
+
pmInstall(pm, { cwdFromProjectRoot: true }),
|
|
203
|
+
];
|
|
204
|
+
},
|
|
205
|
+
notes: "Vite + React + TS",
|
|
206
|
+
},
|
|
207
|
+
"Vite (Vanilla + TS)": {
|
|
208
|
+
id: "ts.vite.vanilla",
|
|
209
|
+
label: "Vite (Vanilla + TS)",
|
|
210
|
+
commands: (ctx) => {
|
|
211
|
+
const projectName = getProjectName(ctx);
|
|
212
|
+
const pm = getPackageManager(ctx);
|
|
213
|
+
return [
|
|
214
|
+
pmExec(pm, "create-vite@latest", [
|
|
215
|
+
projectName,
|
|
216
|
+
"--template",
|
|
217
|
+
"vanilla-ts",
|
|
218
|
+
]),
|
|
219
|
+
pmInstall(pm, { cwdFromProjectRoot: true }),
|
|
220
|
+
];
|
|
221
|
+
},
|
|
222
|
+
notes: "Vite + TypeScript",
|
|
223
|
+
},
|
|
224
|
+
"Vite (Vue + TS)": {
|
|
225
|
+
id: "ts.vite.vue",
|
|
226
|
+
label: "Vite (Vue + TS)",
|
|
227
|
+
commands: (ctx) => {
|
|
228
|
+
const projectName = getProjectName(ctx);
|
|
229
|
+
const pm = getPackageManager(ctx);
|
|
230
|
+
return [
|
|
231
|
+
pmExec(pm, "create-vite@latest", [
|
|
232
|
+
projectName,
|
|
233
|
+
"--template",
|
|
234
|
+
"vue-ts",
|
|
235
|
+
]),
|
|
236
|
+
pmInstall(pm, { cwdFromProjectRoot: true }),
|
|
237
|
+
];
|
|
238
|
+
},
|
|
239
|
+
notes: "Vite + Vue + TypeScript",
|
|
240
|
+
},
|
|
241
|
+
"TanStack Start": {
|
|
242
|
+
id: "ts.tanstack.start",
|
|
243
|
+
label: "TanStack Start",
|
|
244
|
+
commands: (ctx) => {
|
|
245
|
+
const projectName = getProjectName(ctx);
|
|
246
|
+
const pm = getPackageManager(ctx);
|
|
247
|
+
// TanStack docs: npm/pnpm create @tanstack/start@latest
|
|
248
|
+
// Running the package via pm exec is equivalent.
|
|
249
|
+
return [pmExec(pm, "@tanstack/start@latest", [projectName])];
|
|
250
|
+
},
|
|
251
|
+
notes: "Full-stack React framework (wizard)",
|
|
252
|
+
},
|
|
253
|
+
NestJS: {
|
|
254
|
+
id: "ts.nestjs",
|
|
255
|
+
label: "NestJS",
|
|
256
|
+
commands: (ctx) => {
|
|
257
|
+
const projectName = getProjectName(ctx);
|
|
258
|
+
const pm = getPackageManager(ctx);
|
|
259
|
+
// Nest CLI is interactive; it may ask about package manager.
|
|
260
|
+
return [pmExec(pm, "@nestjs/cli", ["new", projectName])];
|
|
261
|
+
},
|
|
262
|
+
notes: "Nest CLI (wizard)",
|
|
263
|
+
},
|
|
264
|
+
"Node.js (basic TS)": {
|
|
265
|
+
id: "ts.node.basic",
|
|
266
|
+
label: "Node.js (basic TS)",
|
|
267
|
+
commands: (ctx) => {
|
|
268
|
+
const projectName = getProjectName(ctx);
|
|
269
|
+
const pm = getPackageManager(ctx);
|
|
270
|
+
return [
|
|
271
|
+
{ type: "mkdir", path: "src" },
|
|
272
|
+
{
|
|
273
|
+
type: "writeFile",
|
|
274
|
+
path: "package.json",
|
|
275
|
+
content: JSON.stringify(
|
|
276
|
+
{
|
|
277
|
+
name: projectName,
|
|
278
|
+
version: "0.1.0",
|
|
279
|
+
private: true,
|
|
280
|
+
type: "module",
|
|
281
|
+
scripts: {
|
|
282
|
+
build: "tsc -p tsconfig.json",
|
|
283
|
+
start: "node dist/index.js",
|
|
284
|
+
},
|
|
285
|
+
devDependencies: {
|
|
286
|
+
typescript: "^5.0.0",
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
null,
|
|
290
|
+
2
|
|
291
|
+
).concat("\n"),
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
type: "writeFile",
|
|
295
|
+
path: "tsconfig.json",
|
|
296
|
+
content: JSON.stringify(
|
|
297
|
+
{
|
|
298
|
+
compilerOptions: {
|
|
299
|
+
target: "ES2022",
|
|
300
|
+
module: "ES2022",
|
|
301
|
+
outDir: "dist",
|
|
302
|
+
strict: true,
|
|
303
|
+
},
|
|
304
|
+
include: ["src"],
|
|
305
|
+
},
|
|
306
|
+
null,
|
|
307
|
+
2
|
|
308
|
+
).concat("\n"),
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
type: "writeFile",
|
|
312
|
+
path: "src/index.ts",
|
|
313
|
+
content: "console.log('hello');\n",
|
|
314
|
+
},
|
|
315
|
+
pmInstall(pm, { cwdFromProjectRoot: true }),
|
|
316
|
+
];
|
|
317
|
+
},
|
|
318
|
+
notes: "Writes files + installs TypeScript",
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
Python: {
|
|
323
|
+
Django: {
|
|
324
|
+
id: "py.django",
|
|
325
|
+
label: "Django",
|
|
326
|
+
commands: (ctx) => {
|
|
327
|
+
const projectName = getProjectName(ctx);
|
|
328
|
+
return [
|
|
329
|
+
{
|
|
330
|
+
program: "django-admin",
|
|
331
|
+
args: ["startproject", projectName],
|
|
332
|
+
},
|
|
333
|
+
];
|
|
334
|
+
},
|
|
335
|
+
notes: "Requires django-admin on PATH (pip install django).",
|
|
336
|
+
},
|
|
337
|
+
Flask: {
|
|
338
|
+
id: "py.flask.basic",
|
|
339
|
+
label: "Flask (basic)",
|
|
340
|
+
commands: (_ctx) => [
|
|
341
|
+
{ type: "mkdir", path: "." },
|
|
342
|
+
{
|
|
343
|
+
type: "writeFile",
|
|
344
|
+
path: "app.py",
|
|
345
|
+
content:
|
|
346
|
+
"from flask import Flask\n\napp = Flask(__name__)\n\n@app.get('/')\ndef hello():\n return {'status': 'ok'}\n\nif __name__ == '__main__':\n app.run(debug=True)\n",
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
type: "writeFile",
|
|
350
|
+
path: "requirements.txt",
|
|
351
|
+
content: "flask\n",
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
type: "writeFile",
|
|
355
|
+
path: ".gitignore",
|
|
356
|
+
content: ".venv\n__pycache__\n*.pyc\n.DS_Store\n",
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
notes: "Writes app.py + requirements.txt (no pip install).",
|
|
360
|
+
},
|
|
361
|
+
FastAPI: {
|
|
362
|
+
id: "py.fastapi.basic",
|
|
363
|
+
label: "FastAPI (basic)",
|
|
364
|
+
commands: (_ctx) => [
|
|
365
|
+
{ type: "mkdir", path: "." },
|
|
366
|
+
{
|
|
367
|
+
type: "writeFile",
|
|
368
|
+
path: "main.py",
|
|
369
|
+
content:
|
|
370
|
+
"from fastapi import FastAPI\n\napp = FastAPI()\n\n@app.get('/')\ndef root():\n return {'status': 'ok'}\n",
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
type: "writeFile",
|
|
374
|
+
path: "requirements.txt",
|
|
375
|
+
content: "fastapi\nuvicorn\n",
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
type: "writeFile",
|
|
379
|
+
path: "README.md",
|
|
380
|
+
content:
|
|
381
|
+
"# FastAPI app\n\nRun:\n\n- python -m venv .venv\n- . .venv/bin/activate\n- pip install -r requirements.txt\n- uvicorn main:app --reload\n",
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
type: "writeFile",
|
|
385
|
+
path: ".gitignore",
|
|
386
|
+
content: ".venv\n__pycache__\n*.pyc\n.DS_Store\n",
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
notes: "Writes files only (no pip install).",
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
Rust: {
|
|
394
|
+
"Cargo (bin)": {
|
|
395
|
+
id: "rs.cargo.bin",
|
|
396
|
+
label: "Cargo (bin)",
|
|
397
|
+
commands: (ctx) => {
|
|
398
|
+
const projectName = getProjectName(ctx);
|
|
399
|
+
return [
|
|
400
|
+
{
|
|
401
|
+
program: "cargo",
|
|
402
|
+
args: ["new", projectName],
|
|
403
|
+
},
|
|
404
|
+
];
|
|
405
|
+
},
|
|
406
|
+
notes: "Uses cargo new",
|
|
407
|
+
},
|
|
408
|
+
"Cargo (lib)": {
|
|
409
|
+
id: "rs.cargo.lib",
|
|
410
|
+
label: "Cargo (lib)",
|
|
411
|
+
commands: (ctx) => {
|
|
412
|
+
const projectName = getProjectName(ctx);
|
|
413
|
+
return [
|
|
414
|
+
{
|
|
415
|
+
program: "cargo",
|
|
416
|
+
args: ["new", "--lib", projectName],
|
|
417
|
+
},
|
|
418
|
+
];
|
|
419
|
+
},
|
|
420
|
+
notes: "Uses cargo new --lib",
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
Go: {
|
|
425
|
+
"Go module (basic)": {
|
|
426
|
+
id: "go.module.basic",
|
|
427
|
+
label: "Go module (basic)",
|
|
428
|
+
commands: (ctx) => {
|
|
429
|
+
const projectName = getProjectName(ctx);
|
|
430
|
+
return [
|
|
431
|
+
{
|
|
432
|
+
program: "go",
|
|
433
|
+
args: ["mod", "init", projectName],
|
|
434
|
+
cwdFromProjectRoot: true,
|
|
435
|
+
},
|
|
436
|
+
];
|
|
437
|
+
},
|
|
438
|
+
notes: "Creates folder then runs go mod init",
|
|
439
|
+
},
|
|
440
|
+
"Go module (hello)": {
|
|
441
|
+
id: "go.module.hello",
|
|
442
|
+
label: "Go module (hello)",
|
|
443
|
+
commands: (ctx) => {
|
|
444
|
+
const projectName = getProjectName(ctx);
|
|
445
|
+
return [
|
|
446
|
+
{ type: "mkdir", path: "." },
|
|
447
|
+
{
|
|
448
|
+
program: "go",
|
|
449
|
+
args: ["mod", "init", projectName],
|
|
450
|
+
cwdFromProjectRoot: true,
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
type: "writeFile",
|
|
454
|
+
path: "main.go",
|
|
455
|
+
content:
|
|
456
|
+
'package main\n\nimport "fmt"\n\nfunc main() {\n fmt.Println("hello")\n}\n',
|
|
457
|
+
},
|
|
458
|
+
];
|
|
459
|
+
},
|
|
460
|
+
notes: "Writes main.go + runs go mod init",
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
"C#": {
|
|
465
|
+
".NET console": {
|
|
466
|
+
id: "cs.dotnet.console",
|
|
467
|
+
label: ".NET console",
|
|
468
|
+
commands: (ctx) => {
|
|
469
|
+
const projectName = getProjectName(ctx);
|
|
470
|
+
return [
|
|
471
|
+
{
|
|
472
|
+
program: "dotnet",
|
|
473
|
+
args: ["new", "console", "-n", projectName],
|
|
474
|
+
},
|
|
475
|
+
];
|
|
476
|
+
},
|
|
477
|
+
notes: "Requires dotnet SDK",
|
|
478
|
+
},
|
|
479
|
+
".NET webapi": {
|
|
480
|
+
id: "cs.dotnet.webapi",
|
|
481
|
+
label: ".NET webapi",
|
|
482
|
+
commands: (ctx) => {
|
|
483
|
+
const projectName = getProjectName(ctx);
|
|
484
|
+
return [
|
|
485
|
+
{
|
|
486
|
+
program: "dotnet",
|
|
487
|
+
args: ["new", "webapi", "-n", projectName],
|
|
488
|
+
},
|
|
489
|
+
];
|
|
490
|
+
},
|
|
491
|
+
notes: "Requires dotnet SDK",
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
function getLanguages() {
|
|
497
|
+
return Object.keys(REGISTRY);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function getFrameworks(language) {
|
|
501
|
+
return Object.keys(REGISTRY[language] || {});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function getGenerator(language, framework) {
|
|
505
|
+
const lang = REGISTRY[language];
|
|
506
|
+
if (!lang) return null;
|
|
507
|
+
return lang[framework] || null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
module.exports = {
|
|
511
|
+
REGISTRY,
|
|
512
|
+
getLanguages,
|
|
513
|
+
getFrameworks,
|
|
514
|
+
getGenerator,
|
|
515
|
+
};
|
package/src/run.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const { spawn } = require("node:child_process");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
function runCommand({ program, args, cwd }) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const child = spawn(program, args, {
|
|
8
|
+
cwd,
|
|
9
|
+
stdio: "inherit",
|
|
10
|
+
shell: false,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
child.on("error", (err) => {
|
|
14
|
+
if (err && err.code === "ENOENT") {
|
|
15
|
+
reject(new Error(`Command not found: ${program}`));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
reject(err);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
child.on("close", (code) => {
|
|
22
|
+
if (code === 0) resolve();
|
|
23
|
+
else reject(new Error(`${program} exited with code ${code}`));
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveUnderProject(projectRoot, relativePath) {
|
|
29
|
+
const resolved = path.resolve(projectRoot, relativePath);
|
|
30
|
+
const rel = path.relative(projectRoot, resolved);
|
|
31
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Refusing to write outside project folder: ${relativePath}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function runStep(step, { projectRoot }) {
|
|
40
|
+
const type = step.type || "command";
|
|
41
|
+
|
|
42
|
+
if (type === "mkdir") {
|
|
43
|
+
const target = resolveUnderProject(projectRoot, step.path);
|
|
44
|
+
fs.mkdirSync(target, { recursive: true });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (type === "writeFile") {
|
|
49
|
+
const target = resolveUnderProject(projectRoot, step.path);
|
|
50
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
51
|
+
const overwrite = Boolean(step.overwrite);
|
|
52
|
+
if (!overwrite && fs.existsSync(target)) {
|
|
53
|
+
throw new Error(`File already exists: ${target}`);
|
|
54
|
+
}
|
|
55
|
+
fs.writeFileSync(target, step.content, "utf8");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (type === "command") {
|
|
60
|
+
const cwd = step.cwdFromProjectRoot ? projectRoot : process.cwd();
|
|
61
|
+
await runCommand({ program: step.program, args: step.args, cwd });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
throw new Error(`Unknown step type: ${type}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function runSteps(steps, { projectRoot }) {
|
|
69
|
+
for (const step of steps) {
|
|
70
|
+
await runStep(step, { projectRoot });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
runCommand,
|
|
76
|
+
runSteps,
|
|
77
|
+
};
|