@stackonward/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1041 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,1041 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import path5 from "path";
|
|
8
|
+
import fs4 from "fs-extra";
|
|
9
|
+
import prompts from "prompts";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
|
|
12
|
+
// src/utils/constants.ts
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import { dirname, resolve } from "path";
|
|
15
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
var __dirname = dirname(__filename);
|
|
17
|
+
var STACKONWARD_ROOT = resolve(__dirname, "../../..");
|
|
18
|
+
var PATHS = {
|
|
19
|
+
templates: resolve(STACKONWARD_ROOT, "packages/templates"),
|
|
20
|
+
components: resolve(STACKONWARD_ROOT, "packages/components"),
|
|
21
|
+
snippets: resolve(STACKONWARD_ROOT, "packages/snippets"),
|
|
22
|
+
configs: resolve(STACKONWARD_ROOT, "packages/configs"),
|
|
23
|
+
packagesRegistry: resolve(STACKONWARD_ROOT, "packages/registry.json")
|
|
24
|
+
};
|
|
25
|
+
var TEMPLATES = {
|
|
26
|
+
"mcp-server": {
|
|
27
|
+
name: "MCP Server",
|
|
28
|
+
description: "Model Context Protocol server template",
|
|
29
|
+
path: "mcp-server"
|
|
30
|
+
},
|
|
31
|
+
"node-cli": {
|
|
32
|
+
name: "Node CLI",
|
|
33
|
+
description: "Node.js CLI tool template",
|
|
34
|
+
path: "node-cli"
|
|
35
|
+
},
|
|
36
|
+
nuxt: {
|
|
37
|
+
name: "Nuxt",
|
|
38
|
+
description: "Nuxt 4 product-site starter with editable registry-installed source",
|
|
39
|
+
path: "nuxt",
|
|
40
|
+
registryItems: ["nuxt-site-source"]
|
|
41
|
+
},
|
|
42
|
+
"vue-spa": {
|
|
43
|
+
name: "Vue SPA",
|
|
44
|
+
description: "Vue 3 Single Page Application template",
|
|
45
|
+
path: "vue-spa"
|
|
46
|
+
},
|
|
47
|
+
next: {
|
|
48
|
+
name: "Next.js",
|
|
49
|
+
description: "Next.js React application template",
|
|
50
|
+
path: "next"
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var DEFAULT_PATHS = {
|
|
54
|
+
nuxt: {
|
|
55
|
+
components: "app/components",
|
|
56
|
+
composables: "app/composables",
|
|
57
|
+
utils: "app/utils"
|
|
58
|
+
},
|
|
59
|
+
vue: {
|
|
60
|
+
components: "src/components",
|
|
61
|
+
composables: "src/composables",
|
|
62
|
+
utils: "src/utils"
|
|
63
|
+
},
|
|
64
|
+
react: {
|
|
65
|
+
components: "src/components",
|
|
66
|
+
hooks: "src/hooks",
|
|
67
|
+
utils: "src/utils"
|
|
68
|
+
},
|
|
69
|
+
node: {
|
|
70
|
+
utils: "src/utils",
|
|
71
|
+
services: "src/services"
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/utils/logger.ts
|
|
76
|
+
import { blue, green, red, yellow, cyan, bold, dim } from "kolorist";
|
|
77
|
+
var logger = {
|
|
78
|
+
info: (msg) => console.log(blue("info"), msg),
|
|
79
|
+
success: (msg) => console.log(green("success"), msg),
|
|
80
|
+
warn: (msg) => console.log(yellow("warn"), msg),
|
|
81
|
+
error: (msg) => console.log(red("error"), msg),
|
|
82
|
+
// Styled outputs
|
|
83
|
+
title: (msg) => console.log(bold(cyan(msg))),
|
|
84
|
+
subtitle: (msg) => console.log(dim(msg)),
|
|
85
|
+
step: (step, total, msg) => console.log(dim(`[${step}/${total}]`), msg),
|
|
86
|
+
// Blank line
|
|
87
|
+
br: () => console.log(),
|
|
88
|
+
// Box output for important messages
|
|
89
|
+
box: (title, content) => {
|
|
90
|
+
const maxLen = Math.max(title.length, ...content.map((c) => c.length));
|
|
91
|
+
const border = "\u2500".repeat(maxLen + 2);
|
|
92
|
+
console.log(`\u250C${border}\u2510`);
|
|
93
|
+
console.log(`\u2502 ${bold(title.padEnd(maxLen))} \u2502`);
|
|
94
|
+
console.log(`\u251C${border}\u2524`);
|
|
95
|
+
content.forEach((line) => {
|
|
96
|
+
console.log(`\u2502 ${line.padEnd(maxLen)} \u2502`);
|
|
97
|
+
});
|
|
98
|
+
console.log(`\u2514${border}\u2518`);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/utils/fs.ts
|
|
103
|
+
import fs from "fs-extra";
|
|
104
|
+
import path from "path";
|
|
105
|
+
import { glob } from "glob";
|
|
106
|
+
async function copyTemplate(src, dest, variables = {}) {
|
|
107
|
+
await fs.ensureDir(dest);
|
|
108
|
+
const files = await glob("**/*", {
|
|
109
|
+
cwd: src,
|
|
110
|
+
dot: true,
|
|
111
|
+
nodir: true,
|
|
112
|
+
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
113
|
+
});
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
const srcPath = path.join(src, file);
|
|
116
|
+
let destPath = path.join(dest, file);
|
|
117
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
118
|
+
destPath = destPath.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
119
|
+
}
|
|
120
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
121
|
+
if (isTextFile(file)) {
|
|
122
|
+
let content = await fs.readFile(srcPath, "utf-8");
|
|
123
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
124
|
+
content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
125
|
+
}
|
|
126
|
+
await fs.writeFile(destPath, content);
|
|
127
|
+
} else {
|
|
128
|
+
await fs.copy(srcPath, destPath);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function copyFiles(files, overwrite = false) {
|
|
133
|
+
const copied = [];
|
|
134
|
+
const skipped = [];
|
|
135
|
+
for (const { src, dest } of files) {
|
|
136
|
+
if (!overwrite && await fs.pathExists(dest)) {
|
|
137
|
+
skipped.push(dest);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
await fs.ensureDir(path.dirname(dest));
|
|
141
|
+
await fs.copy(src, dest);
|
|
142
|
+
copied.push(dest);
|
|
143
|
+
}
|
|
144
|
+
return { copied, skipped };
|
|
145
|
+
}
|
|
146
|
+
function isTextFile(filename) {
|
|
147
|
+
const textExtensions = [
|
|
148
|
+
".ts",
|
|
149
|
+
".tsx",
|
|
150
|
+
".js",
|
|
151
|
+
".jsx",
|
|
152
|
+
".vue",
|
|
153
|
+
".json",
|
|
154
|
+
".md",
|
|
155
|
+
".yml",
|
|
156
|
+
".yaml",
|
|
157
|
+
".html",
|
|
158
|
+
".css",
|
|
159
|
+
".scss",
|
|
160
|
+
".less",
|
|
161
|
+
".txt",
|
|
162
|
+
".env",
|
|
163
|
+
".env.example",
|
|
164
|
+
".gitignore",
|
|
165
|
+
".prettierrc",
|
|
166
|
+
".eslintrc",
|
|
167
|
+
".editorconfig"
|
|
168
|
+
];
|
|
169
|
+
const ext = path.extname(filename).toLowerCase();
|
|
170
|
+
const basename = path.basename(filename);
|
|
171
|
+
return textExtensions.includes(ext) || basename.startsWith(".") || !ext;
|
|
172
|
+
}
|
|
173
|
+
async function isDirEmpty(dir) {
|
|
174
|
+
if (!await fs.pathExists(dir)) return true;
|
|
175
|
+
const files = await fs.readdir(dir);
|
|
176
|
+
return files.length === 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/utils/project.ts
|
|
180
|
+
import fs2 from "fs-extra";
|
|
181
|
+
import path2 from "path";
|
|
182
|
+
import { execa } from "execa";
|
|
183
|
+
async function detectProject(cwd = process.cwd()) {
|
|
184
|
+
const packageJsonPath = path2.join(cwd, "package.json");
|
|
185
|
+
const packageJson = await fs2.pathExists(packageJsonPath) ? await fs2.readJson(packageJsonPath) : null;
|
|
186
|
+
return {
|
|
187
|
+
name: packageJson?.name || path2.basename(cwd),
|
|
188
|
+
root: cwd,
|
|
189
|
+
packageJson,
|
|
190
|
+
framework: detectFramework(packageJson),
|
|
191
|
+
hasTypeScript: await hasTypeScript(cwd),
|
|
192
|
+
packageManager: await detectPackageManager(cwd)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function detectFramework(packageJson) {
|
|
196
|
+
if (!packageJson) return null;
|
|
197
|
+
const deps = {
|
|
198
|
+
...packageJson.dependencies,
|
|
199
|
+
...packageJson.devDependencies
|
|
200
|
+
};
|
|
201
|
+
if (deps.nuxt) return "nuxt";
|
|
202
|
+
if (deps.vue) return "vue";
|
|
203
|
+
if (deps.next || deps.react) return "react";
|
|
204
|
+
if (deps.express || deps.fastify || deps["@modelcontextprotocol/sdk"]) return "node";
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
async function hasTypeScript(cwd) {
|
|
208
|
+
return await fs2.pathExists(path2.join(cwd, "tsconfig.json")) || await fs2.pathExists(path2.join(cwd, "tsconfig.base.json"));
|
|
209
|
+
}
|
|
210
|
+
async function detectPackageManager(cwd) {
|
|
211
|
+
if (await fs2.pathExists(path2.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
212
|
+
if (await fs2.pathExists(path2.join(cwd, "yarn.lock"))) return "yarn";
|
|
213
|
+
if (await fs2.pathExists(path2.join(cwd, "bun.lockb"))) return "bun";
|
|
214
|
+
return "npm";
|
|
215
|
+
}
|
|
216
|
+
async function installDependencies(cwd, deps, isDev = false) {
|
|
217
|
+
const pm = await detectPackageManager(cwd);
|
|
218
|
+
const args = [];
|
|
219
|
+
switch (pm) {
|
|
220
|
+
case "pnpm":
|
|
221
|
+
args.push("add", ...deps);
|
|
222
|
+
if (isDev) args.push("-D");
|
|
223
|
+
break;
|
|
224
|
+
case "yarn":
|
|
225
|
+
args.push("add", ...deps);
|
|
226
|
+
if (isDev) args.push("-D");
|
|
227
|
+
break;
|
|
228
|
+
case "bun":
|
|
229
|
+
args.push("add", ...deps);
|
|
230
|
+
if (isDev) args.push("-d");
|
|
231
|
+
break;
|
|
232
|
+
default:
|
|
233
|
+
args.push("install", ...deps);
|
|
234
|
+
if (isDev) args.push("--save-dev");
|
|
235
|
+
}
|
|
236
|
+
await execa(pm, args, { cwd, stdio: "inherit" });
|
|
237
|
+
}
|
|
238
|
+
async function runInstall(cwd) {
|
|
239
|
+
const pm = await detectPackageManager(cwd);
|
|
240
|
+
await execa(pm, ["install"], { cwd, stdio: "inherit" });
|
|
241
|
+
}
|
|
242
|
+
async function initGit(cwd) {
|
|
243
|
+
await execa("git", ["init"], { cwd });
|
|
244
|
+
await execa("git", ["add", "-A"], { cwd });
|
|
245
|
+
await execa("git", ["commit", "-m", "chore: initial commit"], { cwd });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/utils/registry.ts
|
|
249
|
+
import fs3 from "fs-extra";
|
|
250
|
+
import path3 from "path";
|
|
251
|
+
var registryCache = null;
|
|
252
|
+
async function loadRegistry() {
|
|
253
|
+
if (registryCache) return registryCache;
|
|
254
|
+
const componentsRegistry = await loadComponentsRegistry();
|
|
255
|
+
const snippetsRegistry = await loadSnippetsRegistry();
|
|
256
|
+
const packagesRegistry = await loadPackagesRegistry();
|
|
257
|
+
registryCache = {
|
|
258
|
+
components: componentsRegistry,
|
|
259
|
+
snippets: snippetsRegistry,
|
|
260
|
+
packages: packagesRegistry
|
|
261
|
+
};
|
|
262
|
+
return registryCache;
|
|
263
|
+
}
|
|
264
|
+
async function loadPackagesRegistry() {
|
|
265
|
+
if (await fs3.pathExists(PATHS.packagesRegistry)) {
|
|
266
|
+
return await fs3.readJson(PATHS.packagesRegistry);
|
|
267
|
+
}
|
|
268
|
+
return {};
|
|
269
|
+
}
|
|
270
|
+
async function loadComponentsRegistry() {
|
|
271
|
+
const registryPath = path3.join(PATHS.components, "registry.json");
|
|
272
|
+
if (await fs3.pathExists(registryPath)) {
|
|
273
|
+
return await fs3.readJson(registryPath);
|
|
274
|
+
}
|
|
275
|
+
return {};
|
|
276
|
+
}
|
|
277
|
+
async function loadSnippetsRegistry() {
|
|
278
|
+
const registryPath = path3.join(PATHS.snippets, "registry.json");
|
|
279
|
+
if (await fs3.pathExists(registryPath)) {
|
|
280
|
+
return await fs3.readJson(registryPath);
|
|
281
|
+
}
|
|
282
|
+
return {};
|
|
283
|
+
}
|
|
284
|
+
function resolveComponentDependencies(componentName, registry, resolved = /* @__PURE__ */ new Set()) {
|
|
285
|
+
if (resolved.has(componentName)) return [];
|
|
286
|
+
const component = registry.components[componentName];
|
|
287
|
+
if (!component) {
|
|
288
|
+
throw new Error(`Component "${componentName}" not found in registry`);
|
|
289
|
+
}
|
|
290
|
+
resolved.add(componentName);
|
|
291
|
+
const deps = [];
|
|
292
|
+
for (const depName of component.registryDependencies) {
|
|
293
|
+
deps.push(...resolveComponentDependencies(depName, registry, resolved));
|
|
294
|
+
}
|
|
295
|
+
deps.push(component);
|
|
296
|
+
return deps;
|
|
297
|
+
}
|
|
298
|
+
function getComponentsByFramework(registry, framework) {
|
|
299
|
+
return Object.values(registry.components).filter(
|
|
300
|
+
(c) => c.framework === framework || c.framework === "universal" || framework === "nuxt" && c.framework === "vue"
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
function getSnippetsByFramework(registry, framework) {
|
|
304
|
+
return Object.values(registry.snippets).filter(
|
|
305
|
+
(s) => s.framework === framework || s.framework === "universal" || framework === "nuxt" && s.framework === "vue"
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
function groupByCategory(items) {
|
|
309
|
+
return items.reduce(
|
|
310
|
+
(acc, item) => {
|
|
311
|
+
if (!acc[item.category]) {
|
|
312
|
+
acc[item.category] = [];
|
|
313
|
+
}
|
|
314
|
+
acc[item.category].push(item);
|
|
315
|
+
return acc;
|
|
316
|
+
},
|
|
317
|
+
{}
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/utils/registry-install.ts
|
|
322
|
+
import path4 from "path";
|
|
323
|
+
function resolveComponentFramework(framework) {
|
|
324
|
+
if (framework === "nuxt" || framework === "vue" || framework === "react") {
|
|
325
|
+
return framework;
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
function findComponentKey(registry, component) {
|
|
330
|
+
const match = Object.entries(registry.components).find(
|
|
331
|
+
([, candidate]) => candidate === component
|
|
332
|
+
);
|
|
333
|
+
return match?.[0] ?? component.name.toLowerCase();
|
|
334
|
+
}
|
|
335
|
+
function buildComponentInstallPlan(input) {
|
|
336
|
+
const selectedComponents = input.componentKeys.flatMap(
|
|
337
|
+
(key) => resolveComponentDependencies(key, input.registry)
|
|
338
|
+
);
|
|
339
|
+
const components = [
|
|
340
|
+
...new Map(
|
|
341
|
+
selectedComponents.map((component) => [
|
|
342
|
+
findComponentKey(input.registry, component),
|
|
343
|
+
component
|
|
344
|
+
])
|
|
345
|
+
).values()
|
|
346
|
+
];
|
|
347
|
+
const filesToCopy = components.flatMap(
|
|
348
|
+
(component) => component.files.map(
|
|
349
|
+
(file) => resolveComponentFile({
|
|
350
|
+
component,
|
|
351
|
+
file,
|
|
352
|
+
framework: input.framework,
|
|
353
|
+
projectRoot: input.projectRoot,
|
|
354
|
+
targetBase: input.targetBase
|
|
355
|
+
})
|
|
356
|
+
)
|
|
357
|
+
);
|
|
358
|
+
return {
|
|
359
|
+
components,
|
|
360
|
+
dependencies: [...new Set(components.flatMap((component) => component.dependencies))],
|
|
361
|
+
devDependencies: [...new Set(components.flatMap((component) => component.devDependencies))],
|
|
362
|
+
filesToCopy
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function resolveComponentFile(input) {
|
|
366
|
+
const normalized = typeof input.file === "string" ? { path: input.file } : input.file;
|
|
367
|
+
const sourceFramework = input.component.framework === "universal" ? input.framework : input.component.framework;
|
|
368
|
+
const sourcePath = path4.join(PATHS.components, sourceFramework, normalized.path);
|
|
369
|
+
if (normalized.target) {
|
|
370
|
+
return {
|
|
371
|
+
src: sourcePath,
|
|
372
|
+
dest: path4.resolve(input.projectRoot, normalized.target)
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const targetBase = input.targetBase || DEFAULT_PATHS[input.framework]?.components || "src/components";
|
|
376
|
+
return {
|
|
377
|
+
src: sourcePath,
|
|
378
|
+
dest: path4.resolve(input.projectRoot, targetBase, path4.basename(normalized.path))
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/commands/init.ts
|
|
383
|
+
async function init(name, options) {
|
|
384
|
+
logger.br();
|
|
385
|
+
logger.title("StackOnward - Project Initializer");
|
|
386
|
+
logger.br();
|
|
387
|
+
let projectName = name;
|
|
388
|
+
if (!projectName) {
|
|
389
|
+
const response = await prompts({
|
|
390
|
+
type: "text",
|
|
391
|
+
name: "name",
|
|
392
|
+
message: "Project name:",
|
|
393
|
+
initial: "my-project",
|
|
394
|
+
validate: (value) => /^[a-z0-9-_]+$/i.test(value) || "Project name can only contain letters, numbers, - and _"
|
|
395
|
+
});
|
|
396
|
+
projectName = response.name;
|
|
397
|
+
}
|
|
398
|
+
if (!projectName) {
|
|
399
|
+
logger.error("Project name is required");
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
const targetDir = options.dir ? path5.resolve(options.dir) : path5.resolve(process.cwd(), projectName);
|
|
403
|
+
if (!await isDirEmpty(targetDir)) {
|
|
404
|
+
const { overwrite } = await prompts({
|
|
405
|
+
type: "confirm",
|
|
406
|
+
name: "overwrite",
|
|
407
|
+
message: `Directory ${path5.basename(targetDir)} is not empty. Overwrite?`,
|
|
408
|
+
initial: false
|
|
409
|
+
});
|
|
410
|
+
if (!overwrite) {
|
|
411
|
+
logger.info("Operation cancelled");
|
|
412
|
+
process.exit(0);
|
|
413
|
+
}
|
|
414
|
+
await fs4.emptyDir(targetDir);
|
|
415
|
+
}
|
|
416
|
+
let template = options.template;
|
|
417
|
+
if (!template || !TEMPLATES[template]) {
|
|
418
|
+
const templateChoices = Object.entries(TEMPLATES).map(([key, value]) => ({
|
|
419
|
+
title: value.name,
|
|
420
|
+
description: value.description,
|
|
421
|
+
value: key
|
|
422
|
+
}));
|
|
423
|
+
const response = await prompts({
|
|
424
|
+
type: "select",
|
|
425
|
+
name: "template",
|
|
426
|
+
message: "Select a template:",
|
|
427
|
+
choices: templateChoices
|
|
428
|
+
});
|
|
429
|
+
template = response.template;
|
|
430
|
+
}
|
|
431
|
+
if (!template) {
|
|
432
|
+
logger.error("Template selection is required");
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
const templateInfo = TEMPLATES[template];
|
|
436
|
+
const templatePath = path5.join(PATHS.templates, templateInfo.path);
|
|
437
|
+
if (!await fs4.pathExists(templatePath)) {
|
|
438
|
+
logger.error(`Template "${template}" not found at ${templatePath}`);
|
|
439
|
+
logger.info("Available templates:");
|
|
440
|
+
Object.entries(TEMPLATES).forEach(([key, val]) => {
|
|
441
|
+
logger.info(` - ${key}: ${val.description}`);
|
|
442
|
+
});
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
const { author } = await prompts({
|
|
446
|
+
type: "text",
|
|
447
|
+
name: "author",
|
|
448
|
+
message: "Author name (for LICENSE and package.json):",
|
|
449
|
+
initial: process.env.USER || "your-name"
|
|
450
|
+
});
|
|
451
|
+
const spinner = ora("Creating project...").start();
|
|
452
|
+
try {
|
|
453
|
+
await copyTemplate(templatePath, targetDir, {
|
|
454
|
+
projectName,
|
|
455
|
+
PROJECT_NAME: projectName,
|
|
456
|
+
author: author || "your-name",
|
|
457
|
+
year: (/* @__PURE__ */ new Date()).getFullYear().toString()
|
|
458
|
+
});
|
|
459
|
+
await installTemplateRegistryItems(templateInfo, targetDir);
|
|
460
|
+
spinner.succeed("Project created");
|
|
461
|
+
} catch (error) {
|
|
462
|
+
spinner.fail("Failed to create project");
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
if (!options.skipInstall) {
|
|
466
|
+
const { shouldInstall } = await prompts({
|
|
467
|
+
type: "confirm",
|
|
468
|
+
name: "shouldInstall",
|
|
469
|
+
message: "Install dependencies?",
|
|
470
|
+
initial: true
|
|
471
|
+
});
|
|
472
|
+
if (shouldInstall) {
|
|
473
|
+
const installSpinner = ora("Installing dependencies...").start();
|
|
474
|
+
try {
|
|
475
|
+
await runInstall(targetDir);
|
|
476
|
+
installSpinner.succeed("Dependencies installed");
|
|
477
|
+
} catch (error) {
|
|
478
|
+
installSpinner.fail("Failed to install dependencies");
|
|
479
|
+
logger.warn("You can install dependencies manually later");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (!options.skipGit) {
|
|
484
|
+
const { shouldGit } = await prompts({
|
|
485
|
+
type: "confirm",
|
|
486
|
+
name: "shouldGit",
|
|
487
|
+
message: "Initialize git repository?",
|
|
488
|
+
initial: true
|
|
489
|
+
});
|
|
490
|
+
if (shouldGit) {
|
|
491
|
+
const gitSpinner = ora("Initializing git...").start();
|
|
492
|
+
try {
|
|
493
|
+
await initGit(targetDir);
|
|
494
|
+
gitSpinner.succeed("Git repository initialized");
|
|
495
|
+
} catch (error) {
|
|
496
|
+
gitSpinner.fail("Failed to initialize git");
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
logger.br();
|
|
501
|
+
logger.success("Project ready!");
|
|
502
|
+
logger.br();
|
|
503
|
+
logger.info("Next steps:");
|
|
504
|
+
logger.info(` cd ${path5.relative(process.cwd(), targetDir)}`);
|
|
505
|
+
if (options.skipInstall) {
|
|
506
|
+
logger.info(" pnpm install");
|
|
507
|
+
}
|
|
508
|
+
logger.info(" pnpm dev");
|
|
509
|
+
logger.br();
|
|
510
|
+
}
|
|
511
|
+
async function installTemplateRegistryItems(templateInfo, targetDir) {
|
|
512
|
+
const registryItems = "registryItems" in templateInfo ? templateInfo.registryItems : void 0;
|
|
513
|
+
if (!registryItems?.length) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const registry = await loadRegistry();
|
|
517
|
+
const installPlan = buildComponentInstallPlan({
|
|
518
|
+
componentKeys: [...registryItems],
|
|
519
|
+
framework: "nuxt",
|
|
520
|
+
projectRoot: targetDir,
|
|
521
|
+
registry
|
|
522
|
+
});
|
|
523
|
+
await copyFiles(installPlan.filesToCopy, false);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/commands/add.ts
|
|
527
|
+
import path6 from "path";
|
|
528
|
+
import fs5 from "fs-extra";
|
|
529
|
+
import prompts2 from "prompts";
|
|
530
|
+
import ora2 from "ora";
|
|
531
|
+
|
|
532
|
+
// src/utils/nuxt-config.ts
|
|
533
|
+
function escapeRegExp(value) {
|
|
534
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
535
|
+
}
|
|
536
|
+
function patchNuxtConfigExtends(source, layer) {
|
|
537
|
+
const already = new RegExp(`extends\\s*:\\s*\\[[^\\]]*['"]${escapeRegExp(layer)}['"]`);
|
|
538
|
+
if (already.test(source)) return source;
|
|
539
|
+
const extendsArray = /extends\s*:\s*\[/;
|
|
540
|
+
if (extendsArray.test(source)) {
|
|
541
|
+
return source.replace(extendsArray, (match) => `${match}"${layer}", `);
|
|
542
|
+
}
|
|
543
|
+
const define = /defineNuxtConfig\(\{/;
|
|
544
|
+
if (define.test(source)) {
|
|
545
|
+
return source.replace(define, (match) => `${match}
|
|
546
|
+
extends: ["${layer}"],`);
|
|
547
|
+
}
|
|
548
|
+
throw new Error(
|
|
549
|
+
"Could not locate defineNuxtConfig in nuxt.config; add the layer to `extends` manually"
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/commands/add.ts
|
|
554
|
+
async function add(items, options) {
|
|
555
|
+
logger.br();
|
|
556
|
+
const project = await detectProject();
|
|
557
|
+
if (!project.packageJson) {
|
|
558
|
+
logger.error("No package.json found. Please run this command in a project directory.");
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
const registry = await loadRegistry();
|
|
562
|
+
const packageItems = items.filter((i) => registry.packages[i]).map((i) => registry.packages[i]);
|
|
563
|
+
if (packageItems.length > 0) {
|
|
564
|
+
await addPackages(packageItems, project);
|
|
565
|
+
}
|
|
566
|
+
const nonPackageItems = items.filter((i) => !registry.packages[i]);
|
|
567
|
+
if (items.length > 0 && nonPackageItems.length === 0) return;
|
|
568
|
+
const isComponent = options.component || !options.snippet && !options.component;
|
|
569
|
+
const isSnippet = options.snippet;
|
|
570
|
+
if (isComponent) {
|
|
571
|
+
await addComponents(nonPackageItems, options, project, registry);
|
|
572
|
+
} else if (isSnippet) {
|
|
573
|
+
await addSnippets(nonPackageItems, options, project, registry);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
async function addPackages(packages, project) {
|
|
577
|
+
for (const pkg of packages) {
|
|
578
|
+
const spec = pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name;
|
|
579
|
+
const spinner = ora2(`Installing ${pkg.name}...`).start();
|
|
580
|
+
try {
|
|
581
|
+
await installDependencies(project.root, [spec]);
|
|
582
|
+
spinner.succeed(`Installed ${pkg.name}`);
|
|
583
|
+
} catch (error) {
|
|
584
|
+
spinner.fail(`Failed to install ${pkg.name}`);
|
|
585
|
+
throw error;
|
|
586
|
+
}
|
|
587
|
+
if (pkg.type === "layer") {
|
|
588
|
+
await patchProjectNuxtConfig(project.root, pkg.name);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async function patchProjectNuxtConfig(root, layer) {
|
|
593
|
+
const candidates = ["nuxt.config.ts", "nuxt.config.js", "nuxt.config.mjs"];
|
|
594
|
+
for (const file of candidates) {
|
|
595
|
+
const configPath = path6.join(root, file);
|
|
596
|
+
if (!await fs5.pathExists(configPath)) continue;
|
|
597
|
+
const source = await fs5.readFile(configPath, "utf8");
|
|
598
|
+
try {
|
|
599
|
+
const patched = patchNuxtConfigExtends(source, layer);
|
|
600
|
+
if (patched === source) {
|
|
601
|
+
logger.info(`${layer} already in ${file} extends`);
|
|
602
|
+
} else {
|
|
603
|
+
await fs5.writeFile(configPath, patched);
|
|
604
|
+
logger.success(`Added ${layer} to ${file} extends`);
|
|
605
|
+
}
|
|
606
|
+
} catch {
|
|
607
|
+
logger.warn(`Could not auto-patch ${file}; add "${layer}" to extends manually`);
|
|
608
|
+
}
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
logger.warn(`No nuxt.config found; add "${layer}" to your nuxt.config extends manually`);
|
|
612
|
+
}
|
|
613
|
+
async function addComponents(items, options, project, registry) {
|
|
614
|
+
const framework = resolveComponentFramework(project.framework);
|
|
615
|
+
if (!framework) {
|
|
616
|
+
logger.error("Could not detect framework. Supported: Nuxt, Vue, React");
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
const availableComponents = getComponentsByFramework(registry, framework);
|
|
620
|
+
if (availableComponents.length === 0) {
|
|
621
|
+
logger.warn(`No components available for ${framework}`);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
let selectedComponents = [];
|
|
625
|
+
if (options.all) {
|
|
626
|
+
selectedComponents = availableComponents;
|
|
627
|
+
} else if (items.length > 0) {
|
|
628
|
+
for (const item of items) {
|
|
629
|
+
try {
|
|
630
|
+
const deps = resolveComponentDependencies(item, registry);
|
|
631
|
+
selectedComponents.push(...deps);
|
|
632
|
+
} catch (error) {
|
|
633
|
+
logger.error(error.message);
|
|
634
|
+
process.exit(1);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
} else {
|
|
638
|
+
const grouped = groupByCategory(availableComponents);
|
|
639
|
+
const choices = Object.entries(grouped).flatMap(([category, components]) => [
|
|
640
|
+
{ title: category, disabled: true, value: "" },
|
|
641
|
+
...components.map((c) => ({
|
|
642
|
+
title: ` ${c.name}`,
|
|
643
|
+
description: c.description,
|
|
644
|
+
value: findComponentKey(registry, c)
|
|
645
|
+
}))
|
|
646
|
+
]);
|
|
647
|
+
const response = await prompts2({
|
|
648
|
+
type: "multiselect",
|
|
649
|
+
name: "components",
|
|
650
|
+
message: "Select components to add:",
|
|
651
|
+
choices,
|
|
652
|
+
hint: "- Space to select. Return to submit"
|
|
653
|
+
});
|
|
654
|
+
if (!response.components || response.components.length === 0) {
|
|
655
|
+
logger.info("No components selected");
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
for (const name of response.components) {
|
|
659
|
+
const deps = resolveComponentDependencies(name, registry);
|
|
660
|
+
selectedComponents.push(...deps);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const componentKeys = [
|
|
664
|
+
...new Set(selectedComponents.map((component) => findComponentKey(registry, component)))
|
|
665
|
+
];
|
|
666
|
+
if (selectedComponents.length === 0) {
|
|
667
|
+
logger.info("No components to add");
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (!options.yes) {
|
|
671
|
+
logger.info("Components to add:");
|
|
672
|
+
selectedComponents.forEach((c) => logger.info(` - ${c.name}`));
|
|
673
|
+
logger.br();
|
|
674
|
+
const { confirm } = await prompts2({
|
|
675
|
+
type: "confirm",
|
|
676
|
+
name: "confirm",
|
|
677
|
+
message: "Proceed?",
|
|
678
|
+
initial: true
|
|
679
|
+
});
|
|
680
|
+
if (!confirm) {
|
|
681
|
+
logger.info("Cancelled");
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const installPlan = buildComponentInstallPlan({
|
|
686
|
+
componentKeys,
|
|
687
|
+
framework,
|
|
688
|
+
projectRoot: project.root,
|
|
689
|
+
registry,
|
|
690
|
+
targetBase: options.path
|
|
691
|
+
});
|
|
692
|
+
const spinner = ora2("Adding components...").start();
|
|
693
|
+
try {
|
|
694
|
+
const { copied, skipped } = await copyFiles(installPlan.filesToCopy, options.overwrite);
|
|
695
|
+
spinner.succeed(`Added ${copied.length} file(s)`);
|
|
696
|
+
if (skipped.length > 0) {
|
|
697
|
+
logger.warn(`Skipped ${skipped.length} existing file(s). Use --overwrite to replace.`);
|
|
698
|
+
}
|
|
699
|
+
} catch (error) {
|
|
700
|
+
spinner.fail("Failed to add components");
|
|
701
|
+
throw error;
|
|
702
|
+
}
|
|
703
|
+
const uniqueDeps = installPlan.dependencies;
|
|
704
|
+
const uniqueDevDeps = installPlan.devDependencies;
|
|
705
|
+
if (uniqueDeps.length > 0 || uniqueDevDeps.length > 0) {
|
|
706
|
+
const { shouldInstall } = options.yes ? { shouldInstall: true } : await prompts2({
|
|
707
|
+
type: "confirm",
|
|
708
|
+
name: "shouldInstall",
|
|
709
|
+
message: "Install required dependencies?",
|
|
710
|
+
initial: true
|
|
711
|
+
});
|
|
712
|
+
if (shouldInstall) {
|
|
713
|
+
const installSpinner = ora2("Installing dependencies...").start();
|
|
714
|
+
try {
|
|
715
|
+
if (uniqueDeps.length > 0) {
|
|
716
|
+
await installDependencies(project.root, uniqueDeps, false);
|
|
717
|
+
}
|
|
718
|
+
if (uniqueDevDeps.length > 0) {
|
|
719
|
+
await installDependencies(project.root, uniqueDevDeps, true);
|
|
720
|
+
}
|
|
721
|
+
installSpinner.succeed("Dependencies installed");
|
|
722
|
+
} catch (error) {
|
|
723
|
+
installSpinner.fail("Failed to install some dependencies");
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
logger.br();
|
|
728
|
+
logger.success("Done!");
|
|
729
|
+
}
|
|
730
|
+
async function addSnippets(items, options, project, registry) {
|
|
731
|
+
const framework = project.framework === "nuxt" ? "nuxt" : project.framework === "vue" ? "vue" : project.framework === "react" ? "react" : "node";
|
|
732
|
+
const availableSnippets = getSnippetsByFramework(registry, framework);
|
|
733
|
+
if (availableSnippets.length === 0) {
|
|
734
|
+
logger.warn(`No snippets available for ${framework}`);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
let selectedSnippets = [];
|
|
738
|
+
if (options.all) {
|
|
739
|
+
selectedSnippets = availableSnippets;
|
|
740
|
+
} else if (items.length > 0) {
|
|
741
|
+
for (const item of items) {
|
|
742
|
+
const snippet = registry.snippets[item];
|
|
743
|
+
if (!snippet) {
|
|
744
|
+
logger.error(`Snippet "${item}" not found`);
|
|
745
|
+
process.exit(1);
|
|
746
|
+
}
|
|
747
|
+
selectedSnippets.push(snippet);
|
|
748
|
+
}
|
|
749
|
+
} else {
|
|
750
|
+
const grouped = groupByCategory(availableSnippets);
|
|
751
|
+
const choices = Object.entries(grouped).flatMap(([category, snippets]) => [
|
|
752
|
+
{ title: category, disabled: true, value: "" },
|
|
753
|
+
...snippets.map((s) => ({
|
|
754
|
+
title: ` ${s.name}`,
|
|
755
|
+
description: s.description,
|
|
756
|
+
value: s.name.toLowerCase()
|
|
757
|
+
}))
|
|
758
|
+
]);
|
|
759
|
+
const response = await prompts2({
|
|
760
|
+
type: "multiselect",
|
|
761
|
+
name: "snippets",
|
|
762
|
+
message: "Select snippets to add:",
|
|
763
|
+
choices,
|
|
764
|
+
hint: "- Space to select. Return to submit"
|
|
765
|
+
});
|
|
766
|
+
if (!response.snippets || response.snippets.length === 0) {
|
|
767
|
+
logger.info("No snippets selected");
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
selectedSnippets = response.snippets.map((name) => registry.snippets[name]);
|
|
771
|
+
}
|
|
772
|
+
if (selectedSnippets.length === 0) {
|
|
773
|
+
logger.info("No snippets to add");
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (!options.yes) {
|
|
777
|
+
logger.info("Snippets to add:");
|
|
778
|
+
selectedSnippets.forEach((s) => logger.info(` - ${s.name}`));
|
|
779
|
+
logger.br();
|
|
780
|
+
const { confirm } = await prompts2({
|
|
781
|
+
type: "confirm",
|
|
782
|
+
name: "confirm",
|
|
783
|
+
message: "Proceed?",
|
|
784
|
+
initial: true
|
|
785
|
+
});
|
|
786
|
+
if (!confirm) {
|
|
787
|
+
logger.info("Cancelled");
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const targetBase = options.path || DEFAULT_PATHS[framework]?.utils || "src/utils";
|
|
792
|
+
const targetDir = path6.resolve(project.root, targetBase);
|
|
793
|
+
const spinner = ora2("Adding snippets...").start();
|
|
794
|
+
const filesToCopy = [];
|
|
795
|
+
for (const snippet of selectedSnippets) {
|
|
796
|
+
const snippetFramework = snippet.framework === "universal" ? "node" : snippet.framework;
|
|
797
|
+
const snippetDir = path6.join(PATHS.snippets, snippetFramework);
|
|
798
|
+
for (const file of snippet.files) {
|
|
799
|
+
const normalized = typeof file === "string" ? { path: file } : file;
|
|
800
|
+
filesToCopy.push({
|
|
801
|
+
src: path6.join(snippetDir, normalized.path),
|
|
802
|
+
dest: normalized.target ? path6.resolve(project.root, normalized.target) : path6.join(targetDir, path6.basename(normalized.path))
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
const { copied, skipped } = await copyFiles(filesToCopy, options.overwrite);
|
|
808
|
+
spinner.succeed(`Added ${copied.length} file(s)`);
|
|
809
|
+
if (skipped.length > 0) {
|
|
810
|
+
logger.warn(`Skipped ${skipped.length} existing file(s)`);
|
|
811
|
+
}
|
|
812
|
+
} catch (error) {
|
|
813
|
+
spinner.fail("Failed to add snippets");
|
|
814
|
+
throw error;
|
|
815
|
+
}
|
|
816
|
+
logger.br();
|
|
817
|
+
logger.success("Done!");
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/commands/list.ts
|
|
821
|
+
async function list(type, options) {
|
|
822
|
+
logger.br();
|
|
823
|
+
const showAll = !options.templates && !options.components && !options.snippets && !type;
|
|
824
|
+
const showTemplates = options.templates || type === "templates" || showAll;
|
|
825
|
+
const showComponents = options.components || type === "components" || showAll;
|
|
826
|
+
const showSnippets = options.snippets || type === "snippets" || showAll;
|
|
827
|
+
if (showTemplates) {
|
|
828
|
+
listTemplates();
|
|
829
|
+
}
|
|
830
|
+
if (showComponents) {
|
|
831
|
+
await listComponents();
|
|
832
|
+
}
|
|
833
|
+
if (showSnippets) {
|
|
834
|
+
await listSnippets();
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function listTemplates() {
|
|
838
|
+
logger.title("Templates");
|
|
839
|
+
logger.br();
|
|
840
|
+
const entries = Object.entries(TEMPLATES);
|
|
841
|
+
if (entries.length === 0) {
|
|
842
|
+
logger.info(" No templates available");
|
|
843
|
+
} else {
|
|
844
|
+
for (const [key, template] of entries) {
|
|
845
|
+
logger.info(` ${key}`);
|
|
846
|
+
logger.subtitle(` ${template.description}`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
logger.br();
|
|
850
|
+
logger.subtitle(" Usage: stackonward init --template <name>");
|
|
851
|
+
logger.br();
|
|
852
|
+
}
|
|
853
|
+
async function listComponents() {
|
|
854
|
+
logger.title("Components");
|
|
855
|
+
logger.br();
|
|
856
|
+
const registry = await loadRegistry();
|
|
857
|
+
const components = Object.values(registry.components);
|
|
858
|
+
if (components.length === 0) {
|
|
859
|
+
logger.info(" No components available");
|
|
860
|
+
logger.br();
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const byFramework = {};
|
|
864
|
+
for (const component of components) {
|
|
865
|
+
const fw = component.framework;
|
|
866
|
+
if (!byFramework[fw]) byFramework[fw] = [];
|
|
867
|
+
byFramework[fw].push(component);
|
|
868
|
+
}
|
|
869
|
+
for (const [framework, fwComponents] of Object.entries(byFramework)) {
|
|
870
|
+
logger.info(` [${framework}]`);
|
|
871
|
+
const grouped = groupByCategory(fwComponents);
|
|
872
|
+
for (const [category, catComponents] of Object.entries(grouped)) {
|
|
873
|
+
logger.subtitle(` ${category}/`);
|
|
874
|
+
for (const component of catComponents) {
|
|
875
|
+
logger.info(` - ${component.name.toLowerCase()}: ${component.description}`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
logger.br();
|
|
879
|
+
}
|
|
880
|
+
logger.subtitle(" Usage: stackonward add <component-name>");
|
|
881
|
+
logger.br();
|
|
882
|
+
}
|
|
883
|
+
async function listSnippets() {
|
|
884
|
+
logger.title("Snippets");
|
|
885
|
+
logger.br();
|
|
886
|
+
const registry = await loadRegistry();
|
|
887
|
+
const snippets = Object.values(registry.snippets);
|
|
888
|
+
if (snippets.length === 0) {
|
|
889
|
+
logger.info(" No snippets available");
|
|
890
|
+
logger.br();
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const byFramework = {};
|
|
894
|
+
for (const snippet of snippets) {
|
|
895
|
+
const fw = snippet.framework;
|
|
896
|
+
if (!byFramework[fw]) byFramework[fw] = [];
|
|
897
|
+
byFramework[fw].push(snippet);
|
|
898
|
+
}
|
|
899
|
+
for (const [framework, fwSnippets] of Object.entries(byFramework)) {
|
|
900
|
+
logger.info(` [${framework}]`);
|
|
901
|
+
const grouped = groupByCategory(fwSnippets);
|
|
902
|
+
for (const [category, catSnippets] of Object.entries(grouped)) {
|
|
903
|
+
logger.subtitle(` ${category}/`);
|
|
904
|
+
for (const snippet of catSnippets) {
|
|
905
|
+
logger.info(` - ${snippet.name.toLowerCase()}: ${snippet.description}`);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
logger.br();
|
|
909
|
+
}
|
|
910
|
+
logger.subtitle(" Usage: stackonward add -s <snippet-name>");
|
|
911
|
+
logger.br();
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/commands/update.ts
|
|
915
|
+
import path7 from "path";
|
|
916
|
+
import fs6 from "fs-extra";
|
|
917
|
+
import prompts3 from "prompts";
|
|
918
|
+
import ora3 from "ora";
|
|
919
|
+
var CONFIG_FILES = [
|
|
920
|
+
{ name: "ESLint", file: "eslint.config.js", source: "eslint-config" },
|
|
921
|
+
{ name: "Prettier", file: ".prettierrc", source: "prettier-config" },
|
|
922
|
+
{ name: "EditorConfig", file: ".editorconfig", source: "base" },
|
|
923
|
+
{ name: "Commitlint", file: "commitlint.config.js", source: "base" },
|
|
924
|
+
{ name: "Lint-staged", file: ".lintstagedrc.json", source: "base" }
|
|
925
|
+
];
|
|
926
|
+
async function update(items, options) {
|
|
927
|
+
logger.br();
|
|
928
|
+
const project = await detectProject();
|
|
929
|
+
if (!project.packageJson) {
|
|
930
|
+
logger.error("No package.json found. Please run this command in a project directory.");
|
|
931
|
+
process.exit(1);
|
|
932
|
+
}
|
|
933
|
+
if (options.config || options.all) {
|
|
934
|
+
await updateConfigs(project, items);
|
|
935
|
+
} else {
|
|
936
|
+
await updateConfigs(project, items);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
async function updateConfigs(project, specificConfigs) {
|
|
940
|
+
logger.title("Update Configuration Files");
|
|
941
|
+
logger.br();
|
|
942
|
+
let configsToUpdate = [...CONFIG_FILES];
|
|
943
|
+
if (specificConfigs.length > 0) {
|
|
944
|
+
configsToUpdate = CONFIG_FILES.filter(
|
|
945
|
+
(c) => specificConfigs.some(
|
|
946
|
+
(s) => c.name.toLowerCase().includes(s.toLowerCase()) || c.file.includes(s)
|
|
947
|
+
)
|
|
948
|
+
);
|
|
949
|
+
if (configsToUpdate.length === 0) {
|
|
950
|
+
logger.error("No matching config files found");
|
|
951
|
+
logger.info("Available configs:");
|
|
952
|
+
CONFIG_FILES.forEach((c) => logger.info(` - ${c.name} (${c.file})`));
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
} else {
|
|
956
|
+
const choices = CONFIG_FILES.map((c) => ({
|
|
957
|
+
title: c.name,
|
|
958
|
+
description: c.file,
|
|
959
|
+
value: c.file,
|
|
960
|
+
selected: false
|
|
961
|
+
}));
|
|
962
|
+
const response = await prompts3({
|
|
963
|
+
type: "multiselect",
|
|
964
|
+
name: "configs",
|
|
965
|
+
message: "Select configs to update:",
|
|
966
|
+
choices,
|
|
967
|
+
hint: "- Space to select. Return to submit"
|
|
968
|
+
});
|
|
969
|
+
if (!response.configs || response.configs.length === 0) {
|
|
970
|
+
logger.info("No configs selected");
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
configsToUpdate = CONFIG_FILES.filter((c) => response.configs.includes(c.file));
|
|
974
|
+
}
|
|
975
|
+
const existingConfigs = [];
|
|
976
|
+
const newConfigs = [];
|
|
977
|
+
for (const config of configsToUpdate) {
|
|
978
|
+
const targetPath = path7.join(project.root, config.file);
|
|
979
|
+
if (await fs6.pathExists(targetPath)) {
|
|
980
|
+
existingConfigs.push(config);
|
|
981
|
+
} else {
|
|
982
|
+
newConfigs.push(config);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (existingConfigs.length > 0) {
|
|
986
|
+
logger.warn("The following files will be overwritten:");
|
|
987
|
+
existingConfigs.forEach((c) => logger.warn(` - ${c.file}`));
|
|
988
|
+
const { confirm } = await prompts3({
|
|
989
|
+
type: "confirm",
|
|
990
|
+
name: "confirm",
|
|
991
|
+
message: "Continue?",
|
|
992
|
+
initial: false
|
|
993
|
+
});
|
|
994
|
+
if (!confirm) {
|
|
995
|
+
logger.info("Cancelled");
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
const spinner = ora3("Updating configs...").start();
|
|
1000
|
+
const filesToCopy = [];
|
|
1001
|
+
for (const config of configsToUpdate) {
|
|
1002
|
+
const sourcePaths = [
|
|
1003
|
+
path7.join(PATHS.configs, config.source, config.file),
|
|
1004
|
+
path7.join(PATHS.templates, "base", config.file)
|
|
1005
|
+
];
|
|
1006
|
+
let sourcePath = null;
|
|
1007
|
+
for (const sp of sourcePaths) {
|
|
1008
|
+
if (await fs6.pathExists(sp)) {
|
|
1009
|
+
sourcePath = sp;
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (sourcePath) {
|
|
1014
|
+
filesToCopy.push({
|
|
1015
|
+
src: sourcePath,
|
|
1016
|
+
dest: path7.join(project.root, config.file)
|
|
1017
|
+
});
|
|
1018
|
+
} else {
|
|
1019
|
+
logger.warn(`Source not found for ${config.file}`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
try {
|
|
1023
|
+
const { copied } = await copyFiles(filesToCopy, true);
|
|
1024
|
+
spinner.succeed(`Updated ${copied.length} config file(s)`);
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
spinner.fail("Failed to update configs");
|
|
1027
|
+
throw error;
|
|
1028
|
+
}
|
|
1029
|
+
logger.br();
|
|
1030
|
+
logger.success("Done!");
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/index.ts
|
|
1034
|
+
var program = new Command();
|
|
1035
|
+
program.name("stackonward").description("Create projects and install reusable components, snippets, and configurations").version("0.0.1");
|
|
1036
|
+
program.command("init [name]").description("Create a new project from template").option("-t, --template <template>", "Template to use").option("-d, --dir <directory>", "Target directory").option("--skip-install", "Skip dependency installation").option("--skip-git", "Skip git initialization").action(init);
|
|
1037
|
+
program.command("add [items...]").description("Add components or snippets to your project").option("-c, --component", "Add component(s)").option("-s, --snippet", "Add snippet(s)").option("-a, --all", "Add all items in category").option("-y, --yes", "Skip confirmation prompts").option("-o, --overwrite", "Overwrite existing files").option("-p, --path <path>", "Custom install path").action(add);
|
|
1038
|
+
program.command("list [type]").description("List available templates, components, or snippets").option("-t, --templates", "List templates").option("-c, --components", "List components").option("-s, --snippets", "List snippets").action(list);
|
|
1039
|
+
program.command("update [items...]").description("Update configs or components").option("-c, --config", "Update config files").option("--all", "Update all").action(update);
|
|
1040
|
+
program.parse();
|
|
1041
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/init.ts","../src/utils/constants.ts","../src/utils/logger.ts","../src/utils/fs.ts","../src/utils/project.ts","../src/utils/registry.ts","../src/utils/registry-install.ts","../src/commands/add.ts","../src/utils/nuxt-config.ts","../src/commands/list.ts","../src/commands/update.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { init } from \"./commands/init.js\";\nimport { add } from \"./commands/add.js\";\nimport { list } from \"./commands/list.js\";\nimport { update } from \"./commands/update.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"stackonward\")\n .description(\"Create projects and install reusable components, snippets, and configurations\")\n .version(\"0.0.1\");\n\nprogram\n .command(\"init [name]\")\n .description(\"Create a new project from template\")\n .option(\"-t, --template <template>\", \"Template to use\")\n .option(\"-d, --dir <directory>\", \"Target directory\")\n .option(\"--skip-install\", \"Skip dependency installation\")\n .option(\"--skip-git\", \"Skip git initialization\")\n .action(init);\n\nprogram\n .command(\"add [items...]\")\n .description(\"Add components or snippets to your project\")\n .option(\"-c, --component\", \"Add component(s)\")\n .option(\"-s, --snippet\", \"Add snippet(s)\")\n .option(\"-a, --all\", \"Add all items in category\")\n .option(\"-y, --yes\", \"Skip confirmation prompts\")\n .option(\"-o, --overwrite\", \"Overwrite existing files\")\n .option(\"-p, --path <path>\", \"Custom install path\")\n .action(add);\n\nprogram\n .command(\"list [type]\")\n .description(\"List available templates, components, or snippets\")\n .option(\"-t, --templates\", \"List templates\")\n .option(\"-c, --components\", \"List components\")\n .option(\"-s, --snippets\", \"List snippets\")\n .action(list);\n\nprogram\n .command(\"update [items...]\")\n .description(\"Update configs or components\")\n .option(\"-c, --config\", \"Update config files\")\n .option(\"--all\", \"Update all\")\n .action(update);\n\nprogram.parse();\n","import path from \"path\";\nimport fs from \"fs-extra\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport { TEMPLATES, PATHS, type TemplateName } from \"../utils/constants.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { copyFiles, copyTemplate, isDirEmpty } from \"../utils/fs.js\";\nimport { runInstall, initGit } from \"../utils/project.js\";\nimport { loadRegistry } from \"../utils/registry.js\";\nimport { buildComponentInstallPlan } from \"../utils/registry-install.js\";\n\ninterface InitOptions {\n template?: string;\n dir?: string;\n skipInstall?: boolean;\n skipGit?: boolean;\n}\n\nexport async function init(name: string | undefined, options: InitOptions): Promise<void> {\n logger.br();\n logger.title(\"StackOnward - Project Initializer\");\n logger.br();\n\n // Step 1: Get project name\n let projectName = name;\n if (!projectName) {\n const response = await prompts({\n type: \"text\",\n name: \"name\",\n message: \"Project name:\",\n initial: \"my-project\",\n validate: (value: string) =>\n /^[a-z0-9-_]+$/i.test(value) || \"Project name can only contain letters, numbers, - and _\",\n });\n projectName = response.name;\n }\n\n if (!projectName) {\n logger.error(\"Project name is required\");\n process.exit(1);\n }\n\n // Step 2: Get target directory\n const targetDir = options.dir\n ? path.resolve(options.dir)\n : path.resolve(process.cwd(), projectName);\n\n // Check if directory exists and is not empty\n if (!(await isDirEmpty(targetDir))) {\n const { overwrite } = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Directory ${path.basename(targetDir)} is not empty. Overwrite?`,\n initial: false,\n });\n\n if (!overwrite) {\n logger.info(\"Operation cancelled\");\n process.exit(0);\n }\n\n await fs.emptyDir(targetDir);\n }\n\n // Step 3: Select template\n let template = options.template as TemplateName | undefined;\n if (!template || !TEMPLATES[template]) {\n const templateChoices = Object.entries(TEMPLATES).map(([key, value]) => ({\n title: value.name,\n description: value.description,\n value: key,\n }));\n\n const response = await prompts({\n type: \"select\",\n name: \"template\",\n message: \"Select a template:\",\n choices: templateChoices,\n });\n\n template = response.template;\n }\n\n if (!template) {\n logger.error(\"Template selection is required\");\n process.exit(1);\n }\n\n const templateInfo = TEMPLATES[template];\n const templatePath = path.join(PATHS.templates, templateInfo.path);\n\n // Check if template exists\n if (!(await fs.pathExists(templatePath))) {\n logger.error(`Template \"${template}\" not found at ${templatePath}`);\n logger.info(\"Available templates:\");\n Object.entries(TEMPLATES).forEach(([key, val]) => {\n logger.info(` - ${key}: ${val.description}`);\n });\n process.exit(1);\n }\n\n // Step 4: Get author name (for LICENSE, package.json, etc.)\n const { author } = await prompts({\n type: \"text\",\n name: \"author\",\n message: \"Author name (for LICENSE and package.json):\",\n initial: process.env.USER || \"your-name\",\n });\n\n // Step 5: Copy template\n const spinner = ora(\"Creating project...\").start();\n\n try {\n await copyTemplate(templatePath, targetDir, {\n projectName,\n PROJECT_NAME: projectName,\n author: author || \"your-name\",\n year: new Date().getFullYear().toString(),\n });\n await installTemplateRegistryItems(templateInfo, targetDir);\n\n spinner.succeed(\"Project created\");\n } catch (error) {\n spinner.fail(\"Failed to create project\");\n throw error;\n }\n\n // Step 6: Install dependencies\n if (!options.skipInstall) {\n const { shouldInstall } = await prompts({\n type: \"confirm\",\n name: \"shouldInstall\",\n message: \"Install dependencies?\",\n initial: true,\n });\n\n if (shouldInstall) {\n const installSpinner = ora(\"Installing dependencies...\").start();\n try {\n await runInstall(targetDir);\n installSpinner.succeed(\"Dependencies installed\");\n } catch (error) {\n installSpinner.fail(\"Failed to install dependencies\");\n logger.warn(\"You can install dependencies manually later\");\n }\n }\n }\n\n // Step 7: Initialize git\n if (!options.skipGit) {\n const { shouldGit } = await prompts({\n type: \"confirm\",\n name: \"shouldGit\",\n message: \"Initialize git repository?\",\n initial: true,\n });\n\n if (shouldGit) {\n const gitSpinner = ora(\"Initializing git...\").start();\n try {\n await initGit(targetDir);\n gitSpinner.succeed(\"Git repository initialized\");\n } catch (error) {\n gitSpinner.fail(\"Failed to initialize git\");\n }\n }\n }\n\n // Done!\n logger.br();\n logger.success(\"Project ready!\");\n logger.br();\n logger.info(\"Next steps:\");\n logger.info(` cd ${path.relative(process.cwd(), targetDir)}`);\n if (options.skipInstall) {\n logger.info(\" pnpm install\");\n }\n logger.info(\" pnpm dev\");\n logger.br();\n}\n\nasync function installTemplateRegistryItems(\n templateInfo: (typeof TEMPLATES)[TemplateName],\n targetDir: string,\n): Promise<void> {\n const registryItems = \"registryItems\" in templateInfo ? templateInfo.registryItems : undefined;\n if (!registryItems?.length) {\n return;\n }\n\n const registry = await loadRegistry();\n const installPlan = buildComponentInstallPlan({\n componentKeys: [...registryItems],\n framework: \"nuxt\",\n projectRoot: targetDir,\n registry,\n });\n\n await copyFiles(installPlan.filesToCopy, false);\n}\n","import { fileURLToPath } from \"url\";\nimport { dirname, resolve } from \"path\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Root of the StackOnward workspace where templates, components, and registries are stored.\n// From dist/index.js: dist -> cli -> packages -> repository root.\nexport const STACKONWARD_ROOT = resolve(__dirname, \"../../..\");\n\n// Paths to different resource directories\nexport const PATHS = {\n templates: resolve(STACKONWARD_ROOT, \"packages/templates\"),\n components: resolve(STACKONWARD_ROOT, \"packages/components\"),\n snippets: resolve(STACKONWARD_ROOT, \"packages/snippets\"),\n configs: resolve(STACKONWARD_ROOT, \"packages/configs\"),\n packagesRegistry: resolve(STACKONWARD_ROOT, \"packages/registry.json\"),\n} as const;\n\n// Available templates\nexport const TEMPLATES = {\n \"mcp-server\": {\n name: \"MCP Server\",\n description: \"Model Context Protocol server template\",\n path: \"mcp-server\",\n },\n \"node-cli\": {\n name: \"Node CLI\",\n description: \"Node.js CLI tool template\",\n path: \"node-cli\",\n },\n nuxt: {\n name: \"Nuxt\",\n description: \"Nuxt 4 product-site starter with editable registry-installed source\",\n path: \"nuxt\",\n registryItems: [\"nuxt-site-source\"],\n },\n \"vue-spa\": {\n name: \"Vue SPA\",\n description: \"Vue 3 Single Page Application template\",\n path: \"vue-spa\",\n },\n next: {\n name: \"Next.js\",\n description: \"Next.js React application template\",\n path: \"next\",\n },\n} as const;\n\nexport type TemplateName = keyof typeof TEMPLATES;\n\n// Framework detection patterns\nexport const FRAMEWORK_PATTERNS = {\n nuxt: [\"nuxt\"],\n vue: [\"vue\", \"nuxt\"],\n react: [\"react\", \"next\"],\n node: [\"node\", \"express\", \"fastify\", \"mcp\"],\n} as const;\n\n// Default file paths for components\nexport const DEFAULT_PATHS = {\n nuxt: {\n components: \"app/components\",\n composables: \"app/composables\",\n utils: \"app/utils\",\n },\n vue: {\n components: \"src/components\",\n composables: \"src/composables\",\n utils: \"src/utils\",\n },\n react: {\n components: \"src/components\",\n hooks: \"src/hooks\",\n utils: \"src/utils\",\n },\n node: {\n utils: \"src/utils\",\n services: \"src/services\",\n },\n} as const;\n","import { blue, green, red, yellow, cyan, bold, dim } from \"kolorist\";\n\nexport const logger = {\n info: (msg: string) => console.log(blue(\"info\"), msg),\n success: (msg: string) => console.log(green(\"success\"), msg),\n warn: (msg: string) => console.log(yellow(\"warn\"), msg),\n error: (msg: string) => console.log(red(\"error\"), msg),\n\n // Styled outputs\n title: (msg: string) => console.log(bold(cyan(msg))),\n subtitle: (msg: string) => console.log(dim(msg)),\n step: (step: number, total: number, msg: string) => console.log(dim(`[${step}/${total}]`), msg),\n\n // Blank line\n br: () => console.log(),\n\n // Box output for important messages\n box: (title: string, content: string[]) => {\n const maxLen = Math.max(title.length, ...content.map((c) => c.length));\n const border = \"─\".repeat(maxLen + 2);\n\n console.log(`┌${border}┐`);\n console.log(`│ ${bold(title.padEnd(maxLen))} │`);\n console.log(`├${border}┤`);\n content.forEach((line) => {\n console.log(`│ ${line.padEnd(maxLen)} │`);\n });\n console.log(`└${border}┘`);\n },\n};\n","import fs from \"fs-extra\";\nimport path from \"path\";\nimport { glob } from \"glob\";\n\n/**\n * Copy directory with template variable replacement\n */\nexport async function copyTemplate(\n src: string,\n dest: string,\n variables: Record<string, string> = {},\n): Promise<void> {\n await fs.ensureDir(dest);\n\n const files = await glob(\"**/*\", {\n cwd: src,\n dot: true,\n nodir: true,\n ignore: [\"**/node_modules/**\", \"**/.git/**\"],\n });\n\n for (const file of files) {\n const srcPath = path.join(src, file);\n let destPath = path.join(dest, file);\n\n // Replace template variables in filename\n for (const [key, value] of Object.entries(variables)) {\n destPath = destPath.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, \"g\"), value);\n }\n\n await fs.ensureDir(path.dirname(destPath));\n\n // Check if file is text and should be processed\n if (isTextFile(file)) {\n let content = await fs.readFile(srcPath, \"utf-8\");\n\n // Replace template variables in content\n for (const [key, value] of Object.entries(variables)) {\n content = content.replace(new RegExp(`\\\\{\\\\{${key}\\\\}\\\\}`, \"g\"), value);\n }\n\n await fs.writeFile(destPath, content);\n } else {\n await fs.copy(srcPath, destPath);\n }\n }\n}\n\n/**\n * Copy specific files to destination\n */\nexport async function copyFiles(\n files: { src: string; dest: string }[],\n overwrite = false,\n): Promise<{ copied: string[]; skipped: string[] }> {\n const copied: string[] = [];\n const skipped: string[] = [];\n\n for (const { src, dest } of files) {\n if (!overwrite && (await fs.pathExists(dest))) {\n skipped.push(dest);\n continue;\n }\n\n await fs.ensureDir(path.dirname(dest));\n await fs.copy(src, dest);\n copied.push(dest);\n }\n\n return { copied, skipped };\n}\n\n/**\n * Check if file is a text file that should be processed for template variables\n */\nfunction isTextFile(filename: string): boolean {\n const textExtensions = [\n \".ts\",\n \".tsx\",\n \".js\",\n \".jsx\",\n \".vue\",\n \".json\",\n \".md\",\n \".yml\",\n \".yaml\",\n \".html\",\n \".css\",\n \".scss\",\n \".less\",\n \".txt\",\n \".env\",\n \".env.example\",\n \".gitignore\",\n \".prettierrc\",\n \".eslintrc\",\n \".editorconfig\",\n ];\n\n const ext = path.extname(filename).toLowerCase();\n const basename = path.basename(filename);\n\n return (\n textExtensions.includes(ext) || basename.startsWith(\".\") || !ext // Files without extension are often config files\n );\n}\n\n/**\n * Read and parse JSON file\n */\nexport async function readJson<T>(filePath: string): Promise<T | null> {\n try {\n return await fs.readJson(filePath);\n } catch {\n return null;\n }\n}\n\n/**\n * Write JSON file with formatting\n */\nexport async function writeJson(filePath: string, data: unknown): Promise<void> {\n await fs.writeJson(filePath, data, { spaces: 2 });\n}\n\n/**\n * Check if directory is empty\n */\nexport async function isDirEmpty(dir: string): Promise<boolean> {\n if (!(await fs.pathExists(dir))) return true;\n const files = await fs.readdir(dir);\n return files.length === 0;\n}\n\n/**\n * Get relative path from cwd\n */\nexport function relativePath(absolutePath: string): string {\n return path.relative(process.cwd(), absolutePath);\n}\n","import fs from \"fs-extra\";\nimport path from \"path\";\nimport { execa } from \"execa\";\nimport { type FRAMEWORK_PATTERNS } from \"./constants.js\";\n\nexport interface ProjectInfo {\n name: string;\n root: string;\n packageJson: Record<string, unknown> | null;\n framework: keyof typeof FRAMEWORK_PATTERNS | null;\n hasTypeScript: boolean;\n packageManager: \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n}\n\n/**\n * Detect project information from current directory\n */\nexport async function detectProject(cwd = process.cwd()): Promise<ProjectInfo> {\n const packageJsonPath = path.join(cwd, \"package.json\");\n const packageJson = (await fs.pathExists(packageJsonPath))\n ? await fs.readJson(packageJsonPath)\n : null;\n\n return {\n name: packageJson?.name || path.basename(cwd),\n root: cwd,\n packageJson,\n framework: detectFramework(packageJson),\n hasTypeScript: await hasTypeScript(cwd),\n packageManager: await detectPackageManager(cwd),\n };\n}\n\n/**\n * Detect framework from package.json dependencies\n */\nfunction detectFramework(\n packageJson: Record<string, unknown> | null,\n): keyof typeof FRAMEWORK_PATTERNS | null {\n if (!packageJson) return null;\n\n const deps = {\n ...(packageJson.dependencies as Record<string, string> | undefined),\n ...(packageJson.devDependencies as Record<string, string> | undefined),\n };\n\n if (deps.nuxt) return \"nuxt\";\n if (deps.vue) return \"vue\";\n if (deps.next || deps.react) return \"react\";\n if (deps.express || deps.fastify || deps[\"@modelcontextprotocol/sdk\"]) return \"node\";\n\n return null;\n}\n\n/**\n * Check if project uses TypeScript\n */\nasync function hasTypeScript(cwd: string): Promise<boolean> {\n return (\n (await fs.pathExists(path.join(cwd, \"tsconfig.json\"))) ||\n (await fs.pathExists(path.join(cwd, \"tsconfig.base.json\")))\n );\n}\n\n/**\n * Detect package manager from lock files\n */\nasync function detectPackageManager(cwd: string): Promise<\"npm\" | \"pnpm\" | \"yarn\" | \"bun\"> {\n if (await fs.pathExists(path.join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (await fs.pathExists(path.join(cwd, \"yarn.lock\"))) return \"yarn\";\n if (await fs.pathExists(path.join(cwd, \"bun.lockb\"))) return \"bun\";\n return \"npm\";\n}\n\n/**\n * Install dependencies using detected package manager\n */\nexport async function installDependencies(\n cwd: string,\n deps: string[],\n isDev = false,\n): Promise<void> {\n const pm = await detectPackageManager(cwd);\n\n const args: string[] = [];\n\n switch (pm) {\n case \"pnpm\":\n args.push(\"add\", ...deps);\n if (isDev) args.push(\"-D\");\n break;\n case \"yarn\":\n args.push(\"add\", ...deps);\n if (isDev) args.push(\"-D\");\n break;\n case \"bun\":\n args.push(\"add\", ...deps);\n if (isDev) args.push(\"-d\");\n break;\n default:\n args.push(\"install\", ...deps);\n if (isDev) args.push(\"--save-dev\");\n }\n\n await execa(pm, args, { cwd, stdio: \"inherit\" });\n}\n\n/**\n * Run package manager install\n */\nexport async function runInstall(cwd: string): Promise<void> {\n const pm = await detectPackageManager(cwd);\n await execa(pm, [\"install\"], { cwd, stdio: \"inherit\" });\n}\n\n/**\n * Initialize git repository\n */\nexport async function initGit(cwd: string): Promise<void> {\n await execa(\"git\", [\"init\"], { cwd });\n await execa(\"git\", [\"add\", \"-A\"], { cwd });\n await execa(\"git\", [\"commit\", \"-m\", \"chore: initial commit\"], { cwd });\n}\n","import fs from \"fs-extra\";\nimport path from \"path\";\nimport { PATHS } from \"./constants.js\";\n\nexport interface ComponentMeta {\n name: string;\n description: string;\n files: RegistryFile[];\n dependencies: string[];\n devDependencies: string[];\n registryDependencies: string[];\n framework: \"nuxt\" | \"vue\" | \"react\" | \"universal\";\n category: string;\n}\n\nexport interface SnippetMeta {\n name: string;\n description: string;\n files: RegistryFile[];\n dependencies: string[];\n devDependencies: string[];\n framework: \"nuxt\" | \"vue\" | \"react\" | \"node\" | \"universal\";\n category: string;\n}\n\nexport type RegistryFile =\n | string\n | {\n path: string;\n target?: string;\n };\n\n/** 可安装的包:npm 普通依赖,或 Nuxt layer(装包 + patch extends) */\nexport interface PackageMeta {\n name: string;\n description: string;\n type: \"npm\" | \"layer\";\n version?: string;\n}\n\nexport interface Registry {\n components: Record<string, ComponentMeta>;\n snippets: Record<string, SnippetMeta>;\n packages: Record<string, PackageMeta>;\n}\n\nlet registryCache: Registry | null = null;\n\n/**\n * Load the component/snippet registry\n */\nexport async function loadRegistry(): Promise<Registry> {\n if (registryCache) return registryCache;\n\n const componentsRegistry = await loadComponentsRegistry();\n const snippetsRegistry = await loadSnippetsRegistry();\n const packagesRegistry = await loadPackagesRegistry();\n\n registryCache = {\n components: componentsRegistry,\n snippets: snippetsRegistry,\n packages: packagesRegistry,\n };\n\n return registryCache;\n}\n\nasync function loadPackagesRegistry(): Promise<Record<string, PackageMeta>> {\n if (await fs.pathExists(PATHS.packagesRegistry)) {\n return await fs.readJson(PATHS.packagesRegistry);\n }\n return {};\n}\n\nasync function loadComponentsRegistry(): Promise<Record<string, ComponentMeta>> {\n const registryPath = path.join(PATHS.components, \"registry.json\");\n if (await fs.pathExists(registryPath)) {\n return await fs.readJson(registryPath);\n }\n return {};\n}\n\nasync function loadSnippetsRegistry(): Promise<Record<string, SnippetMeta>> {\n const registryPath = path.join(PATHS.snippets, \"registry.json\");\n if (await fs.pathExists(registryPath)) {\n return await fs.readJson(registryPath);\n }\n return {};\n}\n\n/**\n * Resolve all dependencies for a component (including nested registry dependencies)\n */\nexport function resolveComponentDependencies(\n componentName: string,\n registry: Registry,\n resolved: Set<string> = new Set(),\n): ComponentMeta[] {\n if (resolved.has(componentName)) return [];\n\n const component = registry.components[componentName];\n if (!component) {\n throw new Error(`Component \"${componentName}\" not found in registry`);\n }\n\n resolved.add(componentName);\n\n const deps: ComponentMeta[] = [];\n\n // Resolve registry dependencies first\n for (const depName of component.registryDependencies) {\n deps.push(...resolveComponentDependencies(depName, registry, resolved));\n }\n\n deps.push(component);\n return deps;\n}\n\n/**\n * Get all available components for a framework\n */\nexport function getComponentsByFramework(\n registry: Registry,\n framework: \"nuxt\" | \"vue\" | \"react\",\n): ComponentMeta[] {\n return Object.values(registry.components).filter(\n (c) =>\n c.framework === framework ||\n c.framework === \"universal\" ||\n (framework === \"nuxt\" && c.framework === \"vue\"),\n );\n}\n\n/**\n * Get all available snippets for a framework\n */\nexport function getSnippetsByFramework(\n registry: Registry,\n framework: \"nuxt\" | \"vue\" | \"react\" | \"node\",\n): SnippetMeta[] {\n return Object.values(registry.snippets).filter(\n (s) =>\n s.framework === framework ||\n s.framework === \"universal\" ||\n (framework === \"nuxt\" && s.framework === \"vue\"),\n );\n}\n\n/**\n * Group items by category\n */\nexport function groupByCategory<T extends { category: string }>(items: T[]): Record<string, T[]> {\n return items.reduce(\n (acc, item) => {\n if (!acc[item.category]) {\n acc[item.category] = [];\n }\n acc[item.category].push(item);\n return acc;\n },\n {} as Record<string, T[]>,\n );\n}\n","import path from \"path\";\nimport { DEFAULT_PATHS, PATHS } from \"./constants.js\";\nimport {\n resolveComponentDependencies,\n type ComponentMeta,\n type Registry,\n type RegistryFile,\n} from \"./registry.js\";\n\nexport type ComponentFramework = \"nuxt\" | \"vue\" | \"react\";\n\nexport interface ComponentInstallPlan {\n components: ComponentMeta[];\n dependencies: string[];\n devDependencies: string[];\n filesToCopy: { src: string; dest: string }[];\n}\n\nexport function resolveComponentFramework(framework: string | null): ComponentFramework | null {\n if (framework === \"nuxt\" || framework === \"vue\" || framework === \"react\") {\n return framework;\n }\n return null;\n}\n\nexport function findComponentKey(registry: Registry, component: ComponentMeta): string {\n const match = Object.entries(registry.components).find(\n ([, candidate]) => candidate === component,\n );\n return match?.[0] ?? component.name.toLowerCase();\n}\n\nexport function buildComponentInstallPlan(input: {\n componentKeys: string[];\n framework: ComponentFramework;\n projectRoot: string;\n registry: Registry;\n targetBase?: string;\n}): ComponentInstallPlan {\n const selectedComponents = input.componentKeys.flatMap((key) =>\n resolveComponentDependencies(key, input.registry),\n );\n const components = [\n ...new Map(\n selectedComponents.map((component) => [\n findComponentKey(input.registry, component),\n component,\n ]),\n ).values(),\n ];\n\n const filesToCopy = components.flatMap((component) =>\n component.files.map((file) =>\n resolveComponentFile({\n component,\n file,\n framework: input.framework,\n projectRoot: input.projectRoot,\n targetBase: input.targetBase,\n }),\n ),\n );\n\n return {\n components,\n dependencies: [...new Set(components.flatMap((component) => component.dependencies))],\n devDependencies: [...new Set(components.flatMap((component) => component.devDependencies))],\n filesToCopy,\n };\n}\n\nfunction resolveComponentFile(input: {\n component: ComponentMeta;\n file: RegistryFile;\n framework: ComponentFramework;\n projectRoot: string;\n targetBase?: string;\n}): { src: string; dest: string } {\n const normalized = typeof input.file === \"string\" ? { path: input.file } : input.file;\n const sourceFramework =\n input.component.framework === \"universal\" ? input.framework : input.component.framework;\n const sourcePath = path.join(PATHS.components, sourceFramework, normalized.path);\n\n if (normalized.target) {\n return {\n src: sourcePath,\n dest: path.resolve(input.projectRoot, normalized.target),\n };\n }\n\n const targetBase =\n input.targetBase || DEFAULT_PATHS[input.framework]?.components || \"src/components\";\n\n return {\n src: sourcePath,\n dest: path.resolve(input.projectRoot, targetBase, path.basename(normalized.path)),\n };\n}\n","import path from \"path\";\nimport fs from \"fs-extra\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport { PATHS, DEFAULT_PATHS } from \"../utils/constants.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { copyFiles } from \"../utils/fs.js\";\nimport { detectProject, installDependencies } from \"../utils/project.js\";\nimport { patchNuxtConfigExtends } from \"../utils/nuxt-config.js\";\nimport {\n buildComponentInstallPlan,\n findComponentKey,\n resolveComponentFramework,\n} from \"../utils/registry-install.js\";\nimport {\n loadRegistry,\n resolveComponentDependencies,\n getComponentsByFramework,\n getSnippetsByFramework,\n groupByCategory,\n type ComponentMeta,\n type PackageMeta,\n type SnippetMeta,\n} from \"../utils/registry.js\";\n\ninterface AddOptions {\n component?: boolean;\n snippet?: boolean;\n all?: boolean;\n yes?: boolean;\n overwrite?: boolean;\n path?: string;\n}\n\nexport async function add(items: string[], options: AddOptions): Promise<void> {\n logger.br();\n\n // Detect project info\n const project = await detectProject();\n\n if (!project.packageJson) {\n logger.error(\"No package.json found. Please run this command in a project directory.\");\n process.exit(1);\n }\n\n // Load registry\n const registry = await loadRegistry();\n\n // Package dispatch (npm / layer) takes precedence for matching names\n const packageItems = items.filter((i) => registry.packages[i]).map((i) => registry.packages[i]!);\n if (packageItems.length > 0) {\n await addPackages(packageItems, project);\n }\n const nonPackageItems = items.filter((i) => !registry.packages[i]);\n if (items.length > 0 && nonPackageItems.length === 0) return;\n\n // Determine what type of items to add\n const isComponent = options.component || (!options.snippet && !options.component);\n const isSnippet = options.snippet;\n\n if (isComponent) {\n await addComponents(nonPackageItems, options, project, registry);\n } else if (isSnippet) {\n await addSnippets(nonPackageItems, options, project, registry);\n }\n}\n\n/** 安装 npm/layer 类型的包;layer 额外 patch 项目 nuxt.config 的 extends */\nasync function addPackages(\n packages: PackageMeta[],\n project: Awaited<ReturnType<typeof detectProject>>,\n): Promise<void> {\n for (const pkg of packages) {\n const spec = pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name;\n const spinner = ora(`Installing ${pkg.name}...`).start();\n try {\n await installDependencies(project.root, [spec]);\n spinner.succeed(`Installed ${pkg.name}`);\n } catch (error) {\n spinner.fail(`Failed to install ${pkg.name}`);\n throw error;\n }\n if (pkg.type === \"layer\") {\n await patchProjectNuxtConfig(project.root, pkg.name);\n }\n }\n}\n\nasync function patchProjectNuxtConfig(root: string, layer: string): Promise<void> {\n const candidates = [\"nuxt.config.ts\", \"nuxt.config.js\", \"nuxt.config.mjs\"];\n for (const file of candidates) {\n const configPath = path.join(root, file);\n if (!(await fs.pathExists(configPath))) continue;\n const source = await fs.readFile(configPath, \"utf8\");\n try {\n const patched = patchNuxtConfigExtends(source, layer);\n if (patched === source) {\n logger.info(`${layer} already in ${file} extends`);\n } else {\n await fs.writeFile(configPath, patched);\n logger.success(`Added ${layer} to ${file} extends`);\n }\n } catch {\n logger.warn(`Could not auto-patch ${file}; add \"${layer}\" to extends manually`);\n }\n return;\n }\n logger.warn(`No nuxt.config found; add \"${layer}\" to your nuxt.config extends manually`);\n}\n\nasync function addComponents(\n items: string[],\n options: AddOptions,\n project: Awaited<ReturnType<typeof detectProject>>,\n registry: Awaited<ReturnType<typeof loadRegistry>>,\n): Promise<void> {\n const framework = resolveComponentFramework(project.framework);\n\n if (!framework) {\n logger.error(\"Could not detect framework. Supported: Nuxt, Vue, React\");\n process.exit(1);\n }\n\n const availableComponents = getComponentsByFramework(registry, framework);\n\n if (availableComponents.length === 0) {\n logger.warn(`No components available for ${framework}`);\n return;\n }\n\n let selectedComponents: ComponentMeta[] = [];\n\n if (options.all) {\n // Add all components\n selectedComponents = availableComponents;\n } else if (items.length > 0) {\n // Add specified components\n for (const item of items) {\n try {\n const deps = resolveComponentDependencies(item, registry);\n selectedComponents.push(...deps);\n } catch (error) {\n logger.error((error as Error).message);\n process.exit(1);\n }\n }\n } else {\n // Interactive selection\n const grouped = groupByCategory(availableComponents);\n const choices = Object.entries(grouped).flatMap(([category, components]) => [\n { title: category, disabled: true, value: \"\" },\n ...components.map((c) => ({\n title: ` ${c.name}`,\n description: c.description,\n value: findComponentKey(registry, c),\n })),\n ]);\n\n const response = await prompts({\n type: \"multiselect\",\n name: \"components\",\n message: \"Select components to add:\",\n choices,\n hint: \"- Space to select. Return to submit\",\n });\n\n if (!response.components || response.components.length === 0) {\n logger.info(\"No components selected\");\n return;\n }\n\n for (const name of response.components) {\n const deps = resolveComponentDependencies(name, registry);\n selectedComponents.push(...deps);\n }\n }\n\n const componentKeys = [\n ...new Set(selectedComponents.map((component) => findComponentKey(registry, component))),\n ];\n\n if (selectedComponents.length === 0) {\n logger.info(\"No components to add\");\n return;\n }\n\n // Confirm\n if (!options.yes) {\n logger.info(\"Components to add:\");\n selectedComponents.forEach((c) => logger.info(` - ${c.name}`));\n logger.br();\n\n const { confirm } = await prompts({\n type: \"confirm\",\n name: \"confirm\",\n message: \"Proceed?\",\n initial: true,\n });\n\n if (!confirm) {\n logger.info(\"Cancelled\");\n return;\n }\n }\n\n const installPlan = buildComponentInstallPlan({\n componentKeys,\n framework,\n projectRoot: project.root,\n registry,\n targetBase: options.path,\n });\n\n // Copy files\n const spinner = ora(\"Adding components...\").start();\n\n try {\n const { copied, skipped } = await copyFiles(installPlan.filesToCopy, options.overwrite);\n\n spinner.succeed(`Added ${copied.length} file(s)`);\n\n if (skipped.length > 0) {\n logger.warn(`Skipped ${skipped.length} existing file(s). Use --overwrite to replace.`);\n }\n } catch (error) {\n spinner.fail(\"Failed to add components\");\n throw error;\n }\n\n // Install dependencies\n const uniqueDeps = installPlan.dependencies;\n const uniqueDevDeps = installPlan.devDependencies;\n\n if (uniqueDeps.length > 0 || uniqueDevDeps.length > 0) {\n const { shouldInstall } = options.yes\n ? { shouldInstall: true }\n : await prompts({\n type: \"confirm\",\n name: \"shouldInstall\",\n message: \"Install required dependencies?\",\n initial: true,\n });\n\n if (shouldInstall) {\n const installSpinner = ora(\"Installing dependencies...\").start();\n try {\n if (uniqueDeps.length > 0) {\n await installDependencies(project.root, uniqueDeps, false);\n }\n if (uniqueDevDeps.length > 0) {\n await installDependencies(project.root, uniqueDevDeps, true);\n }\n installSpinner.succeed(\"Dependencies installed\");\n } catch (error) {\n installSpinner.fail(\"Failed to install some dependencies\");\n }\n }\n }\n\n logger.br();\n logger.success(\"Done!\");\n}\n\nasync function addSnippets(\n items: string[],\n options: AddOptions,\n project: Awaited<ReturnType<typeof detectProject>>,\n registry: Awaited<ReturnType<typeof loadRegistry>>,\n): Promise<void> {\n const framework =\n project.framework === \"nuxt\"\n ? \"nuxt\"\n : project.framework === \"vue\"\n ? \"vue\"\n : project.framework === \"react\"\n ? \"react\"\n : \"node\";\n\n const availableSnippets = getSnippetsByFramework(registry, framework);\n\n if (availableSnippets.length === 0) {\n logger.warn(`No snippets available for ${framework}`);\n return;\n }\n\n let selectedSnippets: SnippetMeta[] = [];\n\n if (options.all) {\n selectedSnippets = availableSnippets;\n } else if (items.length > 0) {\n for (const item of items) {\n const snippet = registry.snippets[item];\n if (!snippet) {\n logger.error(`Snippet \"${item}\" not found`);\n process.exit(1);\n }\n selectedSnippets.push(snippet);\n }\n } else {\n const grouped = groupByCategory(availableSnippets);\n const choices = Object.entries(grouped).flatMap(([category, snippets]) => [\n { title: category, disabled: true, value: \"\" },\n ...snippets.map((s) => ({\n title: ` ${s.name}`,\n description: s.description,\n value: s.name.toLowerCase(),\n })),\n ]);\n\n const response = await prompts({\n type: \"multiselect\",\n name: \"snippets\",\n message: \"Select snippets to add:\",\n choices,\n hint: \"- Space to select. Return to submit\",\n });\n\n if (!response.snippets || response.snippets.length === 0) {\n logger.info(\"No snippets selected\");\n return;\n }\n\n selectedSnippets = response.snippets.map((name: string) => registry.snippets[name]);\n }\n\n if (selectedSnippets.length === 0) {\n logger.info(\"No snippets to add\");\n return;\n }\n\n // Confirm\n if (!options.yes) {\n logger.info(\"Snippets to add:\");\n selectedSnippets.forEach((s) => logger.info(` - ${s.name}`));\n logger.br();\n\n const { confirm } = await prompts({\n type: \"confirm\",\n name: \"confirm\",\n message: \"Proceed?\",\n initial: true,\n });\n\n if (!confirm) {\n logger.info(\"Cancelled\");\n return;\n }\n }\n\n // Determine target path\n const targetBase = options.path || DEFAULT_PATHS[framework]?.utils || \"src/utils\";\n const targetDir = path.resolve(project.root, targetBase);\n\n // Copy files\n const spinner = ora(\"Adding snippets...\").start();\n\n const filesToCopy: { src: string; dest: string }[] = [];\n\n for (const snippet of selectedSnippets) {\n // Use snippet's own framework for source path (node, vue, react, or universal -> node)\n const snippetFramework = snippet.framework === \"universal\" ? \"node\" : snippet.framework;\n const snippetDir = path.join(PATHS.snippets, snippetFramework);\n\n for (const file of snippet.files) {\n const normalized = typeof file === \"string\" ? { path: file } : file;\n filesToCopy.push({\n src: path.join(snippetDir, normalized.path),\n dest: normalized.target\n ? path.resolve(project.root, normalized.target)\n : path.join(targetDir, path.basename(normalized.path)),\n });\n }\n }\n\n try {\n const { copied, skipped } = await copyFiles(filesToCopy, options.overwrite);\n spinner.succeed(`Added ${copied.length} file(s)`);\n\n if (skipped.length > 0) {\n logger.warn(`Skipped ${skipped.length} existing file(s)`);\n }\n } catch (error) {\n spinner.fail(\"Failed to add snippets\");\n throw error;\n }\n\n logger.br();\n logger.success(\"Done!\");\n}\n","function escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * 幂等地把一个 layer 加入 nuxt.config 的 `extends` 数组。\n * - 已含该 layer → 原样返回\n * - 已有 extends 数组 → 在数组头插入\n * - 仅有 defineNuxtConfig({ ... }) → 注入 extends\n * - 都识别不了 → 抛错,由调用方提示手动添加\n */\nexport function patchNuxtConfigExtends(source: string, layer: string): string {\n const already = new RegExp(`extends\\\\s*:\\\\s*\\\\[[^\\\\]]*['\"]${escapeRegExp(layer)}['\"]`);\n if (already.test(source)) return source;\n\n const extendsArray = /extends\\s*:\\s*\\[/;\n if (extendsArray.test(source)) {\n return source.replace(extendsArray, (match) => `${match}\"${layer}\", `);\n }\n\n const define = /defineNuxtConfig\\(\\{/;\n if (define.test(source)) {\n return source.replace(define, (match) => `${match}\\n extends: [\"${layer}\"],`);\n }\n\n throw new Error(\n \"Could not locate defineNuxtConfig in nuxt.config; add the layer to `extends` manually\",\n );\n}\n","import { TEMPLATES } from \"../utils/constants.js\";\nimport { logger } from \"../utils/logger.js\";\nimport {\n loadRegistry,\n groupByCategory,\n type ComponentMeta,\n type SnippetMeta,\n} from \"../utils/registry.js\";\n\ninterface ListOptions {\n templates?: boolean;\n components?: boolean;\n snippets?: boolean;\n}\n\nexport async function list(type: string | undefined, options: ListOptions): Promise<void> {\n logger.br();\n\n const showAll = !options.templates && !options.components && !options.snippets && !type;\n const showTemplates = options.templates || type === \"templates\" || showAll;\n const showComponents = options.components || type === \"components\" || showAll;\n const showSnippets = options.snippets || type === \"snippets\" || showAll;\n\n if (showTemplates) {\n listTemplates();\n }\n\n if (showComponents) {\n await listComponents();\n }\n\n if (showSnippets) {\n await listSnippets();\n }\n}\n\nfunction listTemplates(): void {\n logger.title(\"Templates\");\n logger.br();\n\n const entries = Object.entries(TEMPLATES);\n\n if (entries.length === 0) {\n logger.info(\" No templates available\");\n } else {\n for (const [key, template] of entries) {\n logger.info(` ${key}`);\n logger.subtitle(` ${template.description}`);\n }\n }\n\n logger.br();\n logger.subtitle(\" Usage: stackonward init --template <name>\");\n logger.br();\n}\n\nasync function listComponents(): Promise<void> {\n logger.title(\"Components\");\n logger.br();\n\n const registry = await loadRegistry();\n const components = Object.values(registry.components);\n\n if (components.length === 0) {\n logger.info(\" No components available\");\n logger.br();\n return;\n }\n\n // Group by framework first\n const byFramework: Record<string, ComponentMeta[]> = {};\n for (const component of components) {\n const fw = component.framework;\n if (!byFramework[fw]) byFramework[fw] = [];\n byFramework[fw].push(component);\n }\n\n for (const [framework, fwComponents] of Object.entries(byFramework)) {\n logger.info(` [${framework}]`);\n\n const grouped = groupByCategory(fwComponents);\n\n for (const [category, catComponents] of Object.entries(grouped)) {\n logger.subtitle(` ${category}/`);\n for (const component of catComponents) {\n logger.info(` - ${component.name.toLowerCase()}: ${component.description}`);\n }\n }\n\n logger.br();\n }\n\n logger.subtitle(\" Usage: stackonward add <component-name>\");\n logger.br();\n}\n\nasync function listSnippets(): Promise<void> {\n logger.title(\"Snippets\");\n logger.br();\n\n const registry = await loadRegistry();\n const snippets = Object.values(registry.snippets);\n\n if (snippets.length === 0) {\n logger.info(\" No snippets available\");\n logger.br();\n return;\n }\n\n // Group by framework first\n const byFramework: Record<string, SnippetMeta[]> = {};\n for (const snippet of snippets) {\n const fw = snippet.framework;\n if (!byFramework[fw]) byFramework[fw] = [];\n byFramework[fw].push(snippet);\n }\n\n for (const [framework, fwSnippets] of Object.entries(byFramework)) {\n logger.info(` [${framework}]`);\n\n const grouped = groupByCategory(fwSnippets);\n\n for (const [category, catSnippets] of Object.entries(grouped)) {\n logger.subtitle(` ${category}/`);\n for (const snippet of catSnippets) {\n logger.info(` - ${snippet.name.toLowerCase()}: ${snippet.description}`);\n }\n }\n\n logger.br();\n }\n\n logger.subtitle(\" Usage: stackonward add -s <snippet-name>\");\n logger.br();\n}\n","import path from \"path\";\nimport fs from \"fs-extra\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport { PATHS } from \"../utils/constants.js\";\nimport { logger } from \"../utils/logger.js\";\nimport { copyFiles } from \"../utils/fs.js\";\nimport { detectProject } from \"../utils/project.js\";\n\ninterface UpdateOptions {\n config?: boolean;\n all?: boolean;\n}\n\nconst CONFIG_FILES = [\n { name: \"ESLint\", file: \"eslint.config.js\", source: \"eslint-config\" },\n { name: \"Prettier\", file: \".prettierrc\", source: \"prettier-config\" },\n { name: \"EditorConfig\", file: \".editorconfig\", source: \"base\" },\n { name: \"Commitlint\", file: \"commitlint.config.js\", source: \"base\" },\n { name: \"Lint-staged\", file: \".lintstagedrc.json\", source: \"base\" },\n] as const;\n\nexport async function update(items: string[], options: UpdateOptions): Promise<void> {\n logger.br();\n\n const project = await detectProject();\n\n if (!project.packageJson) {\n logger.error(\"No package.json found. Please run this command in a project directory.\");\n process.exit(1);\n }\n\n if (options.config || options.all) {\n await updateConfigs(project, items);\n } else {\n // Default to config update\n await updateConfigs(project, items);\n }\n}\n\nasync function updateConfigs(\n project: Awaited<ReturnType<typeof detectProject>>,\n specificConfigs: string[],\n): Promise<void> {\n logger.title(\"Update Configuration Files\");\n logger.br();\n\n let configsToUpdate: (typeof CONFIG_FILES)[number][] = [...CONFIG_FILES];\n\n if (specificConfigs.length > 0) {\n configsToUpdate = CONFIG_FILES.filter((c) =>\n specificConfigs.some(\n (s) => c.name.toLowerCase().includes(s.toLowerCase()) || c.file.includes(s),\n ),\n );\n\n if (configsToUpdate.length === 0) {\n logger.error(\"No matching config files found\");\n logger.info(\"Available configs:\");\n CONFIG_FILES.forEach((c) => logger.info(` - ${c.name} (${c.file})`));\n return;\n }\n } else {\n // Interactive selection\n const choices = CONFIG_FILES.map((c) => ({\n title: c.name,\n description: c.file,\n value: c.file,\n selected: false,\n }));\n\n const response = await prompts({\n type: \"multiselect\",\n name: \"configs\",\n message: \"Select configs to update:\",\n choices,\n hint: \"- Space to select. Return to submit\",\n });\n\n if (!response.configs || response.configs.length === 0) {\n logger.info(\"No configs selected\");\n return;\n }\n\n configsToUpdate = CONFIG_FILES.filter((c) => response.configs.includes(c.file));\n }\n\n // Check which files exist\n const existingConfigs: (typeof CONFIG_FILES)[number][] = [];\n const newConfigs: (typeof CONFIG_FILES)[number][] = [];\n\n for (const config of configsToUpdate) {\n const targetPath = path.join(project.root, config.file);\n if (await fs.pathExists(targetPath)) {\n existingConfigs.push(config);\n } else {\n newConfigs.push(config);\n }\n }\n\n if (existingConfigs.length > 0) {\n logger.warn(\"The following files will be overwritten:\");\n existingConfigs.forEach((c) => logger.warn(` - ${c.file}`));\n\n const { confirm } = await prompts({\n type: \"confirm\",\n name: \"confirm\",\n message: \"Continue?\",\n initial: false,\n });\n\n if (!confirm) {\n logger.info(\"Cancelled\");\n return;\n }\n }\n\n const spinner = ora(\"Updating configs...\").start();\n\n const filesToCopy: { src: string; dest: string }[] = [];\n\n for (const config of configsToUpdate) {\n // Try to find the source file\n const sourcePaths = [\n path.join(PATHS.configs, config.source, config.file),\n path.join(PATHS.templates, \"base\", config.file),\n ];\n\n let sourcePath: string | null = null;\n for (const sp of sourcePaths) {\n if (await fs.pathExists(sp)) {\n sourcePath = sp;\n break;\n }\n }\n\n if (sourcePath) {\n filesToCopy.push({\n src: sourcePath,\n dest: path.join(project.root, config.file),\n });\n } else {\n logger.warn(`Source not found for ${config.file}`);\n }\n }\n\n try {\n const { copied } = await copyFiles(filesToCopy, true);\n spinner.succeed(`Updated ${copied.length} config file(s)`);\n } catch (error) {\n spinner.fail(\"Failed to update configs\");\n throw error;\n }\n\n logger.br();\n logger.success(\"Done!\");\n}\n"],"mappings":"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","names":["path","fs","fs","path","fs","path","fs","path","path","path","path","fs","path","fs","prompts","ora","ora","path","fs","prompts","path","fs","prompts","ora","prompts","path","fs","ora"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stackonward/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "StackOnward CLI for creating projects and installing reusable components and snippets",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"stackonward": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cli",
|
|
15
|
+
"stackonward",
|
|
16
|
+
"scaffolding",
|
|
17
|
+
"templates",
|
|
18
|
+
"components"
|
|
19
|
+
],
|
|
20
|
+
"author": "yhy",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/1yhy/stackonward.git",
|
|
28
|
+
"directory": "packages/cli"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"commander": "^12.0.0",
|
|
32
|
+
"prompts": "^2.4.2",
|
|
33
|
+
"kolorist": "^1.8.0",
|
|
34
|
+
"fs-extra": "^11.2.0",
|
|
35
|
+
"glob": "^11.0.0",
|
|
36
|
+
"ora": "^8.0.0",
|
|
37
|
+
"execa": "^9.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/fs-extra": "^11.0.4",
|
|
41
|
+
"@types/prompts": "^2.4.9",
|
|
42
|
+
"tsup": "^8.0.0",
|
|
43
|
+
"typescript": "^5.7.0",
|
|
44
|
+
"vitest": "^2.1.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=20.0.0"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"dev": "tsup --watch",
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"clean": "rm -rf dist",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"test": "vitest run"
|
|
55
|
+
}
|
|
56
|
+
}
|