create-better-fullstack 1.5.1 → 1.5.2
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/addons-setup-1jZXP0D3.mjs +1342 -0
- package/dist/addons-setup-AbN693PV.mjs +5 -0
- package/dist/bts-config-BCe8SqYV.mjs +308 -0
- package/dist/cli.mjs +2 -3
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +7931 -1
- package/dist/mcp-C_X1WfCg.mjs +5 -0
- package/dist/mcp-entry.d.mts +1 -0
- package/dist/mcp-entry.mjs +836 -0
- package/package.json +8 -3
- package/dist/src-hfdQPH0o.mjs +0 -9536
- /package/dist/{chunk-DPg_XC7m.mjs → chunk-CCII7kTE.mjs} +0 -0
|
@@ -0,0 +1,1342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { s as dependencyVersionMap, t as readBtsConfig } from "./bts-config-BCe8SqYV.mjs";
|
|
3
|
+
import { autocompleteMultiselect, cancel, group, isCancel, log, multiselect, select, spinner } from "@clack/prompts";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { getLocalWebDevPort } from "@better-fullstack/types";
|
|
8
|
+
import consola, { consola as consola$1 } from "consola";
|
|
9
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
+
import { $ } from "execa";
|
|
11
|
+
|
|
12
|
+
//#region src/utils/context.ts
|
|
13
|
+
const cliStorage = new AsyncLocalStorage();
|
|
14
|
+
function defaultContext() {
|
|
15
|
+
return {
|
|
16
|
+
navigation: {
|
|
17
|
+
isFirstPrompt: false,
|
|
18
|
+
lastPromptShownUI: false
|
|
19
|
+
},
|
|
20
|
+
silent: false,
|
|
21
|
+
verbose: false
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function getContext() {
|
|
25
|
+
const ctx = cliStorage.getStore();
|
|
26
|
+
if (!ctx) return defaultContext();
|
|
27
|
+
return ctx;
|
|
28
|
+
}
|
|
29
|
+
function tryGetContext() {
|
|
30
|
+
return cliStorage.getStore();
|
|
31
|
+
}
|
|
32
|
+
function isSilent() {
|
|
33
|
+
return getContext().silent;
|
|
34
|
+
}
|
|
35
|
+
function isFirstPrompt() {
|
|
36
|
+
return getContext().navigation.isFirstPrompt;
|
|
37
|
+
}
|
|
38
|
+
function didLastPromptShowUI() {
|
|
39
|
+
return getContext().navigation.lastPromptShownUI;
|
|
40
|
+
}
|
|
41
|
+
function setIsFirstPrompt(value) {
|
|
42
|
+
const ctx = tryGetContext();
|
|
43
|
+
if (ctx) ctx.navigation.isFirstPrompt = value;
|
|
44
|
+
}
|
|
45
|
+
function setLastPromptShownUI(value) {
|
|
46
|
+
const ctx = tryGetContext();
|
|
47
|
+
if (ctx) ctx.navigation.lastPromptShownUI = value;
|
|
48
|
+
}
|
|
49
|
+
async function runWithContextAsync(options, fn) {
|
|
50
|
+
const ctx = {
|
|
51
|
+
navigation: {
|
|
52
|
+
isFirstPrompt: false,
|
|
53
|
+
lastPromptShownUI: false
|
|
54
|
+
},
|
|
55
|
+
silent: options.silent ?? false,
|
|
56
|
+
verbose: options.verbose ?? false,
|
|
57
|
+
projectDir: options.projectDir,
|
|
58
|
+
projectName: options.projectName,
|
|
59
|
+
packageManager: options.packageManager
|
|
60
|
+
};
|
|
61
|
+
return cliStorage.run(ctx, fn);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/utils/errors.ts
|
|
66
|
+
var UserCancelledError = class extends Error {
|
|
67
|
+
constructor(message = "Operation cancelled") {
|
|
68
|
+
super(message);
|
|
69
|
+
this.name = "UserCancelledError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var CLIError = class extends Error {
|
|
73
|
+
constructor(message) {
|
|
74
|
+
super(message);
|
|
75
|
+
this.name = "CLIError";
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function exitWithError(message) {
|
|
79
|
+
if (isSilent()) throw new CLIError(message);
|
|
80
|
+
consola.error(pc.red(message));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
function exitCancelled(message = "Operation cancelled") {
|
|
84
|
+
if (isSilent()) throw new UserCancelledError(message);
|
|
85
|
+
cancel(pc.red(message));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
function handleError(error, fallbackMessage) {
|
|
89
|
+
const message = error instanceof Error ? error.message : fallbackMessage || String(error);
|
|
90
|
+
if (isSilent()) throw error instanceof Error ? error : new Error(message);
|
|
91
|
+
consola.error(pc.red(message));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/utils/add-package-deps.ts
|
|
97
|
+
const addPackageDependency = async (opts) => {
|
|
98
|
+
const { dependencies = [], devDependencies = [], customDependencies = {}, customDevDependencies = {}, projectDir } = opts;
|
|
99
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
100
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
101
|
+
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
102
|
+
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
|
|
103
|
+
for (const pkgName of dependencies) {
|
|
104
|
+
const version = dependencyVersionMap[pkgName];
|
|
105
|
+
if (version) pkgJson.dependencies[pkgName] = version;
|
|
106
|
+
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
107
|
+
}
|
|
108
|
+
for (const pkgName of devDependencies) {
|
|
109
|
+
const version = dependencyVersionMap[pkgName];
|
|
110
|
+
if (version) pkgJson.devDependencies[pkgName] = version;
|
|
111
|
+
else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
|
|
112
|
+
}
|
|
113
|
+
for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
|
|
114
|
+
for (const [pkgName, version] of Object.entries(customDevDependencies)) pkgJson.devDependencies[pkgName] = version;
|
|
115
|
+
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/utils/package-runner.ts
|
|
120
|
+
/**
|
|
121
|
+
* Returns the appropriate command for running a package without installing it globally,
|
|
122
|
+
* based on the selected package manager.
|
|
123
|
+
*
|
|
124
|
+
* @param packageManager - The selected package manager (e.g., 'npm', 'yarn', 'pnpm', 'bun').
|
|
125
|
+
* @param commandWithArgs - The command to run, including arguments (e.g., "prisma generate --schema=./prisma/schema.prisma").
|
|
126
|
+
* @returns The full command string (e.g., "npx prisma generate --schema=./prisma/schema.prisma").
|
|
127
|
+
*/
|
|
128
|
+
function getPackageExecutionCommand(packageManager, commandWithArgs) {
|
|
129
|
+
switch (packageManager) {
|
|
130
|
+
case "pnpm": return `pnpm dlx ${commandWithArgs}`;
|
|
131
|
+
case "bun": return `bunx ${commandWithArgs}`;
|
|
132
|
+
case "yarn": return `yarn dlx ${commandWithArgs}`;
|
|
133
|
+
default: return `npx ${commandWithArgs}`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Returns the command and arguments as an array for use with execa's $ template syntax.
|
|
138
|
+
* This avoids the need for shell: true and provides better escaping.
|
|
139
|
+
*
|
|
140
|
+
* @param packageManager - The selected package manager (e.g., 'npm', 'yarn', 'pnpm', 'bun').
|
|
141
|
+
* @param commandWithArgs - The command to run, including arguments (e.g., "prisma generate").
|
|
142
|
+
* @returns An array of [command, ...args] (e.g., ["npx", "prisma", "generate"]).
|
|
143
|
+
*/
|
|
144
|
+
function getPackageExecutionArgs(packageManager, commandWithArgs) {
|
|
145
|
+
const args = commandWithArgs.split(" ");
|
|
146
|
+
switch (packageManager) {
|
|
147
|
+
case "pnpm": return [
|
|
148
|
+
"pnpm",
|
|
149
|
+
"dlx",
|
|
150
|
+
...args
|
|
151
|
+
];
|
|
152
|
+
case "bun": return ["bunx", ...args];
|
|
153
|
+
case "yarn": return [
|
|
154
|
+
"yarn",
|
|
155
|
+
"dlx",
|
|
156
|
+
...args
|
|
157
|
+
];
|
|
158
|
+
default: return ["npx", ...args];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Returns just the runner prefix as an array, for when you already have args built.
|
|
163
|
+
* Use this when you have complex arguments that shouldn't be split by spaces.
|
|
164
|
+
*
|
|
165
|
+
* @param packageManager - The selected package manager.
|
|
166
|
+
* @returns The runner prefix as an array (e.g., ["npx"] or ["pnpm", "dlx"]).
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* const prefix = getPackageRunnerPrefix("bun");
|
|
170
|
+
* const args = ["@tauri-apps/cli@latest", "init", "--app-name=foo"];
|
|
171
|
+
* await $`${[...prefix, ...args]}`;
|
|
172
|
+
*/
|
|
173
|
+
function getPackageRunnerPrefix(packageManager) {
|
|
174
|
+
switch (packageManager) {
|
|
175
|
+
case "pnpm": return ["pnpm", "dlx"];
|
|
176
|
+
case "bun": return ["bunx"];
|
|
177
|
+
case "yarn": return ["yarn", "dlx"];
|
|
178
|
+
default: return ["npx"];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/helpers/addons/fumadocs-setup.ts
|
|
184
|
+
const TEMPLATES$2 = {
|
|
185
|
+
"next-mdx": {
|
|
186
|
+
label: "Next.js: Fumadocs MDX",
|
|
187
|
+
hint: "Recommended template with MDX support",
|
|
188
|
+
value: "+next+fuma-docs-mdx"
|
|
189
|
+
},
|
|
190
|
+
waku: {
|
|
191
|
+
label: "Waku: Content Collections",
|
|
192
|
+
hint: "Template using Waku with content collections",
|
|
193
|
+
value: "waku"
|
|
194
|
+
},
|
|
195
|
+
"react-router": {
|
|
196
|
+
label: "React Router: MDX Remote",
|
|
197
|
+
hint: "Template for React Router with MDX remote",
|
|
198
|
+
value: "react-router"
|
|
199
|
+
},
|
|
200
|
+
"react-router-spa": {
|
|
201
|
+
label: "React Router: SPA",
|
|
202
|
+
hint: "Template for React Router SPA",
|
|
203
|
+
value: "react-router-spa"
|
|
204
|
+
},
|
|
205
|
+
"tanstack-start": {
|
|
206
|
+
label: "Tanstack Start: MDX Remote",
|
|
207
|
+
hint: "Template for Tanstack Start with MDX remote",
|
|
208
|
+
value: "tanstack-start"
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
async function setupFumadocs(config) {
|
|
212
|
+
const { packageManager, projectDir } = config;
|
|
213
|
+
try {
|
|
214
|
+
log.info("Setting up Fumadocs...");
|
|
215
|
+
const template = await select({
|
|
216
|
+
message: "Choose a template",
|
|
217
|
+
options: Object.entries(TEMPLATES$2).map(([key, template$1]) => ({
|
|
218
|
+
value: key,
|
|
219
|
+
label: template$1.label,
|
|
220
|
+
hint: template$1.hint
|
|
221
|
+
})),
|
|
222
|
+
initialValue: "next-mdx"
|
|
223
|
+
});
|
|
224
|
+
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
225
|
+
const args = getPackageExecutionArgs(packageManager, `create-fumadocs-app@latest fumadocs --template ${TEMPLATES$2[template].value} --src --pm ${packageManager} --no-git`);
|
|
226
|
+
const appsDir = path.join(projectDir, "apps");
|
|
227
|
+
await fs.ensureDir(appsDir);
|
|
228
|
+
const s = spinner();
|
|
229
|
+
s.start("Running Fumadocs create command...");
|
|
230
|
+
await $({
|
|
231
|
+
cwd: appsDir,
|
|
232
|
+
env: { CI: "true" }
|
|
233
|
+
})`${args}`;
|
|
234
|
+
const fumadocsDir = path.join(projectDir, "apps", "fumadocs");
|
|
235
|
+
const packageJsonPath = path.join(fumadocsDir, "package.json");
|
|
236
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
237
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
238
|
+
packageJson.name = "fumadocs";
|
|
239
|
+
if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port=4000`;
|
|
240
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
241
|
+
}
|
|
242
|
+
s.stop("Fumadocs setup complete!");
|
|
243
|
+
} catch (error) {
|
|
244
|
+
log.error(pc.red("Failed to set up Fumadocs"));
|
|
245
|
+
if (error instanceof Error) consola.error(pc.red(error.message));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region src/utils/external-commands.ts
|
|
251
|
+
function shouldSkipExternalCommands() {
|
|
252
|
+
const value = process.env.BFS_SKIP_EXTERNAL_COMMANDS;
|
|
253
|
+
if (!value) return false;
|
|
254
|
+
const normalized = value.toLowerCase().trim();
|
|
255
|
+
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/helpers/addons/mcp-setup.ts
|
|
260
|
+
const MCP_AGENTS = [
|
|
261
|
+
{
|
|
262
|
+
value: "cursor",
|
|
263
|
+
label: "Cursor",
|
|
264
|
+
scope: "both"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
value: "claude-code",
|
|
268
|
+
label: "Claude Code",
|
|
269
|
+
scope: "both"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
value: "codex",
|
|
273
|
+
label: "Codex",
|
|
274
|
+
scope: "both"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
value: "opencode",
|
|
278
|
+
label: "OpenCode",
|
|
279
|
+
scope: "both"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
value: "gemini-cli",
|
|
283
|
+
label: "Gemini CLI",
|
|
284
|
+
scope: "both"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
value: "vscode",
|
|
288
|
+
label: "VS Code (GitHub Copilot)",
|
|
289
|
+
scope: "both"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
value: "zed",
|
|
293
|
+
label: "Zed",
|
|
294
|
+
scope: "both"
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
value: "claude-desktop",
|
|
298
|
+
label: "Claude Desktop",
|
|
299
|
+
scope: "global"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
value: "goose",
|
|
303
|
+
label: "Goose",
|
|
304
|
+
scope: "global"
|
|
305
|
+
}
|
|
306
|
+
];
|
|
307
|
+
function uniqueValues$1(values) {
|
|
308
|
+
return Array.from(new Set(values));
|
|
309
|
+
}
|
|
310
|
+
function hasReactBasedFrontend$1(frontend) {
|
|
311
|
+
return frontend.includes("react-router") || frontend.includes("react-vite") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next");
|
|
312
|
+
}
|
|
313
|
+
function hasNativeFrontend$1(frontend) {
|
|
314
|
+
return frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
315
|
+
}
|
|
316
|
+
function getRecommendedMcpServers(config) {
|
|
317
|
+
const servers = [];
|
|
318
|
+
servers.push({
|
|
319
|
+
key: "context7",
|
|
320
|
+
label: "Context7",
|
|
321
|
+
name: "context7",
|
|
322
|
+
target: "@upstash/context7-mcp"
|
|
323
|
+
});
|
|
324
|
+
if (config.runtime === "workers" || config.webDeploy === "cloudflare" || config.serverDeploy === "cloudflare") servers.push({
|
|
325
|
+
key: "cloudflare-docs",
|
|
326
|
+
label: "Cloudflare Docs",
|
|
327
|
+
name: "cloudflare-docs",
|
|
328
|
+
target: "https://docs.mcp.cloudflare.com/sse",
|
|
329
|
+
transport: "sse"
|
|
330
|
+
});
|
|
331
|
+
if (config.backend === "convex") servers.push({
|
|
332
|
+
key: "convex",
|
|
333
|
+
label: "Convex",
|
|
334
|
+
name: "convex",
|
|
335
|
+
target: "npx -y convex@latest mcp start"
|
|
336
|
+
});
|
|
337
|
+
if (hasReactBasedFrontend$1(config.frontend)) servers.push({
|
|
338
|
+
key: "shadcn",
|
|
339
|
+
label: "shadcn/ui",
|
|
340
|
+
name: "shadcn",
|
|
341
|
+
target: "npx -y shadcn@latest mcp"
|
|
342
|
+
});
|
|
343
|
+
if (config.frontend.includes("next")) servers.push({
|
|
344
|
+
key: "next-devtools",
|
|
345
|
+
label: "Next Devtools",
|
|
346
|
+
name: "next-devtools",
|
|
347
|
+
target: "npx -y next-devtools-mcp@latest"
|
|
348
|
+
});
|
|
349
|
+
if (config.frontend.includes("nuxt")) servers.push({
|
|
350
|
+
key: "nuxt-docs",
|
|
351
|
+
label: "Nuxt Docs",
|
|
352
|
+
name: "nuxt",
|
|
353
|
+
target: "https://nuxt.com/mcp"
|
|
354
|
+
}, {
|
|
355
|
+
key: "nuxt-ui-docs",
|
|
356
|
+
label: "Nuxt UI Docs",
|
|
357
|
+
name: "nuxt-ui",
|
|
358
|
+
target: "https://ui.nuxt.com/mcp"
|
|
359
|
+
});
|
|
360
|
+
if (config.frontend.includes("svelte")) servers.push({
|
|
361
|
+
key: "svelte-docs",
|
|
362
|
+
label: "Svelte Docs",
|
|
363
|
+
name: "svelte",
|
|
364
|
+
target: "https://mcp.svelte.dev/mcp"
|
|
365
|
+
});
|
|
366
|
+
if (config.frontend.includes("astro")) servers.push({
|
|
367
|
+
key: "astro-docs",
|
|
368
|
+
label: "Astro Docs",
|
|
369
|
+
name: "astro-docs",
|
|
370
|
+
target: "https://mcp.docs.astro.build/mcp"
|
|
371
|
+
});
|
|
372
|
+
if (config.dbSetup === "planetscale") servers.push({
|
|
373
|
+
key: "planetscale",
|
|
374
|
+
label: "PlanetScale",
|
|
375
|
+
name: "planetscale",
|
|
376
|
+
target: "https://mcp.pscale.dev/mcp/planetscale"
|
|
377
|
+
});
|
|
378
|
+
if (config.dbSetup === "neon") servers.push({
|
|
379
|
+
key: "neon",
|
|
380
|
+
label: "Neon",
|
|
381
|
+
name: "neon",
|
|
382
|
+
target: "https://mcp.neon.tech/mcp"
|
|
383
|
+
});
|
|
384
|
+
if (config.dbSetup === "supabase") servers.push({
|
|
385
|
+
key: "supabase",
|
|
386
|
+
label: "Supabase",
|
|
387
|
+
name: "supabase",
|
|
388
|
+
target: "https://mcp.supabase.com/mcp"
|
|
389
|
+
});
|
|
390
|
+
if (config.auth === "better-auth") servers.push({
|
|
391
|
+
key: "better-auth",
|
|
392
|
+
label: "Better Auth",
|
|
393
|
+
name: "better-auth",
|
|
394
|
+
target: "https://mcp.inkeep.com/better-auth/mcp"
|
|
395
|
+
});
|
|
396
|
+
if (config.auth === "clerk") servers.push({
|
|
397
|
+
key: "clerk",
|
|
398
|
+
label: "Clerk",
|
|
399
|
+
name: "clerk",
|
|
400
|
+
target: "https://mcp.clerk.com/mcp"
|
|
401
|
+
});
|
|
402
|
+
if (hasNativeFrontend$1(config.frontend)) servers.push({
|
|
403
|
+
key: "expo",
|
|
404
|
+
label: "Expo",
|
|
405
|
+
name: "expo-mcp",
|
|
406
|
+
target: "https://mcp.expo.dev/mcp"
|
|
407
|
+
});
|
|
408
|
+
if (config.payments === "polar") servers.push({
|
|
409
|
+
key: "polar",
|
|
410
|
+
label: "Polar",
|
|
411
|
+
name: "polar",
|
|
412
|
+
target: "https://mcp.polar.sh/mcp/polar-mcp"
|
|
413
|
+
});
|
|
414
|
+
return servers;
|
|
415
|
+
}
|
|
416
|
+
function filterAgentsForScope(scope) {
|
|
417
|
+
return MCP_AGENTS.filter((a) => a.scope === "both" || a.scope === scope);
|
|
418
|
+
}
|
|
419
|
+
async function setupMcp(config) {
|
|
420
|
+
if (shouldSkipExternalCommands()) return;
|
|
421
|
+
const { packageManager, projectDir } = config;
|
|
422
|
+
log.info("Setting up MCP servers...");
|
|
423
|
+
const scope = await select({
|
|
424
|
+
message: "Where should MCP servers be installed?",
|
|
425
|
+
options: [{
|
|
426
|
+
value: "project",
|
|
427
|
+
label: "Project",
|
|
428
|
+
hint: "Writes to project config files (recommended for teams)"
|
|
429
|
+
}, {
|
|
430
|
+
value: "global",
|
|
431
|
+
label: "Global",
|
|
432
|
+
hint: "Writes to user-level config files (personal machine)"
|
|
433
|
+
}],
|
|
434
|
+
initialValue: "project"
|
|
435
|
+
});
|
|
436
|
+
if (isCancel(scope)) return;
|
|
437
|
+
const recommendedServers = getRecommendedMcpServers(config);
|
|
438
|
+
if (recommendedServers.length === 0) return;
|
|
439
|
+
const selectedServerKeys = await multiselect({
|
|
440
|
+
message: "Select MCP servers to install",
|
|
441
|
+
options: recommendedServers.map((server) => ({
|
|
442
|
+
value: server.key,
|
|
443
|
+
label: server.label,
|
|
444
|
+
hint: server.target
|
|
445
|
+
})),
|
|
446
|
+
required: false,
|
|
447
|
+
initialValues: recommendedServers.map((server) => server.key)
|
|
448
|
+
});
|
|
449
|
+
if (isCancel(selectedServerKeys) || selectedServerKeys.length === 0) return;
|
|
450
|
+
const agentOptions = filterAgentsForScope(scope).map((a) => ({
|
|
451
|
+
value: a.value,
|
|
452
|
+
label: a.label
|
|
453
|
+
}));
|
|
454
|
+
const selectedAgents = await multiselect({
|
|
455
|
+
message: "Select agents to install MCP servers to",
|
|
456
|
+
options: agentOptions,
|
|
457
|
+
required: false,
|
|
458
|
+
initialValues: uniqueValues$1([
|
|
459
|
+
"cursor",
|
|
460
|
+
"claude-code",
|
|
461
|
+
"vscode"
|
|
462
|
+
].filter((agent) => agentOptions.some((option) => option.value === agent)))
|
|
463
|
+
});
|
|
464
|
+
if (isCancel(selectedAgents) || selectedAgents.length === 0) return;
|
|
465
|
+
const serversByKey = new Map(recommendedServers.map((server) => [server.key, server]));
|
|
466
|
+
const selectedServers = [];
|
|
467
|
+
for (const key of selectedServerKeys) {
|
|
468
|
+
const server = serversByKey.get(key);
|
|
469
|
+
if (server) selectedServers.push(server);
|
|
470
|
+
}
|
|
471
|
+
if (selectedServers.length === 0) return;
|
|
472
|
+
const installSpinner = spinner();
|
|
473
|
+
installSpinner.start("Installing MCP servers...");
|
|
474
|
+
const runner = getPackageRunnerPrefix(packageManager);
|
|
475
|
+
const globalFlags = scope === "global" ? ["-g"] : [];
|
|
476
|
+
for (const server of selectedServers) {
|
|
477
|
+
const transportFlags = server.transport ? ["-t", server.transport] : [];
|
|
478
|
+
const headerFlags = (server.headers ?? []).flatMap((header) => ["--header", header]);
|
|
479
|
+
const agentFlags = selectedAgents.flatMap((agent) => ["-a", agent]);
|
|
480
|
+
const args = [
|
|
481
|
+
...runner,
|
|
482
|
+
"add-mcp@latest",
|
|
483
|
+
server.target,
|
|
484
|
+
"--name",
|
|
485
|
+
server.name,
|
|
486
|
+
...transportFlags,
|
|
487
|
+
...headerFlags,
|
|
488
|
+
...agentFlags,
|
|
489
|
+
...globalFlags,
|
|
490
|
+
"-y"
|
|
491
|
+
];
|
|
492
|
+
try {
|
|
493
|
+
await $({
|
|
494
|
+
cwd: projectDir,
|
|
495
|
+
env: { CI: "true" }
|
|
496
|
+
})`${args}`;
|
|
497
|
+
} catch (error) {
|
|
498
|
+
log.warn(pc.yellow(`Warning: Could not install MCP server '${server.name}': ${error instanceof Error ? error.message : String(error)}`));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
installSpinner.stop("MCP servers installed");
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
//#endregion
|
|
505
|
+
//#region src/helpers/addons/oxlint-setup.ts
|
|
506
|
+
async function setupOxlint(projectDir, packageManager) {
|
|
507
|
+
await addPackageDependency({
|
|
508
|
+
devDependencies: ["oxlint", "oxfmt"],
|
|
509
|
+
projectDir
|
|
510
|
+
});
|
|
511
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
512
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
513
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
514
|
+
packageJson.scripts = {
|
|
515
|
+
...packageJson.scripts,
|
|
516
|
+
check: "oxlint && oxfmt --write"
|
|
517
|
+
};
|
|
518
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
519
|
+
}
|
|
520
|
+
const s = spinner();
|
|
521
|
+
const oxlintArgs = getPackageExecutionArgs(packageManager, "oxlint@latest --init");
|
|
522
|
+
s.start("Initializing oxlint and oxfmt...");
|
|
523
|
+
await $({
|
|
524
|
+
cwd: projectDir,
|
|
525
|
+
env: { CI: "true" }
|
|
526
|
+
})`${oxlintArgs}`;
|
|
527
|
+
const oxfmtArgs = getPackageExecutionArgs(packageManager, "oxfmt@latest --init");
|
|
528
|
+
await $({
|
|
529
|
+
cwd: projectDir,
|
|
530
|
+
env: { CI: "true" }
|
|
531
|
+
})`${oxfmtArgs}`;
|
|
532
|
+
s.stop("oxlint and oxfmt initialized successfully!");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
//#endregion
|
|
536
|
+
//#region src/helpers/addons/ruler-setup.ts
|
|
537
|
+
async function setupRuler(config) {
|
|
538
|
+
const { packageManager, projectDir } = config;
|
|
539
|
+
try {
|
|
540
|
+
log.info("Setting up Ruler...");
|
|
541
|
+
const rulerDir = path.join(projectDir, ".ruler");
|
|
542
|
+
if (!await fs.pathExists(rulerDir)) {
|
|
543
|
+
log.error(pc.red("Ruler template directory not found. Please ensure ruler addon is properly installed."));
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const selectedEditors = await autocompleteMultiselect({
|
|
547
|
+
message: "Select AI assistants for Ruler",
|
|
548
|
+
options: Object.entries({
|
|
549
|
+
agentsmd: { label: "Agents.md" },
|
|
550
|
+
aider: { label: "Aider" },
|
|
551
|
+
amazonqcli: { label: "Amazon Q CLI" },
|
|
552
|
+
amp: { label: "AMP" },
|
|
553
|
+
antigravity: { label: "Antigravity" },
|
|
554
|
+
augmentcode: { label: "AugmentCode" },
|
|
555
|
+
claude: { label: "Claude Code" },
|
|
556
|
+
cline: { label: "Cline" },
|
|
557
|
+
codex: { label: "OpenAI Codex CLI" },
|
|
558
|
+
copilot: { label: "GitHub Copilot" },
|
|
559
|
+
crush: { label: "Crush" },
|
|
560
|
+
cursor: { label: "Cursor" },
|
|
561
|
+
firebase: { label: "Firebase Studio" },
|
|
562
|
+
firebender: { label: "Firebender" },
|
|
563
|
+
"gemini-cli": { label: "Gemini CLI" },
|
|
564
|
+
goose: { label: "Goose" },
|
|
565
|
+
jules: { label: "Jules" },
|
|
566
|
+
junie: { label: "Junie" },
|
|
567
|
+
kilocode: { label: "Kilo Code" },
|
|
568
|
+
kiro: { label: "Kiro" },
|
|
569
|
+
mistral: { label: "Mistral" },
|
|
570
|
+
opencode: { label: "OpenCode" },
|
|
571
|
+
openhands: { label: "Open Hands" },
|
|
572
|
+
qwen: { label: "Qwen" },
|
|
573
|
+
roo: { label: "RooCode" },
|
|
574
|
+
trae: { label: "Trae AI" },
|
|
575
|
+
warp: { label: "Warp" },
|
|
576
|
+
windsurf: { label: "Windsurf" },
|
|
577
|
+
zed: { label: "Zed" }
|
|
578
|
+
}).map(([key, v]) => ({
|
|
579
|
+
value: key,
|
|
580
|
+
label: v.label
|
|
581
|
+
})),
|
|
582
|
+
required: false
|
|
583
|
+
});
|
|
584
|
+
if (isCancel(selectedEditors)) return exitCancelled("Operation cancelled");
|
|
585
|
+
if (selectedEditors.length === 0) {
|
|
586
|
+
log.info("No AI assistants selected. To apply rules later, run:");
|
|
587
|
+
log.info(pc.cyan(`${getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only")}`));
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const configFile = path.join(rulerDir, "ruler.toml");
|
|
591
|
+
let updatedConfig = await fs.readFile(configFile, "utf-8");
|
|
592
|
+
const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
|
|
593
|
+
updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
|
|
594
|
+
await fs.writeFile(configFile, updatedConfig);
|
|
595
|
+
await addRulerScriptToPackageJson(projectDir, packageManager);
|
|
596
|
+
const s = spinner();
|
|
597
|
+
s.start("Applying rules with Ruler...");
|
|
598
|
+
try {
|
|
599
|
+
const rulerApplyArgs = getPackageExecutionArgs(packageManager, `@intellectronica/ruler@latest apply --agents ${selectedEditors.join(",")} --local-only`);
|
|
600
|
+
await $({
|
|
601
|
+
cwd: projectDir,
|
|
602
|
+
env: { CI: "true" }
|
|
603
|
+
})`${rulerApplyArgs}`;
|
|
604
|
+
s.stop("Applied rules with Ruler");
|
|
605
|
+
} catch {
|
|
606
|
+
s.stop(pc.red("Failed to apply rules"));
|
|
607
|
+
}
|
|
608
|
+
} catch (error) {
|
|
609
|
+
log.error(pc.red("Failed to set up Ruler"));
|
|
610
|
+
if (error instanceof Error) console.error(pc.red(error.message));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
async function addRulerScriptToPackageJson(projectDir, packageManager) {
|
|
614
|
+
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
|
615
|
+
if (!await fs.pathExists(rootPackageJsonPath)) {
|
|
616
|
+
log.warn("Root package.json not found, skipping ruler:apply script addition");
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const packageJson = await fs.readJson(rootPackageJsonPath);
|
|
620
|
+
if (!packageJson.scripts) packageJson.scripts = {};
|
|
621
|
+
const rulerApplyCommand = getPackageExecutionCommand(packageManager, "@intellectronica/ruler@latest apply --local-only");
|
|
622
|
+
packageJson.scripts["ruler:apply"] = rulerApplyCommand;
|
|
623
|
+
await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region src/helpers/addons/skills-setup.ts
|
|
628
|
+
const SKILL_SOURCES = {
|
|
629
|
+
"vercel-labs/agent-skills": { label: "Vercel Agent Skills" },
|
|
630
|
+
"vercel/ai": { label: "Vercel AI SDK" },
|
|
631
|
+
"vercel/turborepo": { label: "Turborepo" },
|
|
632
|
+
"yusukebe/hono-skill": { label: "Hono Backend" },
|
|
633
|
+
"vercel-labs/next-skills": { label: "Next.js Best Practices" },
|
|
634
|
+
"nuxt/ui": { label: "Nuxt UI" },
|
|
635
|
+
"heroui-inc/heroui": { label: "HeroUI Native" },
|
|
636
|
+
"better-auth/skills": { label: "Better Auth" },
|
|
637
|
+
"neondatabase/agent-skills": { label: "Neon Database" },
|
|
638
|
+
"supabase/agent-skills": { label: "Supabase" },
|
|
639
|
+
"planetscale/database-skills": { label: "PlanetScale" },
|
|
640
|
+
"expo/skills": { label: "Expo" },
|
|
641
|
+
"prisma/skills": { label: "Prisma" },
|
|
642
|
+
"elysiajs/skills": { label: "ElysiaJS" },
|
|
643
|
+
"waynesutton/convexskills": { label: "Convex" },
|
|
644
|
+
"msmps/opentui-skill": { label: "OpenTUI Platform" },
|
|
645
|
+
"haydenbleasel/ultracite": { label: "Ultracite" }
|
|
646
|
+
};
|
|
647
|
+
const AVAILABLE_AGENTS = [
|
|
648
|
+
{
|
|
649
|
+
value: "cursor",
|
|
650
|
+
label: "Cursor"
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
value: "claude-code",
|
|
654
|
+
label: "Claude Code"
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
value: "cline",
|
|
658
|
+
label: "Cline"
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
value: "github-copilot",
|
|
662
|
+
label: "GitHub Copilot"
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
value: "codex",
|
|
666
|
+
label: "Codex"
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
value: "opencode",
|
|
670
|
+
label: "OpenCode"
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
value: "windsurf",
|
|
674
|
+
label: "Windsurf"
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
value: "goose",
|
|
678
|
+
label: "Goose"
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
value: "roo",
|
|
682
|
+
label: "Roo Code"
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
value: "kilo",
|
|
686
|
+
label: "Kilo Code"
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
value: "gemini-cli",
|
|
690
|
+
label: "Gemini CLI"
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
value: "antigravity",
|
|
694
|
+
label: "Antigravity"
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
value: "openhands",
|
|
698
|
+
label: "OpenHands"
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
value: "trae",
|
|
702
|
+
label: "Trae"
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
value: "amp",
|
|
706
|
+
label: "Amp"
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
value: "pi",
|
|
710
|
+
label: "Pi"
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
value: "qoder",
|
|
714
|
+
label: "Qoder"
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
value: "qwen-code",
|
|
718
|
+
label: "Qwen Code"
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
value: "kiro-cli",
|
|
722
|
+
label: "Kiro CLI"
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
value: "droid",
|
|
726
|
+
label: "Droid"
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
value: "command-code",
|
|
730
|
+
label: "Command Code"
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
value: "clawdbot",
|
|
734
|
+
label: "Clawdbot"
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
value: "zencoder",
|
|
738
|
+
label: "Zencoder"
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
value: "neovate",
|
|
742
|
+
label: "Neovate"
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
value: "mcpjam",
|
|
746
|
+
label: "MCPJam"
|
|
747
|
+
}
|
|
748
|
+
];
|
|
749
|
+
function hasReactBasedFrontend(frontend) {
|
|
750
|
+
return frontend.includes("react-router") || frontend.includes("react-vite") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next");
|
|
751
|
+
}
|
|
752
|
+
function hasNativeFrontend(frontend) {
|
|
753
|
+
return frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
754
|
+
}
|
|
755
|
+
function uniqueValues(values) {
|
|
756
|
+
return Array.from(new Set(values));
|
|
757
|
+
}
|
|
758
|
+
function getRecommendedSourceKeys(config) {
|
|
759
|
+
const sources = [];
|
|
760
|
+
const { frontend, backend, dbSetup, auth, examples, addons, orm } = config;
|
|
761
|
+
if (hasReactBasedFrontend(frontend)) sources.push("vercel-labs/agent-skills");
|
|
762
|
+
if (frontend.includes("next")) sources.push("vercel-labs/next-skills");
|
|
763
|
+
if (frontend.includes("nuxt")) sources.push("nuxt/ui");
|
|
764
|
+
if (frontend.includes("native-uniwind")) sources.push("heroui-inc/heroui");
|
|
765
|
+
if (hasNativeFrontend(frontend)) sources.push("expo/skills");
|
|
766
|
+
if (auth === "better-auth") sources.push("better-auth/skills");
|
|
767
|
+
if (dbSetup === "neon") sources.push("neondatabase/agent-skills");
|
|
768
|
+
if (dbSetup === "supabase") sources.push("supabase/agent-skills");
|
|
769
|
+
if (dbSetup === "planetscale") sources.push("planetscale/database-skills");
|
|
770
|
+
if (orm === "prisma" || dbSetup === "prisma-postgres") sources.push("prisma/skills");
|
|
771
|
+
if (examples.includes("ai")) sources.push("vercel/ai");
|
|
772
|
+
if (addons.includes("turborepo")) sources.push("vercel/turborepo");
|
|
773
|
+
if (backend === "hono") sources.push("yusukebe/hono-skill");
|
|
774
|
+
if (backend === "elysia") sources.push("elysiajs/skills");
|
|
775
|
+
if (backend === "convex") sources.push("waynesutton/convexskills");
|
|
776
|
+
if (addons.includes("opentui")) sources.push("msmps/opentui-skill");
|
|
777
|
+
if (addons.includes("ultracite")) sources.push("haydenbleasel/ultracite");
|
|
778
|
+
return sources;
|
|
779
|
+
}
|
|
780
|
+
const CURATED_SKILLS_BY_SOURCE = {
|
|
781
|
+
"vercel-labs/agent-skills": (config) => {
|
|
782
|
+
const skills = [
|
|
783
|
+
"web-design-guidelines",
|
|
784
|
+
"vercel-composition-patterns",
|
|
785
|
+
"vercel-react-best-practices"
|
|
786
|
+
];
|
|
787
|
+
if (hasNativeFrontend(config.frontend)) skills.push("vercel-react-native-skills");
|
|
788
|
+
return skills;
|
|
789
|
+
},
|
|
790
|
+
"vercel/ai": () => ["ai-sdk"],
|
|
791
|
+
"vercel/turborepo": () => ["turborepo"],
|
|
792
|
+
"yusukebe/hono-skill": () => ["hono"],
|
|
793
|
+
"vercel-labs/next-skills": () => ["next-best-practices", "next-cache-components"],
|
|
794
|
+
"nuxt/ui": () => ["nuxt-ui"],
|
|
795
|
+
"heroui-inc/heroui": () => ["heroui-native"],
|
|
796
|
+
"better-auth/skills": () => ["better-auth-best-practices"],
|
|
797
|
+
"neondatabase/agent-skills": () => ["neon-postgres"],
|
|
798
|
+
"supabase/agent-skills": () => ["supabase-postgres-best-practices"],
|
|
799
|
+
"planetscale/database-skills": (config) => {
|
|
800
|
+
if (config.dbSetup !== "planetscale") return [];
|
|
801
|
+
if (config.database === "postgres") return ["postgres", "neki"];
|
|
802
|
+
if (config.database === "mysql") return ["mysql", "vitess"];
|
|
803
|
+
return [];
|
|
804
|
+
},
|
|
805
|
+
"expo/skills": (config) => {
|
|
806
|
+
const skills = [
|
|
807
|
+
"expo-dev-client",
|
|
808
|
+
"building-native-ui",
|
|
809
|
+
"native-data-fetching",
|
|
810
|
+
"expo-deployment",
|
|
811
|
+
"upgrading-expo",
|
|
812
|
+
"expo-cicd-workflows"
|
|
813
|
+
];
|
|
814
|
+
if (config.frontend.includes("native-uniwind")) skills.push("expo-tailwind-setup");
|
|
815
|
+
return skills;
|
|
816
|
+
},
|
|
817
|
+
"prisma/skills": (config) => {
|
|
818
|
+
const skills = [];
|
|
819
|
+
if (config.orm === "prisma") skills.push("prisma-cli", "prisma-client-api", "prisma-database-setup");
|
|
820
|
+
if (config.dbSetup === "prisma-postgres") skills.push("prisma-postgres");
|
|
821
|
+
return skills;
|
|
822
|
+
},
|
|
823
|
+
"elysiajs/skills": () => ["elysiajs"],
|
|
824
|
+
"waynesutton/convexskills": () => [
|
|
825
|
+
"convex-best-practices",
|
|
826
|
+
"convex-functions",
|
|
827
|
+
"convex-schema-validator",
|
|
828
|
+
"convex-realtime",
|
|
829
|
+
"convex-http-actions",
|
|
830
|
+
"convex-cron-jobs",
|
|
831
|
+
"convex-file-storage",
|
|
832
|
+
"convex-migrations",
|
|
833
|
+
"convex-security-check"
|
|
834
|
+
],
|
|
835
|
+
"msmps/opentui-skill": () => ["opentui"],
|
|
836
|
+
"haydenbleasel/ultracite": () => ["ultracite"]
|
|
837
|
+
};
|
|
838
|
+
function getCuratedSkillNamesForSourceKey(sourceKey, config) {
|
|
839
|
+
return CURATED_SKILLS_BY_SOURCE[sourceKey](config);
|
|
840
|
+
}
|
|
841
|
+
async function setupSkills(config) {
|
|
842
|
+
if (shouldSkipExternalCommands()) return;
|
|
843
|
+
const { packageManager, projectDir } = config;
|
|
844
|
+
const btsConfig = await readBtsConfig(projectDir);
|
|
845
|
+
const fullConfig = btsConfig ? {
|
|
846
|
+
...config,
|
|
847
|
+
addons: btsConfig.addons ?? config.addons
|
|
848
|
+
} : config;
|
|
849
|
+
const recommendedSourceKeys = getRecommendedSourceKeys(fullConfig);
|
|
850
|
+
if (recommendedSourceKeys.length === 0) return;
|
|
851
|
+
const skillOptions = uniqueValues(recommendedSourceKeys).flatMap((sourceKey) => {
|
|
852
|
+
const source = SKILL_SOURCES[sourceKey];
|
|
853
|
+
return getCuratedSkillNamesForSourceKey(sourceKey, fullConfig).map((skillName) => ({
|
|
854
|
+
value: `${sourceKey}::${skillName}`,
|
|
855
|
+
label: skillName,
|
|
856
|
+
hint: source.label
|
|
857
|
+
}));
|
|
858
|
+
});
|
|
859
|
+
if (skillOptions.length === 0) return;
|
|
860
|
+
const scope = await select({
|
|
861
|
+
message: "Where should skills be installed?",
|
|
862
|
+
options: [{
|
|
863
|
+
value: "project",
|
|
864
|
+
label: "Project",
|
|
865
|
+
hint: "Writes to project config files (recommended for teams)"
|
|
866
|
+
}, {
|
|
867
|
+
value: "global",
|
|
868
|
+
label: "Global",
|
|
869
|
+
hint: "Writes to user-level config files (personal machine)"
|
|
870
|
+
}],
|
|
871
|
+
initialValue: "project"
|
|
872
|
+
});
|
|
873
|
+
if (isCancel(scope)) return;
|
|
874
|
+
const selectedSkills = await multiselect({
|
|
875
|
+
message: "Select skills to install",
|
|
876
|
+
options: skillOptions,
|
|
877
|
+
required: false,
|
|
878
|
+
initialValues: skillOptions.map((option) => option.value)
|
|
879
|
+
});
|
|
880
|
+
if (isCancel(selectedSkills) || selectedSkills.length === 0) return;
|
|
881
|
+
const selectedAgents = await multiselect({
|
|
882
|
+
message: "Select agents to install skills to",
|
|
883
|
+
options: AVAILABLE_AGENTS,
|
|
884
|
+
required: false,
|
|
885
|
+
initialValues: [
|
|
886
|
+
"cursor",
|
|
887
|
+
"claude-code",
|
|
888
|
+
"github-copilot"
|
|
889
|
+
]
|
|
890
|
+
});
|
|
891
|
+
if (isCancel(selectedAgents) || selectedAgents.length === 0) return;
|
|
892
|
+
const skillsBySource = {};
|
|
893
|
+
for (const skillKey of selectedSkills) {
|
|
894
|
+
const [source, skillName] = skillKey.split("::");
|
|
895
|
+
if (!skillsBySource[source]) skillsBySource[source] = [];
|
|
896
|
+
skillsBySource[source].push(skillName);
|
|
897
|
+
}
|
|
898
|
+
const installSpinner = spinner();
|
|
899
|
+
installSpinner.start("Installing skills...");
|
|
900
|
+
const runner = getPackageRunnerPrefix(packageManager);
|
|
901
|
+
const agentFlags = selectedAgents.flatMap((agent) => ["-a", agent]);
|
|
902
|
+
const globalFlag = scope === "global" ? ["-g"] : [];
|
|
903
|
+
for (const [source, skills] of Object.entries(skillsBySource)) {
|
|
904
|
+
const skillFlags = skills.flatMap((skill) => ["-s", skill]);
|
|
905
|
+
const args = [
|
|
906
|
+
...runner,
|
|
907
|
+
"skills@latest",
|
|
908
|
+
"add",
|
|
909
|
+
source,
|
|
910
|
+
...globalFlag,
|
|
911
|
+
...skillFlags,
|
|
912
|
+
...agentFlags,
|
|
913
|
+
"-y"
|
|
914
|
+
];
|
|
915
|
+
try {
|
|
916
|
+
await $({
|
|
917
|
+
cwd: projectDir,
|
|
918
|
+
env: { CI: "true" }
|
|
919
|
+
})`${args}`;
|
|
920
|
+
} catch (error) {
|
|
921
|
+
log.warn(pc.yellow(`Warning: Could not install skills from ${source}: ${error instanceof Error ? error.message : String(error)}`));
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
installSpinner.stop("Skills installed");
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
//#endregion
|
|
928
|
+
//#region src/helpers/addons/starlight-setup.ts
|
|
929
|
+
async function setupStarlight(config) {
|
|
930
|
+
const { packageManager, projectDir } = config;
|
|
931
|
+
const s = spinner();
|
|
932
|
+
try {
|
|
933
|
+
s.start("Setting up Starlight docs...");
|
|
934
|
+
const args = getPackageExecutionArgs(packageManager, `create-astro@latest ${[
|
|
935
|
+
"docs",
|
|
936
|
+
"--template",
|
|
937
|
+
"starlight",
|
|
938
|
+
"--no-install",
|
|
939
|
+
"--add",
|
|
940
|
+
"tailwind",
|
|
941
|
+
"--no-git",
|
|
942
|
+
"--skip-houston"
|
|
943
|
+
].join(" ")}`);
|
|
944
|
+
const appsDir = path.join(projectDir, "apps");
|
|
945
|
+
await fs.ensureDir(appsDir);
|
|
946
|
+
await $({
|
|
947
|
+
cwd: appsDir,
|
|
948
|
+
env: { CI: "true" }
|
|
949
|
+
})`${args}`;
|
|
950
|
+
s.stop("Starlight docs setup successfully!");
|
|
951
|
+
} catch (error) {
|
|
952
|
+
s.stop(pc.red("Failed to set up Starlight docs"));
|
|
953
|
+
if (error instanceof Error) consola.error(pc.red(error.message));
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
//#endregion
|
|
958
|
+
//#region src/helpers/addons/tauri-setup.ts
|
|
959
|
+
async function setupTauri(config) {
|
|
960
|
+
const { packageManager, frontend, projectDir } = config;
|
|
961
|
+
const s = spinner();
|
|
962
|
+
const clientPackageDir = path.join(projectDir, "apps/web");
|
|
963
|
+
if (!await fs.pathExists(clientPackageDir)) return;
|
|
964
|
+
try {
|
|
965
|
+
s.start("Setting up Tauri desktop app support...");
|
|
966
|
+
const hasNuxt = frontend.includes("nuxt");
|
|
967
|
+
const hasSvelte = frontend.includes("svelte");
|
|
968
|
+
const hasNext = frontend.includes("next");
|
|
969
|
+
const devUrl = `http://localhost:${getLocalWebDevPort(frontend)}`;
|
|
970
|
+
const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : frontend.includes("react-router") ? "../build/client" : frontend.includes("react-vite") ? "../dist" : "../dist";
|
|
971
|
+
const tauriArgs = [
|
|
972
|
+
"@tauri-apps/cli@latest",
|
|
973
|
+
"init",
|
|
974
|
+
`--app-name=${path.basename(projectDir)}`,
|
|
975
|
+
`--window-title=${path.basename(projectDir)}`,
|
|
976
|
+
`--frontend-dist=${frontendDist}`,
|
|
977
|
+
`--dev-url=${devUrl}`,
|
|
978
|
+
`--before-dev-command=${packageManager} run dev`,
|
|
979
|
+
`--before-build-command=${packageManager} run build`
|
|
980
|
+
];
|
|
981
|
+
const prefix = getPackageRunnerPrefix(packageManager);
|
|
982
|
+
await $({
|
|
983
|
+
cwd: clientPackageDir,
|
|
984
|
+
env: { CI: "true" }
|
|
985
|
+
})`${[...prefix, ...tauriArgs]}`;
|
|
986
|
+
s.stop("Tauri desktop app support configured successfully!");
|
|
987
|
+
} catch (error) {
|
|
988
|
+
s.stop(pc.red("Failed to set up Tauri"));
|
|
989
|
+
if (error instanceof Error) consola$1.error(pc.red(error.message));
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
//#endregion
|
|
994
|
+
//#region src/helpers/addons/tui-setup.ts
|
|
995
|
+
const TEMPLATES$1 = {
|
|
996
|
+
core: {
|
|
997
|
+
label: "Core",
|
|
998
|
+
hint: "Basic OpenTUI template"
|
|
999
|
+
},
|
|
1000
|
+
react: {
|
|
1001
|
+
label: "React",
|
|
1002
|
+
hint: "React-based OpenTUI template"
|
|
1003
|
+
},
|
|
1004
|
+
solid: {
|
|
1005
|
+
label: "Solid",
|
|
1006
|
+
hint: "SolidJS-based OpenTUI template"
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
async function setupTui(config) {
|
|
1010
|
+
const { packageManager, projectDir } = config;
|
|
1011
|
+
try {
|
|
1012
|
+
log.info("Setting up OpenTUI...");
|
|
1013
|
+
const template = await select({
|
|
1014
|
+
message: "Choose a template",
|
|
1015
|
+
options: Object.entries(TEMPLATES$1).map(([key, template$1]) => ({
|
|
1016
|
+
value: key,
|
|
1017
|
+
label: template$1.label,
|
|
1018
|
+
hint: template$1.hint
|
|
1019
|
+
})),
|
|
1020
|
+
initialValue: "core"
|
|
1021
|
+
});
|
|
1022
|
+
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
1023
|
+
const args = getPackageExecutionArgs(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
|
|
1024
|
+
const appsDir = path.join(projectDir, "apps");
|
|
1025
|
+
await fs.ensureDir(appsDir);
|
|
1026
|
+
const s = spinner();
|
|
1027
|
+
s.start("Running OpenTUI create command...");
|
|
1028
|
+
await $({
|
|
1029
|
+
cwd: appsDir,
|
|
1030
|
+
env: { CI: "true" }
|
|
1031
|
+
})`${args}`;
|
|
1032
|
+
s.stop("OpenTUI setup complete!");
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
log.error(pc.red("Failed to set up OpenTUI"));
|
|
1035
|
+
if (error instanceof Error) console.error(pc.red(error.message));
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
//#endregion
|
|
1040
|
+
//#region src/helpers/addons/ultracite-setup.ts
|
|
1041
|
+
const LINTERS = {
|
|
1042
|
+
biome: {
|
|
1043
|
+
label: "Biome",
|
|
1044
|
+
hint: "Fast formatter and linter"
|
|
1045
|
+
},
|
|
1046
|
+
eslint: {
|
|
1047
|
+
label: "ESLint",
|
|
1048
|
+
hint: "Traditional JavaScript linter"
|
|
1049
|
+
},
|
|
1050
|
+
oxlint: {
|
|
1051
|
+
label: "Oxlint",
|
|
1052
|
+
hint: "Oxidation compiler linter"
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
const EDITORS = {
|
|
1056
|
+
vscode: { label: "VS Code" },
|
|
1057
|
+
cursor: { label: "Cursor" },
|
|
1058
|
+
windsurf: { label: "Windsurf" },
|
|
1059
|
+
antigravity: { label: "Antigravity" },
|
|
1060
|
+
kiro: { label: "Kiro" },
|
|
1061
|
+
trae: { label: "Trae" },
|
|
1062
|
+
void: { label: "Void" },
|
|
1063
|
+
zed: { label: "Zed" }
|
|
1064
|
+
};
|
|
1065
|
+
const AGENTS = {
|
|
1066
|
+
claude: { label: "Claude" },
|
|
1067
|
+
codex: { label: "Codex" },
|
|
1068
|
+
jules: { label: "Jules" },
|
|
1069
|
+
copilot: { label: "GitHub Copilot" },
|
|
1070
|
+
cline: { label: "Cline" },
|
|
1071
|
+
amp: { label: "Amp" },
|
|
1072
|
+
aider: { label: "Aider" },
|
|
1073
|
+
"firebase-studio": { label: "Firebase Studio" },
|
|
1074
|
+
"open-hands": { label: "Open Hands" },
|
|
1075
|
+
gemini: { label: "Gemini" },
|
|
1076
|
+
junie: { label: "Junie" },
|
|
1077
|
+
augmentcode: { label: "AugmentCode" },
|
|
1078
|
+
"kilo-code": { label: "Kilo Code" },
|
|
1079
|
+
goose: { label: "Goose" },
|
|
1080
|
+
"roo-code": { label: "Roo Code" },
|
|
1081
|
+
warp: { label: "Warp" },
|
|
1082
|
+
droid: { label: "Droid" },
|
|
1083
|
+
opencode: { label: "OpenCode" },
|
|
1084
|
+
crush: { label: "Crush" },
|
|
1085
|
+
qwen: { label: "Qwen" },
|
|
1086
|
+
"amazon-q-cli": { label: "Amazon Q CLI" },
|
|
1087
|
+
firebender: { label: "Firebender" },
|
|
1088
|
+
"cursor-cli": { label: "Cursor CLI" },
|
|
1089
|
+
"mistral-vibe": { label: "Mistral Vibe" },
|
|
1090
|
+
vercel: { label: "Vercel" }
|
|
1091
|
+
};
|
|
1092
|
+
const HOOKS = {
|
|
1093
|
+
cursor: { label: "Cursor" },
|
|
1094
|
+
windsurf: { label: "Windsurf" },
|
|
1095
|
+
claude: { label: "Claude" }
|
|
1096
|
+
};
|
|
1097
|
+
function getFrameworksFromFrontend(frontend) {
|
|
1098
|
+
const frameworkMap = {
|
|
1099
|
+
"tanstack-router": "react",
|
|
1100
|
+
"react-router": "react",
|
|
1101
|
+
"react-vite": "react",
|
|
1102
|
+
"tanstack-start": "react",
|
|
1103
|
+
next: "next",
|
|
1104
|
+
nuxt: "vue",
|
|
1105
|
+
"native-bare": "react",
|
|
1106
|
+
"native-uniwind": "react",
|
|
1107
|
+
"native-unistyles": "react",
|
|
1108
|
+
svelte: "svelte",
|
|
1109
|
+
solid: "solid"
|
|
1110
|
+
};
|
|
1111
|
+
const frameworks = /* @__PURE__ */ new Set();
|
|
1112
|
+
for (const f of frontend) if (f !== "none" && frameworkMap[f]) frameworks.add(frameworkMap[f]);
|
|
1113
|
+
return Array.from(frameworks);
|
|
1114
|
+
}
|
|
1115
|
+
async function setupUltracite(config, gitHooks) {
|
|
1116
|
+
const { packageManager, projectDir, frontend } = config;
|
|
1117
|
+
try {
|
|
1118
|
+
log.info("Setting up Ultracite...");
|
|
1119
|
+
const result = await group({
|
|
1120
|
+
linter: () => select({
|
|
1121
|
+
message: "Choose linter/formatter",
|
|
1122
|
+
options: Object.entries(LINTERS).map(([key, linter$1]) => ({
|
|
1123
|
+
value: key,
|
|
1124
|
+
label: linter$1.label,
|
|
1125
|
+
hint: linter$1.hint
|
|
1126
|
+
})),
|
|
1127
|
+
initialValue: "biome"
|
|
1128
|
+
}),
|
|
1129
|
+
editors: () => multiselect({
|
|
1130
|
+
message: "Choose editors",
|
|
1131
|
+
options: Object.entries(EDITORS).map(([key, editor]) => ({
|
|
1132
|
+
value: key,
|
|
1133
|
+
label: editor.label
|
|
1134
|
+
})),
|
|
1135
|
+
required: true
|
|
1136
|
+
}),
|
|
1137
|
+
agents: () => multiselect({
|
|
1138
|
+
message: "Choose agents",
|
|
1139
|
+
options: Object.entries(AGENTS).map(([key, agent]) => ({
|
|
1140
|
+
value: key,
|
|
1141
|
+
label: agent.label
|
|
1142
|
+
})),
|
|
1143
|
+
required: true
|
|
1144
|
+
}),
|
|
1145
|
+
hooks: () => multiselect({
|
|
1146
|
+
message: "Choose hooks",
|
|
1147
|
+
options: Object.entries(HOOKS).map(([key, hook]) => ({
|
|
1148
|
+
value: key,
|
|
1149
|
+
label: hook.label
|
|
1150
|
+
}))
|
|
1151
|
+
})
|
|
1152
|
+
}, { onCancel: () => {
|
|
1153
|
+
exitCancelled("Operation cancelled");
|
|
1154
|
+
} });
|
|
1155
|
+
const linter = result.linter;
|
|
1156
|
+
const editors = result.editors;
|
|
1157
|
+
const agents = result.agents;
|
|
1158
|
+
const hooks = result.hooks;
|
|
1159
|
+
const frameworks = getFrameworksFromFrontend(frontend);
|
|
1160
|
+
const ultraciteArgs = [
|
|
1161
|
+
"init",
|
|
1162
|
+
"--pm",
|
|
1163
|
+
packageManager,
|
|
1164
|
+
"--linter",
|
|
1165
|
+
linter
|
|
1166
|
+
];
|
|
1167
|
+
if (frameworks.length > 0) ultraciteArgs.push("--frameworks", ...frameworks);
|
|
1168
|
+
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
1169
|
+
if (agents.length > 0) ultraciteArgs.push("--agents", ...agents);
|
|
1170
|
+
if (hooks.length > 0) ultraciteArgs.push("--hooks", ...hooks);
|
|
1171
|
+
if (gitHooks.length > 0) {
|
|
1172
|
+
const integrations = [...gitHooks];
|
|
1173
|
+
if (gitHooks.includes("husky")) integrations.push("lint-staged");
|
|
1174
|
+
ultraciteArgs.push("--integrations", ...integrations);
|
|
1175
|
+
}
|
|
1176
|
+
const args = getPackageExecutionArgs(packageManager, `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`);
|
|
1177
|
+
const s = spinner();
|
|
1178
|
+
s.start("Running Ultracite init command...");
|
|
1179
|
+
await $({
|
|
1180
|
+
cwd: projectDir,
|
|
1181
|
+
env: { CI: "true" }
|
|
1182
|
+
})`${args}`;
|
|
1183
|
+
s.stop("Ultracite setup successfully!");
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
log.error(pc.red("Failed to set up Ultracite"));
|
|
1186
|
+
if (error instanceof Error) console.error(pc.red(error.message));
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
//#endregion
|
|
1191
|
+
//#region src/helpers/addons/wxt-setup.ts
|
|
1192
|
+
const TEMPLATES = {
|
|
1193
|
+
vanilla: {
|
|
1194
|
+
label: "Vanilla",
|
|
1195
|
+
hint: "Vanilla JavaScript template"
|
|
1196
|
+
},
|
|
1197
|
+
vue: {
|
|
1198
|
+
label: "Vue",
|
|
1199
|
+
hint: "Vue.js template"
|
|
1200
|
+
},
|
|
1201
|
+
react: {
|
|
1202
|
+
label: "React",
|
|
1203
|
+
hint: "React template"
|
|
1204
|
+
},
|
|
1205
|
+
solid: {
|
|
1206
|
+
label: "Solid",
|
|
1207
|
+
hint: "SolidJS template"
|
|
1208
|
+
},
|
|
1209
|
+
svelte: {
|
|
1210
|
+
label: "Svelte",
|
|
1211
|
+
hint: "Svelte template"
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
async function setupWxt(config) {
|
|
1215
|
+
const { packageManager, projectDir } = config;
|
|
1216
|
+
try {
|
|
1217
|
+
log.info("Setting up WXT...");
|
|
1218
|
+
const template = await select({
|
|
1219
|
+
message: "Choose a template",
|
|
1220
|
+
options: Object.entries(TEMPLATES).map(([key, template$1]) => ({
|
|
1221
|
+
value: key,
|
|
1222
|
+
label: template$1.label,
|
|
1223
|
+
hint: template$1.hint
|
|
1224
|
+
})),
|
|
1225
|
+
initialValue: "react"
|
|
1226
|
+
});
|
|
1227
|
+
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
1228
|
+
const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
|
|
1229
|
+
const appsDir = path.join(projectDir, "apps");
|
|
1230
|
+
await fs.ensureDir(appsDir);
|
|
1231
|
+
const s = spinner();
|
|
1232
|
+
s.start("Running WXT init command...");
|
|
1233
|
+
await $({
|
|
1234
|
+
cwd: appsDir,
|
|
1235
|
+
env: { CI: "true" }
|
|
1236
|
+
})`${args}`;
|
|
1237
|
+
const extensionDir = path.join(projectDir, "apps", "extension");
|
|
1238
|
+
const packageJsonPath = path.join(extensionDir, "package.json");
|
|
1239
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
1240
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
1241
|
+
packageJson.name = "extension";
|
|
1242
|
+
if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port 5555`;
|
|
1243
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
1244
|
+
}
|
|
1245
|
+
s.stop("WXT setup complete!");
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
log.error(pc.red("Failed to set up WXT"));
|
|
1248
|
+
if (error instanceof Error) console.error(pc.red(error.message));
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
//#endregion
|
|
1253
|
+
//#region src/helpers/addons/addons-setup.ts
|
|
1254
|
+
async function setupAddons(config) {
|
|
1255
|
+
const warnings = [];
|
|
1256
|
+
const { addons, frontend, projectDir } = config;
|
|
1257
|
+
const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("react-vite") || frontend.includes("tanstack-router") || frontend.includes("next");
|
|
1258
|
+
const hasNuxtFrontend = frontend.includes("nuxt");
|
|
1259
|
+
const hasSvelteFrontend = frontend.includes("svelte");
|
|
1260
|
+
const hasSolidFrontend = frontend.includes("solid");
|
|
1261
|
+
const hasNextFrontend = frontend.includes("next");
|
|
1262
|
+
if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await setupTauri(config);
|
|
1263
|
+
const hasUltracite = addons.includes("ultracite");
|
|
1264
|
+
const hasBiome = addons.includes("biome");
|
|
1265
|
+
const hasHusky = addons.includes("husky");
|
|
1266
|
+
const hasLefthook = addons.includes("lefthook");
|
|
1267
|
+
const hasOxlint = addons.includes("oxlint");
|
|
1268
|
+
if (hasUltracite) {
|
|
1269
|
+
const gitHooks = [];
|
|
1270
|
+
if (hasHusky) gitHooks.push("husky");
|
|
1271
|
+
if (hasLefthook) gitHooks.push("lefthook");
|
|
1272
|
+
await setupUltracite(config, gitHooks);
|
|
1273
|
+
} else {
|
|
1274
|
+
if (hasBiome) await setupBiome(projectDir);
|
|
1275
|
+
if (hasOxlint) await setupOxlint(projectDir, config.packageManager);
|
|
1276
|
+
if (hasHusky || hasLefthook) {
|
|
1277
|
+
let linter;
|
|
1278
|
+
if (hasOxlint) linter = "oxlint";
|
|
1279
|
+
else if (hasBiome) linter = "biome";
|
|
1280
|
+
if (hasHusky) await setupHusky(projectDir, linter);
|
|
1281
|
+
if (hasLefthook) await setupLefthook(projectDir);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (addons.includes("starlight")) await setupStarlight(config);
|
|
1285
|
+
if (addons.includes("fumadocs")) await setupFumadocs(config);
|
|
1286
|
+
if (addons.includes("opentui")) await setupTui(config);
|
|
1287
|
+
if (addons.includes("wxt")) await setupWxt(config);
|
|
1288
|
+
if (addons.includes("ruler")) await setupRuler(config);
|
|
1289
|
+
if (addons.includes("mcp")) try {
|
|
1290
|
+
await setupMcp(config);
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
warnings.push(`MCP setup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1293
|
+
}
|
|
1294
|
+
if (addons.includes("skills")) try {
|
|
1295
|
+
await setupSkills(config);
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
warnings.push(`Skills setup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1298
|
+
}
|
|
1299
|
+
return warnings;
|
|
1300
|
+
}
|
|
1301
|
+
async function setupBiome(projectDir) {
|
|
1302
|
+
await addPackageDependency({
|
|
1303
|
+
devDependencies: ["@biomejs/biome"],
|
|
1304
|
+
projectDir
|
|
1305
|
+
});
|
|
1306
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
1307
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
1308
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
1309
|
+
packageJson.scripts = {
|
|
1310
|
+
...packageJson.scripts,
|
|
1311
|
+
check: "biome check --write ."
|
|
1312
|
+
};
|
|
1313
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
async function setupHusky(projectDir, linter) {
|
|
1317
|
+
await addPackageDependency({
|
|
1318
|
+
devDependencies: ["husky", "lint-staged"],
|
|
1319
|
+
projectDir
|
|
1320
|
+
});
|
|
1321
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
1322
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
1323
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
1324
|
+
packageJson.scripts = {
|
|
1325
|
+
...packageJson.scripts,
|
|
1326
|
+
prepare: "husky"
|
|
1327
|
+
};
|
|
1328
|
+
if (linter === "oxlint") packageJson["lint-staged"] = { "*": ["oxlint", "oxfmt --write"] };
|
|
1329
|
+
else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
|
|
1330
|
+
else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
|
|
1331
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
async function setupLefthook(projectDir) {
|
|
1335
|
+
await addPackageDependency({
|
|
1336
|
+
devDependencies: ["lefthook"],
|
|
1337
|
+
projectDir
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
//#endregion
|
|
1342
|
+
export { setLastPromptShownUI as _, getPackageExecutionArgs as a, UserCancelledError as c, handleError as d, didLastPromptShowUI as f, setIsFirstPrompt as g, runWithContextAsync as h, setupLefthook as i, exitCancelled as l, isSilent as m, setupBiome as n, addPackageDependency as o, isFirstPrompt as p, setupHusky as r, CLIError as s, setupAddons as t, exitWithError as u };
|