create-krispya 0.5.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -242
- package/dist/index.cjs +2969 -17
- package/dist/index.d.cts +23 -7
- package/dist/index.d.mts +23 -7
- package/dist/index.d.ts +23 -7
- package/dist/index.mjs +2953 -2
- package/package.json +29 -15
- package/LICENSE +0 -15
- package/dist/chunks/index.cjs +0 -2766
- package/dist/chunks/index.mjs +0 -2741
- package/dist/cli.cjs +0 -2037
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.mts +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.mjs +0 -2016
package/dist/cli.mjs
DELETED
|
@@ -1,2016 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'module';
|
|
3
|
-
import { cwd } from 'process';
|
|
4
|
-
import { join, dirname, resolve } from 'path';
|
|
5
|
-
import { access, constants, mkdir, writeFile, unlink, readFile } from 'fs/promises';
|
|
6
|
-
import { constants as constants$1 } from 'fs';
|
|
7
|
-
import { Command } from 'commander';
|
|
8
|
-
import * as p from '@clack/prompts';
|
|
9
|
-
import color from 'chalk';
|
|
10
|
-
import { fetch } from 'undici';
|
|
11
|
-
import { spawn } from 'child_process';
|
|
12
|
-
import { g as getBaseTemplate, a as getLanguageFromTemplate, b as generateRandomName, c as generateTypescriptConfigPackage, d as generateOxlintConfigPackage, e as generateEslintConfigPackage, f as generateOxfmtConfigPackage, h as generatePrettierConfigPackage, i as generateVscodeFiles, j as generateAiFiles, k as getLatestNpmVersion, l as generate, m as getLatestPnpmVersion, n as getLatestYarnVersion, o as getLatestNpmCliVersion, p as getLatestNodeVersion, v as validatePackageName, q as parseWorkspaceYamlContent } from './chunks/index.mjs';
|
|
13
|
-
import Conf from 'conf';
|
|
14
|
-
|
|
15
|
-
const editorNames = {
|
|
16
|
-
cursor: "Cursor",
|
|
17
|
-
code: "VS Code",
|
|
18
|
-
webstorm: "WebStorm",
|
|
19
|
-
skip: "Skip"
|
|
20
|
-
};
|
|
21
|
-
function openInEditor(editor, path, reuseWindow) {
|
|
22
|
-
return new Promise((resolve, reject) => {
|
|
23
|
-
const isWindows = process.platform === "win32";
|
|
24
|
-
const useReuseFlag = reuseWindow && (editor === "cursor" || editor === "code");
|
|
25
|
-
const args = useReuseFlag ? ["-r", path] : [path];
|
|
26
|
-
const child = isWindows ? spawn(`${editor} ${useReuseFlag ? "-r " : ""}"${path}"`, {
|
|
27
|
-
detached: true,
|
|
28
|
-
stdio: "ignore",
|
|
29
|
-
shell: true
|
|
30
|
-
}) : spawn(editor, args, {
|
|
31
|
-
detached: true,
|
|
32
|
-
stdio: "ignore"
|
|
33
|
-
});
|
|
34
|
-
child.on("error", reject);
|
|
35
|
-
child.unref();
|
|
36
|
-
setTimeout(resolve, 100);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function formatConfigSummary(options) {
|
|
41
|
-
const lines = [];
|
|
42
|
-
const VALUE_COL = 27;
|
|
43
|
-
const formatRow = (label, value, indent = "") => {
|
|
44
|
-
const fullLabel = indent + label;
|
|
45
|
-
const dotCount = Math.max(1, VALUE_COL - fullLabel.length - 1);
|
|
46
|
-
const dots = color.gray(".".repeat(dotCount));
|
|
47
|
-
return `${indent}${label} ${dots} ${value}`;
|
|
48
|
-
};
|
|
49
|
-
const formatLanguage = (lang) => {
|
|
50
|
-
return lang === "typescript" ? "TypeScript" : lang === "javascript" ? "JavaScript" : lang;
|
|
51
|
-
};
|
|
52
|
-
const projectType = options.projectType ?? "app";
|
|
53
|
-
const baseTemplate = options.template ? getBaseTemplate(options.template) : "vanilla";
|
|
54
|
-
if (baseTemplate === "react") {
|
|
55
|
-
lines.push(formatRow("Framework", "React"));
|
|
56
|
-
} else if (baseTemplate === "r3f") {
|
|
57
|
-
lines.push(formatRow("Framework", "React Three Fiber"));
|
|
58
|
-
}
|
|
59
|
-
const language = options.template ? getLanguageFromTemplate(options.template) : "typescript";
|
|
60
|
-
lines.push(formatRow("Language", formatLanguage(language)));
|
|
61
|
-
if (projectType === "library") {
|
|
62
|
-
lines.push(formatRow("Bundler", options.libraryBundler ?? "unbuild"));
|
|
63
|
-
} else {
|
|
64
|
-
lines.push(formatRow("Bundler", "vite"));
|
|
65
|
-
}
|
|
66
|
-
lines.push(formatRow("Node version", options.nodeVersion || "latest"));
|
|
67
|
-
lines.push(formatRow("Package manager", options.packageManager || "pnpm"));
|
|
68
|
-
if (options.packageManager === "pnpm") {
|
|
69
|
-
const versionManaged = options.pnpmManageVersions ? "yes" : "no";
|
|
70
|
-
lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
|
|
71
|
-
}
|
|
72
|
-
if (options.linter) {
|
|
73
|
-
lines.push(formatRow("Linter", options.linter));
|
|
74
|
-
}
|
|
75
|
-
if (options.formatter) {
|
|
76
|
-
lines.push(formatRow("Formatter", options.formatter));
|
|
77
|
-
}
|
|
78
|
-
const testing = options.testing ?? (projectType === "library" ? "vitest" : "none");
|
|
79
|
-
lines.push(formatRow("Testing", testing));
|
|
80
|
-
if (options.template && getBaseTemplate(options.template) === "r3f") {
|
|
81
|
-
const integrationNames = [
|
|
82
|
-
options.drei && "drei",
|
|
83
|
-
options.handle && "handle",
|
|
84
|
-
options.leva && "leva",
|
|
85
|
-
options.postprocessing && "postproc",
|
|
86
|
-
options.rapier && "rapier",
|
|
87
|
-
options.xr && "xr",
|
|
88
|
-
options.uikit && "uikit",
|
|
89
|
-
options.offscreen && "offscreen",
|
|
90
|
-
options.zustand && "zustand",
|
|
91
|
-
options.koota && "koota",
|
|
92
|
-
options.triplex && "triplex",
|
|
93
|
-
options.viverse && "viverse"
|
|
94
|
-
].filter(Boolean);
|
|
95
|
-
lines.push("");
|
|
96
|
-
lines.push(color.dim("Integrations"));
|
|
97
|
-
for (let i = 0; i < integrationNames.length; i += 2) {
|
|
98
|
-
const left = `${color.green("\u25CF")} ${integrationNames[i]}`;
|
|
99
|
-
const right = integrationNames[i + 1] ? `${color.green("\u25CF")} ${integrationNames[i + 1]}` : "";
|
|
100
|
-
const spacing = " ".repeat(Math.max(1, 16 - integrationNames[i].length));
|
|
101
|
-
lines.push(` ${left}${spacing}${right}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return lines.join("\n");
|
|
105
|
-
}
|
|
106
|
-
function formatMonorepoConfigSummary(options) {
|
|
107
|
-
const lines = [];
|
|
108
|
-
const VALUE_COL = 27;
|
|
109
|
-
const formatRow = (label, value, indent = "") => {
|
|
110
|
-
const fullLabel = indent + label;
|
|
111
|
-
const dotCount = Math.max(1, VALUE_COL - fullLabel.length - 1);
|
|
112
|
-
const dots = color.gray(".".repeat(dotCount));
|
|
113
|
-
return `${indent}${label} ${dots} ${value}`;
|
|
114
|
-
};
|
|
115
|
-
lines.push(formatRow("Node version", options.nodeVersion || "latest"));
|
|
116
|
-
lines.push(formatRow("Package manager", options.packageManager || "pnpm"));
|
|
117
|
-
if (options.packageManager === "pnpm") {
|
|
118
|
-
const versionManaged = options.pnpmManageVersions ? "yes" : "no";
|
|
119
|
-
lines.push(formatRow("\u21B3 Version managed", versionManaged, ""));
|
|
120
|
-
}
|
|
121
|
-
lines.push(formatRow("Linter", options.linter));
|
|
122
|
-
lines.push(formatRow("Formatter", options.formatter));
|
|
123
|
-
return lines.join("\n");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const config = new Conf({
|
|
127
|
-
projectName: "create-krispya"
|
|
128
|
-
});
|
|
129
|
-
function getPreferredEditor() {
|
|
130
|
-
return config.get("preferredEditor");
|
|
131
|
-
}
|
|
132
|
-
function setPreferredEditor(editor) {
|
|
133
|
-
config.set("preferredEditor", editor);
|
|
134
|
-
}
|
|
135
|
-
function getReuseWindow() {
|
|
136
|
-
return config.get("reuseWindow") ?? false;
|
|
137
|
-
}
|
|
138
|
-
function setReuseWindow(reuse) {
|
|
139
|
-
config.set("reuseWindow", reuse);
|
|
140
|
-
}
|
|
141
|
-
function getAiFiles() {
|
|
142
|
-
return config.get("aiFiles");
|
|
143
|
-
}
|
|
144
|
-
function setAiFiles(files) {
|
|
145
|
-
config.set("aiFiles", files);
|
|
146
|
-
}
|
|
147
|
-
function clearConfig() {
|
|
148
|
-
config.clear();
|
|
149
|
-
}
|
|
150
|
-
function getConfigPath() {
|
|
151
|
-
return config.path;
|
|
152
|
-
}
|
|
153
|
-
function getCustomTemplates() {
|
|
154
|
-
return config.get("customTemplates") ?? {};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function getDefaultOptions(template, name, projectType = "app", libraryBundler, integrations, inheritedTooling) {
|
|
158
|
-
const baseTemplate = getBaseTemplate(template);
|
|
159
|
-
const base = {
|
|
160
|
-
name,
|
|
161
|
-
template,
|
|
162
|
-
projectType,
|
|
163
|
-
libraryBundler: projectType === "library" ? libraryBundler ?? "unbuild" : void 0,
|
|
164
|
-
packageManager: "pnpm",
|
|
165
|
-
pnpmManageVersions: true,
|
|
166
|
-
nodeVersion: "latest",
|
|
167
|
-
linter: inheritedTooling?.linter ?? "oxlint",
|
|
168
|
-
formatter: inheritedTooling?.formatter ?? "oxfmt",
|
|
169
|
-
// Libraries get vitest by default, apps don't
|
|
170
|
-
testing: projectType === "library" ? "vitest" : "none"
|
|
171
|
-
};
|
|
172
|
-
if (baseTemplate === "r3f" && integrations) {
|
|
173
|
-
return {
|
|
174
|
-
...base,
|
|
175
|
-
drei: integrations.includes("drei") ? {} : void 0,
|
|
176
|
-
handle: integrations.includes("handle") ? {} : void 0,
|
|
177
|
-
leva: integrations.includes("leva") ? {} : void 0,
|
|
178
|
-
postprocessing: integrations.includes("postprocessing") ? {} : void 0,
|
|
179
|
-
rapier: integrations.includes("rapier") ? {} : void 0,
|
|
180
|
-
xr: integrations.includes("xr") ? {} : void 0,
|
|
181
|
-
uikit: integrations.includes("uikit") ? {} : void 0,
|
|
182
|
-
offscreen: integrations.includes("offscreen") ? {} : void 0,
|
|
183
|
-
zustand: integrations.includes("zustand") ? {} : void 0,
|
|
184
|
-
koota: integrations.includes("koota") ? {} : void 0,
|
|
185
|
-
triplex: integrations.includes("triplex") ? {} : void 0,
|
|
186
|
-
viverse: integrations.includes("viverse") ? {} : void 0
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
return base;
|
|
190
|
-
}
|
|
191
|
-
function getDefaultProjectName(template) {
|
|
192
|
-
const base = getBaseTemplate(template);
|
|
193
|
-
switch (base) {
|
|
194
|
-
case "vanilla":
|
|
195
|
-
return `vanilla-${generateRandomName()}`;
|
|
196
|
-
case "react":
|
|
197
|
-
return `react-${generateRandomName()}`;
|
|
198
|
-
case "r3f":
|
|
199
|
-
return `react-three-${generateRandomName()}`;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
async function promptForR3fIntegrations() {
|
|
203
|
-
const selected = await p.multiselect({
|
|
204
|
-
message: "R3F integrations",
|
|
205
|
-
options: [
|
|
206
|
-
{ value: "drei", label: "Drei" },
|
|
207
|
-
{ value: "handle", label: "Handle" },
|
|
208
|
-
{ value: "leva", label: "Leva" },
|
|
209
|
-
{ value: "postprocessing", label: "Postprocessing" },
|
|
210
|
-
{ value: "rapier", label: "Rapier" },
|
|
211
|
-
{ value: "xr", label: "XR" },
|
|
212
|
-
{ value: "uikit", label: "UIKit" },
|
|
213
|
-
{ value: "offscreen", label: "Offscreen" },
|
|
214
|
-
{ value: "zustand", label: "Zustand" },
|
|
215
|
-
{ value: "koota", label: "Koota" },
|
|
216
|
-
{ value: "triplex", label: "Triplex" },
|
|
217
|
-
{ value: "viverse", label: "Viverse" }
|
|
218
|
-
],
|
|
219
|
-
initialValues: ["drei"],
|
|
220
|
-
required: false
|
|
221
|
-
});
|
|
222
|
-
if (p.isCancel(selected)) {
|
|
223
|
-
p.cancel("Operation cancelled.");
|
|
224
|
-
process.exit(0);
|
|
225
|
-
}
|
|
226
|
-
return selected;
|
|
227
|
-
}
|
|
228
|
-
async function promptForCustomization(template, name, projectType, integrations, inheritedTooling) {
|
|
229
|
-
let libraryBundler;
|
|
230
|
-
if (projectType === "library") {
|
|
231
|
-
const bundler = await p.select({
|
|
232
|
-
message: "Library bundler",
|
|
233
|
-
options: [
|
|
234
|
-
{ value: "unbuild", label: "unbuild", hint: "unjs, simple config" },
|
|
235
|
-
{ value: "tsdown", label: "tsdown", hint: "fast, esbuild-based" }
|
|
236
|
-
],
|
|
237
|
-
initialValue: "unbuild"
|
|
238
|
-
});
|
|
239
|
-
if (p.isCancel(bundler)) {
|
|
240
|
-
p.cancel("Operation cancelled.");
|
|
241
|
-
process.exit(0);
|
|
242
|
-
}
|
|
243
|
-
libraryBundler = bundler;
|
|
244
|
-
}
|
|
245
|
-
const nodeVersion = await p.text({
|
|
246
|
-
message: "Node.js version",
|
|
247
|
-
placeholder: "latest",
|
|
248
|
-
defaultValue: "latest",
|
|
249
|
-
validate: (value) => {
|
|
250
|
-
if (!value.length) return "Required";
|
|
251
|
-
if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
|
|
252
|
-
return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
if (p.isCancel(nodeVersion)) {
|
|
257
|
-
p.cancel("Operation cancelled.");
|
|
258
|
-
process.exit(0);
|
|
259
|
-
}
|
|
260
|
-
const packageManager = await p.select({
|
|
261
|
-
message: "Package manager",
|
|
262
|
-
options: [
|
|
263
|
-
{ value: "pnpm", label: "pnpm" },
|
|
264
|
-
{ value: "npm", label: "npm" },
|
|
265
|
-
{ value: "yarn", label: "yarn" },
|
|
266
|
-
{ value: "custom", label: "Other (custom)" }
|
|
267
|
-
],
|
|
268
|
-
initialValue: "pnpm"
|
|
269
|
-
});
|
|
270
|
-
if (p.isCancel(packageManager)) {
|
|
271
|
-
p.cancel("Operation cancelled.");
|
|
272
|
-
process.exit(0);
|
|
273
|
-
}
|
|
274
|
-
let finalPackageManager = packageManager;
|
|
275
|
-
if (packageManager === "custom") {
|
|
276
|
-
const customPm = await p.text({
|
|
277
|
-
message: "Enter package manager command",
|
|
278
|
-
validate: (value) => {
|
|
279
|
-
if (!value.length) return "Required";
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
if (p.isCancel(customPm)) {
|
|
283
|
-
p.cancel("Operation cancelled.");
|
|
284
|
-
process.exit(0);
|
|
285
|
-
}
|
|
286
|
-
finalPackageManager = customPm;
|
|
287
|
-
}
|
|
288
|
-
let pnpmManageVersions = true;
|
|
289
|
-
if (packageManager === "pnpm") {
|
|
290
|
-
const managePnpm = await p.confirm({
|
|
291
|
-
message: "Enable manage-package-manager-versions?",
|
|
292
|
-
initialValue: true
|
|
293
|
-
});
|
|
294
|
-
if (p.isCancel(managePnpm)) {
|
|
295
|
-
p.cancel("Operation cancelled.");
|
|
296
|
-
process.exit(0);
|
|
297
|
-
}
|
|
298
|
-
pnpmManageVersions = managePnpm;
|
|
299
|
-
}
|
|
300
|
-
let linter = inheritedTooling?.linter ?? "oxlint";
|
|
301
|
-
let formatter = inheritedTooling?.formatter ?? "oxfmt";
|
|
302
|
-
if (!inheritedTooling?.linter) {
|
|
303
|
-
const linterChoice = await p.select({
|
|
304
|
-
message: "Linter",
|
|
305
|
-
options: [
|
|
306
|
-
{ value: "oxlint", label: "Oxlint", hint: "fast, from OXC" },
|
|
307
|
-
{ value: "eslint", label: "ESLint", hint: "classic" },
|
|
308
|
-
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
309
|
-
],
|
|
310
|
-
initialValue: "oxlint"
|
|
311
|
-
});
|
|
312
|
-
if (p.isCancel(linterChoice)) {
|
|
313
|
-
p.cancel("Operation cancelled.");
|
|
314
|
-
process.exit(0);
|
|
315
|
-
}
|
|
316
|
-
linter = linterChoice;
|
|
317
|
-
}
|
|
318
|
-
if (!inheritedTooling?.formatter) {
|
|
319
|
-
const formatterChoice = await p.select({
|
|
320
|
-
message: "Formatter",
|
|
321
|
-
options: [
|
|
322
|
-
{ value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
|
|
323
|
-
{ value: "prettier", label: "Prettier", hint: "classic" },
|
|
324
|
-
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
325
|
-
],
|
|
326
|
-
initialValue: "oxfmt"
|
|
327
|
-
});
|
|
328
|
-
if (p.isCancel(formatterChoice)) {
|
|
329
|
-
p.cancel("Operation cancelled.");
|
|
330
|
-
process.exit(0);
|
|
331
|
-
}
|
|
332
|
-
formatter = formatterChoice;
|
|
333
|
-
}
|
|
334
|
-
const testing = await p.select({
|
|
335
|
-
message: "Testing",
|
|
336
|
-
options: [
|
|
337
|
-
{ value: "vitest", label: "Vitest", hint: "fast, Vite-native" },
|
|
338
|
-
{ value: "none", label: "None" }
|
|
339
|
-
],
|
|
340
|
-
initialValue: projectType === "library" ? "vitest" : "none"
|
|
341
|
-
});
|
|
342
|
-
if (p.isCancel(testing)) {
|
|
343
|
-
p.cancel("Operation cancelled.");
|
|
344
|
-
process.exit(0);
|
|
345
|
-
}
|
|
346
|
-
const language = await p.select({
|
|
347
|
-
message: "Language",
|
|
348
|
-
options: [
|
|
349
|
-
{ value: "typescript", label: "TypeScript" },
|
|
350
|
-
{ value: "javascript", label: "JavaScript" }
|
|
351
|
-
],
|
|
352
|
-
initialValue: "typescript"
|
|
353
|
-
});
|
|
354
|
-
if (p.isCancel(language)) {
|
|
355
|
-
p.cancel("Operation cancelled.");
|
|
356
|
-
process.exit(0);
|
|
357
|
-
}
|
|
358
|
-
const baseTemplate = getBaseTemplate(template);
|
|
359
|
-
const finalTemplate = language === "javascript" ? `${baseTemplate}-js` : baseTemplate;
|
|
360
|
-
const base = {
|
|
361
|
-
name,
|
|
362
|
-
template: finalTemplate,
|
|
363
|
-
projectType,
|
|
364
|
-
libraryBundler: projectType === "library" ? libraryBundler : void 0,
|
|
365
|
-
nodeVersion,
|
|
366
|
-
packageManager: finalPackageManager,
|
|
367
|
-
pnpmManageVersions,
|
|
368
|
-
linter,
|
|
369
|
-
formatter,
|
|
370
|
-
testing
|
|
371
|
-
};
|
|
372
|
-
if (baseTemplate === "r3f" && integrations) {
|
|
373
|
-
return {
|
|
374
|
-
...base,
|
|
375
|
-
drei: integrations.includes("drei") ? {} : void 0,
|
|
376
|
-
handle: integrations.includes("handle") ? {} : void 0,
|
|
377
|
-
leva: integrations.includes("leva") ? {} : void 0,
|
|
378
|
-
postprocessing: integrations.includes("postprocessing") ? {} : void 0,
|
|
379
|
-
rapier: integrations.includes("rapier") ? {} : void 0,
|
|
380
|
-
xr: integrations.includes("xr") ? {} : void 0,
|
|
381
|
-
uikit: integrations.includes("uikit") ? {} : void 0,
|
|
382
|
-
offscreen: integrations.includes("offscreen") ? {} : void 0,
|
|
383
|
-
zustand: integrations.includes("zustand") ? {} : void 0,
|
|
384
|
-
koota: integrations.includes("koota") ? {} : void 0,
|
|
385
|
-
triplex: integrations.includes("triplex") ? {} : void 0,
|
|
386
|
-
viverse: integrations.includes("viverse") ? {} : void 0
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
return base;
|
|
390
|
-
}
|
|
391
|
-
async function promptForInitialPackage() {
|
|
392
|
-
const choice = await p.select({
|
|
393
|
-
message: "Add an initial package?",
|
|
394
|
-
options: [
|
|
395
|
-
{ value: "app", label: "Application" },
|
|
396
|
-
{ value: "library", label: "Library" },
|
|
397
|
-
{ value: "skip", label: "Skip" }
|
|
398
|
-
],
|
|
399
|
-
initialValue: "app"
|
|
400
|
-
});
|
|
401
|
-
if (p.isCancel(choice)) {
|
|
402
|
-
p.cancel("Operation cancelled.");
|
|
403
|
-
process.exit(0);
|
|
404
|
-
}
|
|
405
|
-
return choice;
|
|
406
|
-
}
|
|
407
|
-
function getDefaultMonorepoOptions(name) {
|
|
408
|
-
return {
|
|
409
|
-
name,
|
|
410
|
-
projectType: "monorepo",
|
|
411
|
-
packageManager: "pnpm",
|
|
412
|
-
pnpmManageVersions: true,
|
|
413
|
-
nodeVersion: "latest",
|
|
414
|
-
linter: "oxlint",
|
|
415
|
-
formatter: "oxfmt"
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
async function promptForMonorepoCustomization(name) {
|
|
419
|
-
const nodeVersion = await p.text({
|
|
420
|
-
message: "Node.js version",
|
|
421
|
-
placeholder: "latest",
|
|
422
|
-
defaultValue: "latest",
|
|
423
|
-
validate: (value) => {
|
|
424
|
-
if (!value.length) return "Required";
|
|
425
|
-
if (value !== "latest" && !/^\d+(\.\d+(\.\d+)?)?$/.test(value)) {
|
|
426
|
-
return 'Must be "latest" or a valid semver (e.g., "22" or "22.13.0")';
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
if (p.isCancel(nodeVersion)) {
|
|
431
|
-
p.cancel("Operation cancelled.");
|
|
432
|
-
process.exit(0);
|
|
433
|
-
}
|
|
434
|
-
const managePnpm = await p.confirm({
|
|
435
|
-
message: "Enable manage-package-manager-versions?",
|
|
436
|
-
initialValue: true
|
|
437
|
-
});
|
|
438
|
-
if (p.isCancel(managePnpm)) {
|
|
439
|
-
p.cancel("Operation cancelled.");
|
|
440
|
-
process.exit(0);
|
|
441
|
-
}
|
|
442
|
-
const linter = await p.select({
|
|
443
|
-
message: "Linter",
|
|
444
|
-
options: [
|
|
445
|
-
{ value: "oxlint", label: "Oxlint", hint: "fast, from OXC" },
|
|
446
|
-
{ value: "eslint", label: "ESLint", hint: "classic" },
|
|
447
|
-
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
448
|
-
],
|
|
449
|
-
initialValue: "oxlint"
|
|
450
|
-
});
|
|
451
|
-
if (p.isCancel(linter)) {
|
|
452
|
-
p.cancel("Operation cancelled.");
|
|
453
|
-
process.exit(0);
|
|
454
|
-
}
|
|
455
|
-
const formatter = await p.select({
|
|
456
|
-
message: "Formatter",
|
|
457
|
-
options: [
|
|
458
|
-
{ value: "oxfmt", label: "Oxfmt", hint: "fast, Prettier-compatible" },
|
|
459
|
-
{ value: "prettier", label: "Prettier", hint: "classic" },
|
|
460
|
-
{ value: "biome", label: "Biome", hint: "all-in-one" }
|
|
461
|
-
],
|
|
462
|
-
initialValue: "oxfmt"
|
|
463
|
-
});
|
|
464
|
-
if (p.isCancel(formatter)) {
|
|
465
|
-
p.cancel("Operation cancelled.");
|
|
466
|
-
process.exit(0);
|
|
467
|
-
}
|
|
468
|
-
return {
|
|
469
|
-
name,
|
|
470
|
-
projectType: "monorepo",
|
|
471
|
-
nodeVersion,
|
|
472
|
-
packageManager: "pnpm",
|
|
473
|
-
pnpmManageVersions: managePnpm,
|
|
474
|
-
linter,
|
|
475
|
-
formatter
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
async function promptForMonorepo(workspaceName) {
|
|
479
|
-
const defaultOptions = getDefaultMonorepoOptions(workspaceName);
|
|
480
|
-
p.note(
|
|
481
|
-
formatMonorepoConfigSummary({
|
|
482
|
-
name: defaultOptions.name,
|
|
483
|
-
nodeVersion: defaultOptions.nodeVersion ?? "latest",
|
|
484
|
-
packageManager: defaultOptions.packageManager ?? "pnpm",
|
|
485
|
-
pnpmManageVersions: defaultOptions.pnpmManageVersions,
|
|
486
|
-
linter: defaultOptions.linter ?? "oxlint",
|
|
487
|
-
formatter: defaultOptions.formatter ?? "oxfmt"
|
|
488
|
-
}),
|
|
489
|
-
"Workspace Configuration"
|
|
490
|
-
);
|
|
491
|
-
const proceed = await p.confirm({
|
|
492
|
-
message: "Proceed with these settings?",
|
|
493
|
-
initialValue: true
|
|
494
|
-
});
|
|
495
|
-
if (p.isCancel(proceed)) {
|
|
496
|
-
p.cancel("Operation cancelled.");
|
|
497
|
-
process.exit(0);
|
|
498
|
-
}
|
|
499
|
-
if (proceed) {
|
|
500
|
-
return defaultOptions;
|
|
501
|
-
}
|
|
502
|
-
return promptForMonorepoCustomization(workspaceName);
|
|
503
|
-
}
|
|
504
|
-
async function promptForOptions(name) {
|
|
505
|
-
let projectName = name;
|
|
506
|
-
if (!projectName) {
|
|
507
|
-
const nameResult = await p.text({
|
|
508
|
-
message: "What is your project named?",
|
|
509
|
-
placeholder: generateRandomName(),
|
|
510
|
-
defaultValue: generateRandomName(),
|
|
511
|
-
validate: (value) => {
|
|
512
|
-
if (!value.length) return "Project name is required";
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
if (p.isCancel(nameResult)) {
|
|
516
|
-
p.cancel("Operation cancelled.");
|
|
517
|
-
process.exit(0);
|
|
518
|
-
}
|
|
519
|
-
projectName = nameResult;
|
|
520
|
-
}
|
|
521
|
-
const projectType = await p.select({
|
|
522
|
-
message: "Project type",
|
|
523
|
-
options: [
|
|
524
|
-
{ value: "app", label: "Application" },
|
|
525
|
-
{ value: "library", label: "Library" },
|
|
526
|
-
{ value: "monorepo", label: "Monorepo" }
|
|
527
|
-
],
|
|
528
|
-
initialValue: "app"
|
|
529
|
-
});
|
|
530
|
-
if (p.isCancel(projectType)) {
|
|
531
|
-
p.cancel("Operation cancelled.");
|
|
532
|
-
process.exit(0);
|
|
533
|
-
}
|
|
534
|
-
if (projectType === "monorepo") {
|
|
535
|
-
return promptForMonorepo(projectName);
|
|
536
|
-
}
|
|
537
|
-
return promptForPackageOptions(projectName, projectType);
|
|
538
|
-
}
|
|
539
|
-
function customTemplateToOptions(customTemplate, name, projectType) {
|
|
540
|
-
const baseTemplate = customTemplate.baseTemplate;
|
|
541
|
-
const template = baseTemplate;
|
|
542
|
-
const base = {
|
|
543
|
-
name,
|
|
544
|
-
template,
|
|
545
|
-
projectType,
|
|
546
|
-
packageManager: "pnpm",
|
|
547
|
-
pnpmManageVersions: true,
|
|
548
|
-
nodeVersion: "latest",
|
|
549
|
-
linter: customTemplate.linter,
|
|
550
|
-
formatter: customTemplate.formatter,
|
|
551
|
-
testing: customTemplate.testing
|
|
552
|
-
};
|
|
553
|
-
if (baseTemplate === "r3f" && customTemplate.integrations) {
|
|
554
|
-
const integrations = customTemplate.integrations;
|
|
555
|
-
return {
|
|
556
|
-
...base,
|
|
557
|
-
drei: integrations.includes("drei") ? {} : void 0,
|
|
558
|
-
handle: integrations.includes("handle") ? {} : void 0,
|
|
559
|
-
leva: integrations.includes("leva") ? {} : void 0,
|
|
560
|
-
postprocessing: integrations.includes("postprocessing") ? {} : void 0,
|
|
561
|
-
rapier: integrations.includes("rapier") ? {} : void 0,
|
|
562
|
-
xr: integrations.includes("xr") ? {} : void 0,
|
|
563
|
-
uikit: integrations.includes("uikit") ? {} : void 0,
|
|
564
|
-
offscreen: integrations.includes("offscreen") ? {} : void 0,
|
|
565
|
-
zustand: integrations.includes("zustand") ? {} : void 0,
|
|
566
|
-
koota: integrations.includes("koota") ? {} : void 0,
|
|
567
|
-
triplex: integrations.includes("triplex") ? {} : void 0,
|
|
568
|
-
viverse: integrations.includes("viverse") ? {} : void 0
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
return base;
|
|
572
|
-
}
|
|
573
|
-
async function promptForPackageOptions(projectName, projectType, inheritedTooling) {
|
|
574
|
-
const builtInOptions = [
|
|
575
|
-
{ value: "vanilla", label: "Vanilla" },
|
|
576
|
-
{ value: "react", label: "React" },
|
|
577
|
-
{ value: "r3f", label: "React Three Fiber" }
|
|
578
|
-
];
|
|
579
|
-
const customTemplates = getCustomTemplates();
|
|
580
|
-
const customOptions = Object.keys(customTemplates).map((name) => ({
|
|
581
|
-
value: `custom:${name}`,
|
|
582
|
-
label: name,
|
|
583
|
-
hint: "saved template"
|
|
584
|
-
}));
|
|
585
|
-
const allOptions = [...builtInOptions, ...customOptions];
|
|
586
|
-
const templateSelection = await p.select({
|
|
587
|
-
message: "Select a template",
|
|
588
|
-
options: allOptions,
|
|
589
|
-
initialValue: "vanilla"
|
|
590
|
-
});
|
|
591
|
-
if (p.isCancel(templateSelection)) {
|
|
592
|
-
p.cancel("Operation cancelled.");
|
|
593
|
-
process.exit(0);
|
|
594
|
-
}
|
|
595
|
-
const selection = templateSelection;
|
|
596
|
-
if (selection.startsWith("custom:")) {
|
|
597
|
-
const customName = selection.slice(7);
|
|
598
|
-
const customTemplate = customTemplates[customName];
|
|
599
|
-
const defaultOptions2 = customTemplateToOptions(customTemplate, projectName, projectType);
|
|
600
|
-
if (inheritedTooling?.linter) {
|
|
601
|
-
defaultOptions2.linter = inheritedTooling.linter;
|
|
602
|
-
}
|
|
603
|
-
if (inheritedTooling?.formatter) {
|
|
604
|
-
defaultOptions2.formatter = inheritedTooling.formatter;
|
|
605
|
-
}
|
|
606
|
-
const configTitle2 = inheritedTooling ? `Template: ${customName} (using workspace tooling)` : `Template: ${customName}`;
|
|
607
|
-
p.note(formatConfigSummary(defaultOptions2), configTitle2);
|
|
608
|
-
const proceed2 = await p.confirm({
|
|
609
|
-
message: "Proceed with these settings?",
|
|
610
|
-
initialValue: true
|
|
611
|
-
});
|
|
612
|
-
if (p.isCancel(proceed2)) {
|
|
613
|
-
p.cancel("Operation cancelled.");
|
|
614
|
-
process.exit(0);
|
|
615
|
-
}
|
|
616
|
-
if (proceed2) {
|
|
617
|
-
return defaultOptions2;
|
|
618
|
-
}
|
|
619
|
-
return promptForCustomization(
|
|
620
|
-
customTemplate.baseTemplate,
|
|
621
|
-
projectName,
|
|
622
|
-
projectType,
|
|
623
|
-
customTemplate.integrations,
|
|
624
|
-
inheritedTooling
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
const template = selection;
|
|
628
|
-
const baseTemplate = getBaseTemplate(template);
|
|
629
|
-
let integrations;
|
|
630
|
-
if (baseTemplate === "r3f") {
|
|
631
|
-
integrations = await promptForR3fIntegrations();
|
|
632
|
-
}
|
|
633
|
-
const defaultOptions = getDefaultOptions(
|
|
634
|
-
template,
|
|
635
|
-
projectName,
|
|
636
|
-
projectType,
|
|
637
|
-
void 0,
|
|
638
|
-
integrations,
|
|
639
|
-
inheritedTooling
|
|
640
|
-
);
|
|
641
|
-
const configTitle = inheritedTooling ? "Template Configuration (using workspace tooling)" : "Template Configuration";
|
|
642
|
-
p.note(formatConfigSummary(defaultOptions), configTitle);
|
|
643
|
-
const proceed = await p.confirm({
|
|
644
|
-
message: "Proceed with these settings?",
|
|
645
|
-
initialValue: true
|
|
646
|
-
});
|
|
647
|
-
if (p.isCancel(proceed)) {
|
|
648
|
-
p.cancel("Operation cancelled.");
|
|
649
|
-
process.exit(0);
|
|
650
|
-
}
|
|
651
|
-
if (proceed) {
|
|
652
|
-
return defaultOptions;
|
|
653
|
-
}
|
|
654
|
-
return promptForCustomization(template, projectName, projectType, integrations, inheritedTooling);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
async function checkAnyExists(paths) {
|
|
658
|
-
for (const path of paths) {
|
|
659
|
-
try {
|
|
660
|
-
await access(path, constants.F_OK);
|
|
661
|
-
return true;
|
|
662
|
-
} catch {
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
return false;
|
|
666
|
-
}
|
|
667
|
-
async function validateWorkspace(monorepoRoot) {
|
|
668
|
-
const errors = [];
|
|
669
|
-
const tsConfigPath = join(monorepoRoot, ".config/typescript/package.json");
|
|
670
|
-
try {
|
|
671
|
-
await access(tsConfigPath, constants.F_OK);
|
|
672
|
-
} catch {
|
|
673
|
-
errors.push("Missing .config/typescript package");
|
|
674
|
-
}
|
|
675
|
-
const linterPaths = [
|
|
676
|
-
join(monorepoRoot, ".config/oxlint/package.json"),
|
|
677
|
-
join(monorepoRoot, ".config/eslint/package.json"),
|
|
678
|
-
join(monorepoRoot, "eslint.config.js"),
|
|
679
|
-
join(monorepoRoot, "biome.json")
|
|
680
|
-
];
|
|
681
|
-
const hasLinter = await checkAnyExists(linterPaths);
|
|
682
|
-
if (!hasLinter) {
|
|
683
|
-
errors.push(
|
|
684
|
-
"Missing linter config (.config/oxlint, .config/eslint, eslint.config.js, or biome.json)"
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
const formatterPaths = [
|
|
688
|
-
join(monorepoRoot, ".config/oxfmt/package.json"),
|
|
689
|
-
join(monorepoRoot, ".config/prettier/package.json"),
|
|
690
|
-
join(monorepoRoot, ".prettierrc.json"),
|
|
691
|
-
join(monorepoRoot, "biome.json")
|
|
692
|
-
];
|
|
693
|
-
const hasFormatter = await checkAnyExists(formatterPaths);
|
|
694
|
-
if (!hasFormatter) {
|
|
695
|
-
errors.push(
|
|
696
|
-
"Missing formatter config (.config/oxfmt, .config/prettier, .prettierrc.json, or biome.json)"
|
|
697
|
-
);
|
|
698
|
-
}
|
|
699
|
-
return { valid: errors.length === 0, errors };
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
const require$1 = createRequire(import.meta.url);
|
|
703
|
-
const pkg = require$1("../package.json");
|
|
704
|
-
async function fileExists(path) {
|
|
705
|
-
try {
|
|
706
|
-
await access(path, constants$1.F_OK);
|
|
707
|
-
return true;
|
|
708
|
-
} catch {
|
|
709
|
-
return false;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
async function writeGeneratedFiles(basePath, files) {
|
|
713
|
-
const filePaths = Object.keys(files).sort();
|
|
714
|
-
for (const filePath of filePaths) {
|
|
715
|
-
const fullFilePath = join(basePath, filePath);
|
|
716
|
-
await mkdir(dirname(fullFilePath), { recursive: true });
|
|
717
|
-
const file = files[filePath];
|
|
718
|
-
if (file.type === "text") {
|
|
719
|
-
await writeFile(fullFilePath, file.content);
|
|
720
|
-
} else {
|
|
721
|
-
const response = await fetch(file.url);
|
|
722
|
-
await writeFile(fullFilePath, response.body);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
function calculateWorkspaceRoot(packagePath) {
|
|
727
|
-
const segments = packagePath.split(/[/\\]/).filter(Boolean);
|
|
728
|
-
return segments.map(() => "..").join("/");
|
|
729
|
-
}
|
|
730
|
-
async function detectMonorepoRoot() {
|
|
731
|
-
let currentDir = cwd();
|
|
732
|
-
const root = resolve("/");
|
|
733
|
-
while (currentDir !== root) {
|
|
734
|
-
const workspaceFile = join(currentDir, "pnpm-workspace.yaml");
|
|
735
|
-
try {
|
|
736
|
-
await access(workspaceFile, constants$1.F_OK);
|
|
737
|
-
const content = await readFile(workspaceFile, "utf-8");
|
|
738
|
-
if (content.includes("packages:")) {
|
|
739
|
-
return currentDir;
|
|
740
|
-
}
|
|
741
|
-
} catch {
|
|
742
|
-
}
|
|
743
|
-
currentDir = dirname(currentDir);
|
|
744
|
-
}
|
|
745
|
-
return null;
|
|
746
|
-
}
|
|
747
|
-
async function parseWorkspaceDirectories(monorepoRoot) {
|
|
748
|
-
try {
|
|
749
|
-
const workspaceFile = join(monorepoRoot, "pnpm-workspace.yaml");
|
|
750
|
-
const content = await readFile(workspaceFile, "utf-8");
|
|
751
|
-
return parseWorkspaceYamlContent(content);
|
|
752
|
-
} catch {
|
|
753
|
-
return [];
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
async function detectWorkspaceTooling(monorepoRoot) {
|
|
757
|
-
try {
|
|
758
|
-
const pkgPath = join(monorepoRoot, "package.json");
|
|
759
|
-
const content = await readFile(pkgPath, "utf-8");
|
|
760
|
-
const pkgJson = JSON.parse(content);
|
|
761
|
-
const devDeps = pkgJson.devDependencies ?? {};
|
|
762
|
-
const linter = devDeps.oxlint ? "oxlint" : devDeps.eslint ? "eslint" : devDeps["@biomejs/biome"] ? "biome" : void 0;
|
|
763
|
-
const formatter = devDeps.oxfmt ? "oxfmt" : devDeps.prettier ? "prettier" : devDeps["@biomejs/biome"] ? "biome" : void 0;
|
|
764
|
-
return { linter, formatter };
|
|
765
|
-
} catch {
|
|
766
|
-
return {};
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
async function detectExistingConfigs(monorepoRoot) {
|
|
770
|
-
const configs = {};
|
|
771
|
-
const eslintPath = join(monorepoRoot, "eslint.config.js");
|
|
772
|
-
if (await fileExists(eslintPath)) {
|
|
773
|
-
configs.linter = "eslint";
|
|
774
|
-
configs.eslintConfigPath = eslintPath;
|
|
775
|
-
}
|
|
776
|
-
const prettierPath = join(monorepoRoot, ".prettierrc.json");
|
|
777
|
-
if (await fileExists(prettierPath)) {
|
|
778
|
-
configs.formatter = "prettier";
|
|
779
|
-
configs.prettierConfigPath = prettierPath;
|
|
780
|
-
}
|
|
781
|
-
const biomePath = join(monorepoRoot, "biome.json");
|
|
782
|
-
if (await fileExists(biomePath)) {
|
|
783
|
-
configs.biomeConfigPath = biomePath;
|
|
784
|
-
if (!configs.linter) configs.linter = "biome";
|
|
785
|
-
if (!configs.formatter) configs.formatter = "biome";
|
|
786
|
-
}
|
|
787
|
-
return configs;
|
|
788
|
-
}
|
|
789
|
-
async function getMonorepoScope(monorepoRoot) {
|
|
790
|
-
try {
|
|
791
|
-
const pkgPath = join(monorepoRoot, "package.json");
|
|
792
|
-
const content = await readFile(pkgPath, "utf-8");
|
|
793
|
-
const pkgJson = JSON.parse(content);
|
|
794
|
-
if (pkgJson.name) {
|
|
795
|
-
return pkgJson.name.replace(/^@/, "").replace(/\/.*$/, "");
|
|
796
|
-
}
|
|
797
|
-
} catch {
|
|
798
|
-
}
|
|
799
|
-
return monorepoRoot.split(/[/\\]/).pop() ?? "workspace";
|
|
800
|
-
}
|
|
801
|
-
async function getWorkspacePackages(monorepoRoot) {
|
|
802
|
-
const packagesDir = join(monorepoRoot, "packages");
|
|
803
|
-
try {
|
|
804
|
-
const { readdir } = await import('fs/promises');
|
|
805
|
-
const entries = await readdir(packagesDir, { withFileTypes: true });
|
|
806
|
-
const names = [];
|
|
807
|
-
for (const entry of entries) {
|
|
808
|
-
if (!entry.isDirectory()) continue;
|
|
809
|
-
try {
|
|
810
|
-
const content = await readFile(
|
|
811
|
-
join(packagesDir, entry.name, "package.json"),
|
|
812
|
-
"utf-8"
|
|
813
|
-
);
|
|
814
|
-
const pkg2 = JSON.parse(content);
|
|
815
|
-
if (pkg2.name) names.push(pkg2.name);
|
|
816
|
-
} catch {
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
return names;
|
|
820
|
-
} catch {
|
|
821
|
-
return [];
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
async function ensureConfigInWorkspace(monorepoRoot) {
|
|
825
|
-
const workspacePath = join(monorepoRoot, "pnpm-workspace.yaml");
|
|
826
|
-
let content;
|
|
827
|
-
try {
|
|
828
|
-
content = await readFile(workspacePath, "utf-8");
|
|
829
|
-
} catch {
|
|
830
|
-
content = `packages:
|
|
831
|
-
- ".config/*"
|
|
832
|
-
- "packages/*"
|
|
833
|
-
`;
|
|
834
|
-
await writeFile(workspacePath, content);
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
if (content.includes(".config/*") || content.includes('".config/*"')) {
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
const lines = content.split("\n");
|
|
841
|
-
const packagesIndex = lines.findIndex(
|
|
842
|
-
(line) => line.trim().startsWith("packages:")
|
|
843
|
-
);
|
|
844
|
-
if (packagesIndex === -1) {
|
|
845
|
-
content = `packages:
|
|
846
|
-
- ".config/*"
|
|
847
|
-
${content}`;
|
|
848
|
-
} else {
|
|
849
|
-
lines.splice(packagesIndex + 1, 0, ' - ".config/*"');
|
|
850
|
-
content = lines.join("\n");
|
|
851
|
-
}
|
|
852
|
-
await writeFile(workspacePath, content);
|
|
853
|
-
}
|
|
854
|
-
async function migrateEslintConfig(monorepoRoot, files) {
|
|
855
|
-
const configBasePath = ".config/eslint";
|
|
856
|
-
const existingConfigPath = join(monorepoRoot, "eslint.config.js");
|
|
857
|
-
let existingContent;
|
|
858
|
-
try {
|
|
859
|
-
existingContent = await readFile(existingConfigPath, "utf-8");
|
|
860
|
-
} catch {
|
|
861
|
-
generateEslintConfigPackage(files);
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
files[`${configBasePath}/package.json`] = {
|
|
865
|
-
type: "text",
|
|
866
|
-
content: JSON.stringify(
|
|
867
|
-
{
|
|
868
|
-
name: "@config/eslint",
|
|
869
|
-
version: "0.1.0",
|
|
870
|
-
private: true,
|
|
871
|
-
type: "module",
|
|
872
|
-
exports: {
|
|
873
|
-
"./base": "./base.js",
|
|
874
|
-
"./react": "./react.js"
|
|
875
|
-
}
|
|
876
|
-
},
|
|
877
|
-
null,
|
|
878
|
-
2
|
|
879
|
-
)
|
|
880
|
-
};
|
|
881
|
-
files[`${configBasePath}/README.md`] = {
|
|
882
|
-
type: "text",
|
|
883
|
-
content: `# \`@config/eslint\`
|
|
884
|
-
|
|
885
|
-
Shared ESLint configurations.
|
|
886
|
-
|
|
887
|
-
## Usage
|
|
888
|
-
|
|
889
|
-
In your package's \`eslint.config.js\`:
|
|
890
|
-
|
|
891
|
-
\`\`\`js
|
|
892
|
-
import base from "@config/eslint/base";
|
|
893
|
-
|
|
894
|
-
export default [...base];
|
|
895
|
-
\`\`\`
|
|
896
|
-
|
|
897
|
-
## Available Configs
|
|
898
|
-
|
|
899
|
-
- \`base\` - Base ESLint rules (migrated from root)
|
|
900
|
-
- \`react\` - React-specific rules
|
|
901
|
-
`
|
|
902
|
-
};
|
|
903
|
-
files[`${configBasePath}/base.js`] = {
|
|
904
|
-
type: "text",
|
|
905
|
-
content: existingContent
|
|
906
|
-
};
|
|
907
|
-
files[`${configBasePath}/react.js`] = {
|
|
908
|
-
type: "text",
|
|
909
|
-
content: `import react from "eslint-plugin-react";
|
|
910
|
-
import reactHooks from "eslint-plugin-react-hooks";
|
|
911
|
-
|
|
912
|
-
export default [
|
|
913
|
-
{
|
|
914
|
-
plugins: {
|
|
915
|
-
react,
|
|
916
|
-
"react-hooks": reactHooks,
|
|
917
|
-
},
|
|
918
|
-
rules: {
|
|
919
|
-
...react.configs.recommended.rules,
|
|
920
|
-
...reactHooks.configs.recommended.rules,
|
|
921
|
-
"react/react-in-jsx-scope": "off",
|
|
922
|
-
},
|
|
923
|
-
settings: {
|
|
924
|
-
react: {
|
|
925
|
-
version: "detect",
|
|
926
|
-
},
|
|
927
|
-
},
|
|
928
|
-
},
|
|
929
|
-
];
|
|
930
|
-
`
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
async function migratePrettierConfig(monorepoRoot, files) {
|
|
934
|
-
const configBasePath = ".config/prettier";
|
|
935
|
-
const existingConfigPath = join(monorepoRoot, ".prettierrc.json");
|
|
936
|
-
let existingContent;
|
|
937
|
-
try {
|
|
938
|
-
existingContent = await readFile(existingConfigPath, "utf-8");
|
|
939
|
-
} catch {
|
|
940
|
-
generatePrettierConfigPackage(files);
|
|
941
|
-
return;
|
|
942
|
-
}
|
|
943
|
-
files[`${configBasePath}/package.json`] = {
|
|
944
|
-
type: "text",
|
|
945
|
-
content: JSON.stringify(
|
|
946
|
-
{
|
|
947
|
-
name: "@config/prettier",
|
|
948
|
-
version: "0.1.0",
|
|
949
|
-
private: true,
|
|
950
|
-
exports: {
|
|
951
|
-
"./base": "./base.json"
|
|
952
|
-
}
|
|
953
|
-
},
|
|
954
|
-
null,
|
|
955
|
-
2
|
|
956
|
-
)
|
|
957
|
-
};
|
|
958
|
-
files[`${configBasePath}/README.md`] = {
|
|
959
|
-
type: "text",
|
|
960
|
-
content: `# \`@config/prettier\`
|
|
961
|
-
|
|
962
|
-
Shared Prettier configurations.
|
|
963
|
-
|
|
964
|
-
## Usage
|
|
965
|
-
|
|
966
|
-
In your package's \`.prettierrc\`:
|
|
967
|
-
|
|
968
|
-
\`\`\`json
|
|
969
|
-
"@config/prettier/base"
|
|
970
|
-
\`\`\`
|
|
971
|
-
|
|
972
|
-
Or in \`package.json\`:
|
|
973
|
-
|
|
974
|
-
\`\`\`json
|
|
975
|
-
{
|
|
976
|
-
"prettier": "@config/prettier/base"
|
|
977
|
-
}
|
|
978
|
-
\`\`\`
|
|
979
|
-
|
|
980
|
-
## Available Configs
|
|
981
|
-
|
|
982
|
-
- \`base\` - Base Prettier rules (migrated from root)
|
|
983
|
-
`
|
|
984
|
-
};
|
|
985
|
-
files[`${configBasePath}/base.json`] = {
|
|
986
|
-
type: "text",
|
|
987
|
-
content: existingContent
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
async function createPackageInWorkspace(monorepoRoot, packageManager, inheritedTooling, scope) {
|
|
991
|
-
const workspaceDirectories = await parseWorkspaceDirectories(monorepoRoot);
|
|
992
|
-
const defaultDirectories = ["apps", "packages"];
|
|
993
|
-
const hasCustomDirectories = workspaceDirectories.length > 0 && !workspaceDirectories.every((dir) => defaultDirectories.includes(dir));
|
|
994
|
-
const packageType = await promptForInitialPackage();
|
|
995
|
-
if (packageType === "skip") {
|
|
996
|
-
return false;
|
|
997
|
-
}
|
|
998
|
-
const defaultDir = packageType === "app" ? "apps" : "packages";
|
|
999
|
-
const packageNameInput = await p.text({
|
|
1000
|
-
message: "Package name?",
|
|
1001
|
-
initialValue: `@${scope}/`,
|
|
1002
|
-
validate: (value) => {
|
|
1003
|
-
const validationError = validatePackageName(value);
|
|
1004
|
-
if (validationError) return validationError;
|
|
1005
|
-
const dirName = value.includes("/") ? value.split("/").pop() : value;
|
|
1006
|
-
if (!dirName) return "Package name is required";
|
|
1007
|
-
if (!hasCustomDirectories) {
|
|
1008
|
-
const targetPath = join(monorepoRoot, defaultDir, dirName);
|
|
1009
|
-
try {
|
|
1010
|
-
const { statSync } = require$1("fs");
|
|
1011
|
-
statSync(targetPath);
|
|
1012
|
-
return `Directory ${defaultDir}/${dirName} already exists`;
|
|
1013
|
-
} catch {
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
});
|
|
1018
|
-
if (p.isCancel(packageNameInput)) {
|
|
1019
|
-
return false;
|
|
1020
|
-
}
|
|
1021
|
-
const scopedName = packageNameInput;
|
|
1022
|
-
const shortName = scopedName.includes("/") ? scopedName.split("/").pop() : scopedName;
|
|
1023
|
-
const packageOptions = await promptForPackageOptions(
|
|
1024
|
-
scopedName,
|
|
1025
|
-
packageType,
|
|
1026
|
-
inheritedTooling
|
|
1027
|
-
);
|
|
1028
|
-
let targetDir = defaultDir;
|
|
1029
|
-
if (hasCustomDirectories && workspaceDirectories.length > 0) {
|
|
1030
|
-
const dirChoice = await p.select({
|
|
1031
|
-
message: "Target directory",
|
|
1032
|
-
options: workspaceDirectories.map((dir) => ({
|
|
1033
|
-
value: dir,
|
|
1034
|
-
label: dir
|
|
1035
|
-
})),
|
|
1036
|
-
initialValue: workspaceDirectories.includes(defaultDir) ? defaultDir : workspaceDirectories[0]
|
|
1037
|
-
});
|
|
1038
|
-
if (p.isCancel(dirChoice)) {
|
|
1039
|
-
return false;
|
|
1040
|
-
}
|
|
1041
|
-
targetDir = dirChoice;
|
|
1042
|
-
const targetPath = join(monorepoRoot, targetDir, shortName);
|
|
1043
|
-
try {
|
|
1044
|
-
const { statSync } = require$1("fs");
|
|
1045
|
-
statSync(targetPath);
|
|
1046
|
-
p.log.error(`Directory ${targetDir}/${shortName} already exists`);
|
|
1047
|
-
return false;
|
|
1048
|
-
} catch {
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
const relativePkgPath = join(targetDir, shortName);
|
|
1052
|
-
const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
|
|
1053
|
-
packageOptions.workspaceRoot = workspaceRoot;
|
|
1054
|
-
packageOptions.name = scopedName;
|
|
1055
|
-
if (packageManager === "pnpm") {
|
|
1056
|
-
packageOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
1057
|
-
} else if (packageManager === "yarn") {
|
|
1058
|
-
packageOptions.yarnVersion = await getLatestYarnVersion();
|
|
1059
|
-
} else if (packageManager === "npm") {
|
|
1060
|
-
packageOptions.npmVersion = await getLatestNpmCliVersion();
|
|
1061
|
-
}
|
|
1062
|
-
const nodeVersion = packageOptions.nodeVersion ?? "latest";
|
|
1063
|
-
if (nodeVersion === "latest") {
|
|
1064
|
-
packageOptions.nodeVersion = await getLatestNodeVersion();
|
|
1065
|
-
}
|
|
1066
|
-
const versions = {};
|
|
1067
|
-
const versionPromises = [];
|
|
1068
|
-
const pkgIsLibrary = packageOptions.projectType === "library";
|
|
1069
|
-
const pkgTesting = packageOptions.testing ?? (pkgIsLibrary ? "vitest" : "none");
|
|
1070
|
-
if (pkgTesting === "vitest") {
|
|
1071
|
-
versionPromises.push(
|
|
1072
|
-
getLatestNpmVersion("vitest", "4.0.0").then((v) => {
|
|
1073
|
-
versions.vitest = v;
|
|
1074
|
-
})
|
|
1075
|
-
);
|
|
1076
|
-
}
|
|
1077
|
-
if (!pkgIsLibrary) {
|
|
1078
|
-
versionPromises.push(
|
|
1079
|
-
getLatestNpmVersion("vite", "6.3.4").then((v) => {
|
|
1080
|
-
versions.vite = v;
|
|
1081
|
-
})
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
|
-
const linter = packageOptions.linter ?? "oxlint";
|
|
1085
|
-
if (linter === "eslint") {
|
|
1086
|
-
versionPromises.push(
|
|
1087
|
-
getLatestNpmVersion("eslint", "9.17.0").then((v) => {
|
|
1088
|
-
versions.eslint = v;
|
|
1089
|
-
})
|
|
1090
|
-
);
|
|
1091
|
-
} else if (linter === "oxlint") {
|
|
1092
|
-
versionPromises.push(
|
|
1093
|
-
getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
|
|
1094
|
-
versions.oxlint = v;
|
|
1095
|
-
})
|
|
1096
|
-
);
|
|
1097
|
-
} else if (linter === "biome") {
|
|
1098
|
-
versionPromises.push(
|
|
1099
|
-
getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
|
|
1100
|
-
versions.biome = v;
|
|
1101
|
-
})
|
|
1102
|
-
);
|
|
1103
|
-
}
|
|
1104
|
-
const formatter = packageOptions.formatter ?? "oxfmt";
|
|
1105
|
-
if (formatter === "prettier") {
|
|
1106
|
-
versionPromises.push(
|
|
1107
|
-
getLatestNpmVersion("prettier", "3.4.2").then((v) => {
|
|
1108
|
-
versions.prettier = v;
|
|
1109
|
-
})
|
|
1110
|
-
);
|
|
1111
|
-
} else if (formatter === "oxfmt") {
|
|
1112
|
-
versionPromises.push(
|
|
1113
|
-
getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
|
|
1114
|
-
versions.oxfmt = v;
|
|
1115
|
-
})
|
|
1116
|
-
);
|
|
1117
|
-
} else if (formatter === "biome" && linter !== "biome") {
|
|
1118
|
-
versionPromises.push(
|
|
1119
|
-
getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
|
|
1120
|
-
versions.biome = v;
|
|
1121
|
-
})
|
|
1122
|
-
);
|
|
1123
|
-
}
|
|
1124
|
-
await Promise.all(versionPromises);
|
|
1125
|
-
packageOptions.versions = versions;
|
|
1126
|
-
const workspacePackages = packageType === "app" ? await getWorkspacePackages(monorepoRoot) : [];
|
|
1127
|
-
if (workspacePackages.length > 0) {
|
|
1128
|
-
const selectedDeps = await p.multiselect({
|
|
1129
|
-
message: "Add workspace dependencies?",
|
|
1130
|
-
options: workspacePackages.map((name) => ({ value: name, label: name })),
|
|
1131
|
-
required: false
|
|
1132
|
-
});
|
|
1133
|
-
if (!p.isCancel(selectedDeps) && selectedDeps.length > 0) {
|
|
1134
|
-
packageOptions.workspaceDependencies = selectedDeps;
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
const outputPath = join(monorepoRoot, relativePkgPath);
|
|
1138
|
-
const spinner = p.spinner();
|
|
1139
|
-
spinner.start("Creating package...");
|
|
1140
|
-
try {
|
|
1141
|
-
const files = generate(packageOptions);
|
|
1142
|
-
await writeGeneratedFiles(outputPath, files);
|
|
1143
|
-
spinner.stop(
|
|
1144
|
-
color.green.inverse(` \u2713 Package created at ${relativePkgPath}! `)
|
|
1145
|
-
);
|
|
1146
|
-
const addAnother = await p.select({
|
|
1147
|
-
message: "Add another package?",
|
|
1148
|
-
options: [
|
|
1149
|
-
{ value: "no", label: "No, I'm done" },
|
|
1150
|
-
{ value: "yes", label: "Yes, add another" }
|
|
1151
|
-
],
|
|
1152
|
-
initialValue: "no"
|
|
1153
|
-
});
|
|
1154
|
-
return !p.isCancel(addAnother) && addAnother === "yes";
|
|
1155
|
-
} catch (error) {
|
|
1156
|
-
spinner.stop("Failed to create package");
|
|
1157
|
-
p.log.error(String(error));
|
|
1158
|
-
return false;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
async function promptAndOpenEditor(projectPath) {
|
|
1162
|
-
const savedEditor = getPreferredEditor();
|
|
1163
|
-
let selectedEditor;
|
|
1164
|
-
if (savedEditor && savedEditor !== "skip") {
|
|
1165
|
-
const useDefault = await p.confirm({
|
|
1166
|
-
message: `Open in editor? ${color.dim(`(${editorNames[savedEditor]})`)}`,
|
|
1167
|
-
initialValue: true
|
|
1168
|
-
});
|
|
1169
|
-
if (p.isCancel(useDefault)) {
|
|
1170
|
-
selectedEditor = void 0;
|
|
1171
|
-
} else if (useDefault) {
|
|
1172
|
-
selectedEditor = savedEditor;
|
|
1173
|
-
} else {
|
|
1174
|
-
selectedEditor = "skip";
|
|
1175
|
-
}
|
|
1176
|
-
} else {
|
|
1177
|
-
const openEditor = await p.select({
|
|
1178
|
-
message: "Open project in editor?",
|
|
1179
|
-
options: [
|
|
1180
|
-
{ value: "skip", label: "Skip" },
|
|
1181
|
-
{ value: "cursor", label: "Cursor" },
|
|
1182
|
-
{ value: "code", label: "VS Code" },
|
|
1183
|
-
{ value: "webstorm", label: "WebStorm" }
|
|
1184
|
-
],
|
|
1185
|
-
initialValue: "skip"
|
|
1186
|
-
});
|
|
1187
|
-
if (!p.isCancel(openEditor)) {
|
|
1188
|
-
selectedEditor = openEditor;
|
|
1189
|
-
const saveChoice = await p.confirm({
|
|
1190
|
-
message: `Save ${editorNames[selectedEditor] ?? "Skip"} as default editor?`,
|
|
1191
|
-
initialValue: true
|
|
1192
|
-
});
|
|
1193
|
-
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1194
|
-
setPreferredEditor(selectedEditor);
|
|
1195
|
-
if (selectedEditor === "cursor" || selectedEditor === "code") {
|
|
1196
|
-
const reuseChoice = await p.confirm({
|
|
1197
|
-
message: "Reuse current window when opening projects?",
|
|
1198
|
-
initialValue: false
|
|
1199
|
-
});
|
|
1200
|
-
if (!p.isCancel(reuseChoice)) {
|
|
1201
|
-
setReuseWindow(reuseChoice);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
if (selectedEditor && selectedEditor !== "skip") {
|
|
1208
|
-
try {
|
|
1209
|
-
await openInEditor(
|
|
1210
|
-
selectedEditor,
|
|
1211
|
-
projectPath,
|
|
1212
|
-
getReuseWindow()
|
|
1213
|
-
);
|
|
1214
|
-
p.log.success(`Opening in ${editorNames[selectedEditor]}...`);
|
|
1215
|
-
} catch {
|
|
1216
|
-
p.log.warn(
|
|
1217
|
-
`Could not open ${editorNames[selectedEditor]}. Make sure the CLI command is in your PATH.`
|
|
1218
|
-
);
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
async function handleCheckCommand() {
|
|
1223
|
-
const monorepoRoot = await detectMonorepoRoot();
|
|
1224
|
-
if (!monorepoRoot) {
|
|
1225
|
-
console.log(color.red("\u2717") + " Not a monorepo workspace");
|
|
1226
|
-
process.exit(1);
|
|
1227
|
-
}
|
|
1228
|
-
const { valid, errors } = await validateWorkspace(monorepoRoot);
|
|
1229
|
-
if (valid) {
|
|
1230
|
-
console.log(color.green("\u2713") + " Valid monorepo workspace");
|
|
1231
|
-
console.log(color.dim(` ${monorepoRoot}`));
|
|
1232
|
-
} else {
|
|
1233
|
-
console.log(color.red("\u2717") + " Invalid monorepo workspace");
|
|
1234
|
-
console.log(color.dim(` ${monorepoRoot}`));
|
|
1235
|
-
for (const error of errors) {
|
|
1236
|
-
console.log(color.red(` \u2022 ${error}`));
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
process.exit(valid ? 0 : 1);
|
|
1240
|
-
}
|
|
1241
|
-
async function handleFixCommand(options) {
|
|
1242
|
-
const monorepoRoot = await detectMonorepoRoot();
|
|
1243
|
-
if (!monorepoRoot) {
|
|
1244
|
-
console.log(color.red("\u2717") + " Not a monorepo workspace");
|
|
1245
|
-
console.log(color.dim(" Run this command from within a monorepo"));
|
|
1246
|
-
process.exit(1);
|
|
1247
|
-
}
|
|
1248
|
-
const { valid, errors } = await validateWorkspace(monorepoRoot);
|
|
1249
|
-
if (valid) {
|
|
1250
|
-
console.log(color.green("\u2713") + " Workspace is already valid");
|
|
1251
|
-
console.log(color.dim(` ${monorepoRoot}`));
|
|
1252
|
-
process.exit(0);
|
|
1253
|
-
}
|
|
1254
|
-
console.log(color.yellow("!") + " Invalid monorepo workspace");
|
|
1255
|
-
for (const error of errors) {
|
|
1256
|
-
console.log(color.dim(` \u2022 ${error}`));
|
|
1257
|
-
}
|
|
1258
|
-
console.log();
|
|
1259
|
-
const tooling = await detectWorkspaceTooling(monorepoRoot);
|
|
1260
|
-
const existingConfigs = await detectExistingConfigs(monorepoRoot);
|
|
1261
|
-
const detectedLinter = tooling.linter ?? existingConfigs.linter ?? "oxlint";
|
|
1262
|
-
const detectedFormatter = tooling.formatter ?? existingConfigs.formatter ?? "oxfmt";
|
|
1263
|
-
const isNonInteractive = options.linter && options.formatter;
|
|
1264
|
-
let linter;
|
|
1265
|
-
let formatter;
|
|
1266
|
-
if (isNonInteractive) {
|
|
1267
|
-
linter = options.linter;
|
|
1268
|
-
formatter = options.formatter;
|
|
1269
|
-
} else {
|
|
1270
|
-
const linterChoice = await p.select({
|
|
1271
|
-
message: "Linter",
|
|
1272
|
-
options: [
|
|
1273
|
-
{
|
|
1274
|
-
value: "oxlint",
|
|
1275
|
-
label: "oxlint" + (tooling.linter === "oxlint" ? color.dim(" (installed)") : "")
|
|
1276
|
-
},
|
|
1277
|
-
{
|
|
1278
|
-
value: "eslint",
|
|
1279
|
-
label: "eslint" + (tooling.linter === "eslint" || existingConfigs.linter === "eslint" ? color.dim(" (installed)") : "")
|
|
1280
|
-
},
|
|
1281
|
-
{
|
|
1282
|
-
value: "biome",
|
|
1283
|
-
label: "biome" + (tooling.linter === "biome" ? color.dim(" (installed)") : "")
|
|
1284
|
-
}
|
|
1285
|
-
],
|
|
1286
|
-
initialValue: detectedLinter
|
|
1287
|
-
});
|
|
1288
|
-
if (p.isCancel(linterChoice)) {
|
|
1289
|
-
p.cancel("Operation cancelled.");
|
|
1290
|
-
process.exit(0);
|
|
1291
|
-
}
|
|
1292
|
-
const formatterChoice = await p.select({
|
|
1293
|
-
message: "Formatter",
|
|
1294
|
-
options: [
|
|
1295
|
-
{
|
|
1296
|
-
value: "oxfmt",
|
|
1297
|
-
label: "oxfmt" + (tooling.formatter === "oxfmt" ? color.dim(" (installed)") : "")
|
|
1298
|
-
},
|
|
1299
|
-
{
|
|
1300
|
-
value: "prettier",
|
|
1301
|
-
label: "prettier" + (tooling.formatter === "prettier" || existingConfigs.formatter === "prettier" ? color.dim(" (installed)") : "")
|
|
1302
|
-
},
|
|
1303
|
-
{
|
|
1304
|
-
value: "biome",
|
|
1305
|
-
label: "biome" + (tooling.formatter === "biome" ? color.dim(" (installed)") : "")
|
|
1306
|
-
}
|
|
1307
|
-
],
|
|
1308
|
-
initialValue: detectedFormatter
|
|
1309
|
-
});
|
|
1310
|
-
if (p.isCancel(formatterChoice)) {
|
|
1311
|
-
p.cancel("Operation cancelled.");
|
|
1312
|
-
process.exit(0);
|
|
1313
|
-
}
|
|
1314
|
-
linter = linterChoice;
|
|
1315
|
-
formatter = formatterChoice;
|
|
1316
|
-
}
|
|
1317
|
-
console.log();
|
|
1318
|
-
const spinner = p.spinner();
|
|
1319
|
-
spinner.start("Fixing workspace...");
|
|
1320
|
-
try {
|
|
1321
|
-
const files = {};
|
|
1322
|
-
const tsConfigExists = await fileExists(
|
|
1323
|
-
join(monorepoRoot, ".config/typescript/package.json")
|
|
1324
|
-
);
|
|
1325
|
-
if (!tsConfigExists) {
|
|
1326
|
-
generateTypescriptConfigPackage(files);
|
|
1327
|
-
}
|
|
1328
|
-
if (linter === "oxlint") {
|
|
1329
|
-
const oxlintExists = await fileExists(
|
|
1330
|
-
join(monorepoRoot, ".config/oxlint/package.json")
|
|
1331
|
-
);
|
|
1332
|
-
if (!oxlintExists) generateOxlintConfigPackage(files);
|
|
1333
|
-
} else if (linter === "eslint") {
|
|
1334
|
-
const eslintPkgExists = await fileExists(
|
|
1335
|
-
join(monorepoRoot, ".config/eslint/package.json")
|
|
1336
|
-
);
|
|
1337
|
-
if (!eslintPkgExists) {
|
|
1338
|
-
if (existingConfigs.eslintConfigPath) {
|
|
1339
|
-
await migrateEslintConfig(monorepoRoot, files);
|
|
1340
|
-
} else {
|
|
1341
|
-
generateEslintConfigPackage(files);
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
if (formatter === "oxfmt") {
|
|
1346
|
-
const oxfmtExists = await fileExists(
|
|
1347
|
-
join(monorepoRoot, ".config/oxfmt/package.json")
|
|
1348
|
-
);
|
|
1349
|
-
if (!oxfmtExists) generateOxfmtConfigPackage(files);
|
|
1350
|
-
} else if (formatter === "prettier") {
|
|
1351
|
-
const prettierPkgExists = await fileExists(
|
|
1352
|
-
join(monorepoRoot, ".config/prettier/package.json")
|
|
1353
|
-
);
|
|
1354
|
-
if (!prettierPkgExists) {
|
|
1355
|
-
if (existingConfigs.prettierConfigPath) {
|
|
1356
|
-
await migratePrettierConfig(monorepoRoot, files);
|
|
1357
|
-
} else {
|
|
1358
|
-
generatePrettierConfigPackage(files);
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
if ((linter === "biome" || formatter === "biome") && !existingConfigs.biomeConfigPath) {
|
|
1363
|
-
const biomeConfig = {
|
|
1364
|
-
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
1365
|
-
vcs: {
|
|
1366
|
-
enabled: true,
|
|
1367
|
-
clientKind: "git",
|
|
1368
|
-
useIgnoreFile: true
|
|
1369
|
-
},
|
|
1370
|
-
linter: {
|
|
1371
|
-
enabled: linter === "biome",
|
|
1372
|
-
rules: {
|
|
1373
|
-
recommended: true
|
|
1374
|
-
}
|
|
1375
|
-
},
|
|
1376
|
-
formatter: {
|
|
1377
|
-
enabled: formatter === "biome"
|
|
1378
|
-
}
|
|
1379
|
-
};
|
|
1380
|
-
files["biome.json"] = {
|
|
1381
|
-
type: "text",
|
|
1382
|
-
content: JSON.stringify(biomeConfig, null, 2)
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
for (const [filePath, file] of Object.entries(files)) {
|
|
1386
|
-
const fullPath = join(monorepoRoot, filePath);
|
|
1387
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
1388
|
-
await writeFile(fullPath, file.content);
|
|
1389
|
-
}
|
|
1390
|
-
await ensureConfigInWorkspace(monorepoRoot);
|
|
1391
|
-
if (existingConfigs.eslintConfigPath && linter === "eslint") {
|
|
1392
|
-
try {
|
|
1393
|
-
await unlink(existingConfigs.eslintConfigPath);
|
|
1394
|
-
} catch {
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
if (existingConfigs.prettierConfigPath && formatter === "prettier") {
|
|
1398
|
-
try {
|
|
1399
|
-
await unlink(existingConfigs.prettierConfigPath);
|
|
1400
|
-
} catch {
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
spinner.stop(color.green("\u2713") + " Workspace fixed!");
|
|
1404
|
-
const generated = Object.keys(files).filter(
|
|
1405
|
-
(f) => f.endsWith("package.json")
|
|
1406
|
-
);
|
|
1407
|
-
for (const pkgFile of generated) {
|
|
1408
|
-
const pkgName = pkgFile.replace("/package.json", "");
|
|
1409
|
-
console.log(color.dim(` Generated ${pkgName}`));
|
|
1410
|
-
}
|
|
1411
|
-
const vscodeSettingsExists = await fileExists(
|
|
1412
|
-
join(monorepoRoot, ".vscode/settings.json")
|
|
1413
|
-
);
|
|
1414
|
-
const vscodeExtensionsExists = await fileExists(
|
|
1415
|
-
join(monorepoRoot, ".vscode/extensions.json")
|
|
1416
|
-
);
|
|
1417
|
-
const vscodeExists = vscodeSettingsExists && vscodeExtensionsExists;
|
|
1418
|
-
if (!vscodeExists) {
|
|
1419
|
-
let addVscode = false;
|
|
1420
|
-
if (isNonInteractive) {
|
|
1421
|
-
addVscode = true;
|
|
1422
|
-
} else {
|
|
1423
|
-
const vscodeChoice = await p.confirm({
|
|
1424
|
-
message: "Generate VS Code settings?",
|
|
1425
|
-
initialValue: true
|
|
1426
|
-
});
|
|
1427
|
-
addVscode = !p.isCancel(vscodeChoice) && vscodeChoice;
|
|
1428
|
-
}
|
|
1429
|
-
if (addVscode) {
|
|
1430
|
-
const vscodeFiles = {};
|
|
1431
|
-
generateVscodeFiles(vscodeFiles, linter, formatter);
|
|
1432
|
-
for (const [filePath, file] of Object.entries(vscodeFiles)) {
|
|
1433
|
-
const fullPath = join(monorepoRoot, filePath);
|
|
1434
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
1435
|
-
await writeFile(fullPath, file.content);
|
|
1436
|
-
}
|
|
1437
|
-
console.log(color.dim(" Generated .vscode/settings.json"));
|
|
1438
|
-
console.log(color.dim(" Generated .vscode/extensions.json"));
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
const aiFilePaths = {
|
|
1442
|
-
"cursor-rules": ".cursor/rules",
|
|
1443
|
-
"agents-md": "AGENTS.md",
|
|
1444
|
-
"claude-md": "CLAUDE.md",
|
|
1445
|
-
"copilot-md": ".github/copilot-instructions.md"
|
|
1446
|
-
};
|
|
1447
|
-
const existingAiFiles = [];
|
|
1448
|
-
for (const [choice, path] of Object.entries(aiFilePaths)) {
|
|
1449
|
-
if (await fileExists(join(monorepoRoot, path))) {
|
|
1450
|
-
existingAiFiles.push(choice);
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
let selectedAiFiles = [];
|
|
1454
|
-
const savedAiFiles = getAiFiles();
|
|
1455
|
-
const availableChoices = ["cursor-rules", "agents-md", "claude-md", "copilot-md"].filter((c) => !existingAiFiles.includes(c));
|
|
1456
|
-
if (availableChoices.length === 0) {
|
|
1457
|
-
} else if (isNonInteractive) {
|
|
1458
|
-
const preferred = savedAiFiles ?? ["cursor-rules"];
|
|
1459
|
-
selectedAiFiles = preferred.filter((f) => availableChoices.includes(f));
|
|
1460
|
-
} else if (savedAiFiles && savedAiFiles.length > 0) {
|
|
1461
|
-
const availableSaved = savedAiFiles.filter(
|
|
1462
|
-
(f) => availableChoices.includes(f)
|
|
1463
|
-
);
|
|
1464
|
-
if (availableSaved.length > 0) {
|
|
1465
|
-
const savedLabels = availableSaved.map((f) => aiFilePaths[f]).join(", ");
|
|
1466
|
-
const useDefault = await p.confirm({
|
|
1467
|
-
message: `Generate AI instruction files? ${color.dim(
|
|
1468
|
-
`(${savedLabels})`
|
|
1469
|
-
)}`,
|
|
1470
|
-
initialValue: true
|
|
1471
|
-
});
|
|
1472
|
-
if (!p.isCancel(useDefault) && useDefault) {
|
|
1473
|
-
selectedAiFiles = availableSaved;
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
} else {
|
|
1477
|
-
const aiFilesChoice = await p.multiselect({
|
|
1478
|
-
message: "Generate AI instruction files?",
|
|
1479
|
-
options: availableChoices.map((c) => ({
|
|
1480
|
-
value: c,
|
|
1481
|
-
label: aiFilePaths[c],
|
|
1482
|
-
hint: c === "cursor-rules" ? "Cursor AI" : c === "agents-md" ? "GitHub Copilot, general" : c === "claude-md" ? "Claude" : "GitHub Copilot"
|
|
1483
|
-
})),
|
|
1484
|
-
required: false
|
|
1485
|
-
});
|
|
1486
|
-
if (!p.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
|
|
1487
|
-
selectedAiFiles = aiFilesChoice;
|
|
1488
|
-
const saveChoice = await p.confirm({
|
|
1489
|
-
message: "Save as default for future?",
|
|
1490
|
-
initialValue: true
|
|
1491
|
-
});
|
|
1492
|
-
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1493
|
-
setAiFiles(selectedAiFiles);
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
if (selectedAiFiles.length > 0) {
|
|
1498
|
-
const scope = await getMonorepoScope(monorepoRoot);
|
|
1499
|
-
const aiFilesOutput = {};
|
|
1500
|
-
generateAiFiles(aiFilesOutput, {
|
|
1501
|
-
name: scope,
|
|
1502
|
-
packageManager: "pnpm",
|
|
1503
|
-
linter,
|
|
1504
|
-
formatter,
|
|
1505
|
-
aiFiles: selectedAiFiles
|
|
1506
|
-
});
|
|
1507
|
-
for (const [filePath, file] of Object.entries(aiFilesOutput)) {
|
|
1508
|
-
const fullPath = join(monorepoRoot, filePath);
|
|
1509
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
1510
|
-
await writeFile(fullPath, file.content);
|
|
1511
|
-
console.log(color.dim(` Generated ${filePath}`));
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
process.exit(0);
|
|
1515
|
-
} catch (error) {
|
|
1516
|
-
spinner.stop(color.red("\u2717") + " Failed to fix workspace");
|
|
1517
|
-
console.error(error);
|
|
1518
|
-
process.exit(1);
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
async function handleWorkspaceCommand(name, options) {
|
|
1522
|
-
const monorepoRoot = await detectMonorepoRoot();
|
|
1523
|
-
if (!monorepoRoot) {
|
|
1524
|
-
console.error(
|
|
1525
|
-
color.red("Error:") + " --workspace flag requires being inside a monorepo"
|
|
1526
|
-
);
|
|
1527
|
-
process.exit(1);
|
|
1528
|
-
}
|
|
1529
|
-
if (!name) {
|
|
1530
|
-
console.error(
|
|
1531
|
-
color.red("Error:") + " Package name is required with --workspace flag"
|
|
1532
|
-
);
|
|
1533
|
-
console.log(
|
|
1534
|
-
color.dim(
|
|
1535
|
-
" Example: pnpm create krispya my-lib --workspace --type library"
|
|
1536
|
-
)
|
|
1537
|
-
);
|
|
1538
|
-
process.exit(1);
|
|
1539
|
-
}
|
|
1540
|
-
const scope = await getMonorepoScope(monorepoRoot);
|
|
1541
|
-
const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
|
|
1542
|
-
const projectType = options.type ?? "app";
|
|
1543
|
-
const defaultDir = projectType === "library" ? "packages" : "apps";
|
|
1544
|
-
const targetDir = options.dir ?? defaultDir;
|
|
1545
|
-
const template = options.template ?? "vanilla";
|
|
1546
|
-
const baseTemplate = getBaseTemplate(template);
|
|
1547
|
-
const scopedName = name.startsWith("@") ? name : `@${scope}/${name}`;
|
|
1548
|
-
const fullPackagePath = join(monorepoRoot, targetDir, name);
|
|
1549
|
-
try {
|
|
1550
|
-
await access(fullPackagePath, constants$1.F_OK);
|
|
1551
|
-
console.error(
|
|
1552
|
-
color.red("Error:") + ` Directory ${targetDir}/${name} already exists`
|
|
1553
|
-
);
|
|
1554
|
-
process.exit(1);
|
|
1555
|
-
} catch {
|
|
1556
|
-
}
|
|
1557
|
-
const versions = {};
|
|
1558
|
-
const versionPromises = [];
|
|
1559
|
-
const isLibrary = projectType === "library";
|
|
1560
|
-
if (!isLibrary) {
|
|
1561
|
-
versionPromises.push(
|
|
1562
|
-
getLatestNpmVersion("vite", "6.3.4").then((v) => {
|
|
1563
|
-
versions.vite = v;
|
|
1564
|
-
})
|
|
1565
|
-
);
|
|
1566
|
-
}
|
|
1567
|
-
const linter = inheritedTooling.linter ?? options.linter ?? "oxlint";
|
|
1568
|
-
const formatter = inheritedTooling.formatter ?? options.formatter ?? "oxfmt";
|
|
1569
|
-
await Promise.all(versionPromises);
|
|
1570
|
-
const relativePkgPath = join(targetDir, name);
|
|
1571
|
-
const workspaceRoot = calculateWorkspaceRoot(relativePkgPath);
|
|
1572
|
-
const generateOptions = {
|
|
1573
|
-
name: scopedName,
|
|
1574
|
-
projectType,
|
|
1575
|
-
libraryBundler: isLibrary ? options.bundler ?? "unbuild" : void 0,
|
|
1576
|
-
template,
|
|
1577
|
-
linter,
|
|
1578
|
-
formatter,
|
|
1579
|
-
workspaceRoot,
|
|
1580
|
-
versions,
|
|
1581
|
-
...baseTemplate === "r3f" && {
|
|
1582
|
-
drei: options.drei ? {} : void 0,
|
|
1583
|
-
handle: options.handle ? {} : void 0,
|
|
1584
|
-
leva: options.leva ? {} : void 0,
|
|
1585
|
-
postprocessing: options.postprocessing ? {} : void 0,
|
|
1586
|
-
rapier: options.rapier ? {} : void 0,
|
|
1587
|
-
xr: options.xr ? {} : void 0,
|
|
1588
|
-
uikit: options.uikit ? {} : void 0,
|
|
1589
|
-
offscreen: options.offscreen ? {} : void 0,
|
|
1590
|
-
zustand: options.zustand ? {} : void 0,
|
|
1591
|
-
koota: options.koota ? {} : void 0,
|
|
1592
|
-
viverse: options.viverse ? {} : void 0,
|
|
1593
|
-
triplex: options.triplex ? {} : void 0
|
|
1594
|
-
}
|
|
1595
|
-
};
|
|
1596
|
-
console.log(
|
|
1597
|
-
color.cyan("Creating") + ` ${scopedName} in ${targetDir}/${name}...`
|
|
1598
|
-
);
|
|
1599
|
-
try {
|
|
1600
|
-
const files = generate(generateOptions);
|
|
1601
|
-
await writeGeneratedFiles(fullPackagePath, files);
|
|
1602
|
-
console.log(
|
|
1603
|
-
color.green("\u2713") + ` Created ${scopedName} at ${targetDir}/${name}`
|
|
1604
|
-
);
|
|
1605
|
-
process.exit(0);
|
|
1606
|
-
} catch (error) {
|
|
1607
|
-
console.error(color.red("Error:") + " Failed to create package");
|
|
1608
|
-
console.error(String(error));
|
|
1609
|
-
process.exit(1);
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
async function handleMonorepoCreation(generateOptions) {
|
|
1613
|
-
const { generateMonorepo } = await import('./chunks/index.mjs').then(function (n) { return n.s; });
|
|
1614
|
-
const packageManager = generateOptions.packageManager || "pnpm";
|
|
1615
|
-
if (packageManager === "pnpm") {
|
|
1616
|
-
generateOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
1617
|
-
} else if (packageManager === "yarn") {
|
|
1618
|
-
generateOptions.yarnVersion = await getLatestYarnVersion();
|
|
1619
|
-
} else if (packageManager === "npm") {
|
|
1620
|
-
generateOptions.npmVersion = await getLatestNpmCliVersion();
|
|
1621
|
-
}
|
|
1622
|
-
const nodeVersion = generateOptions.nodeVersion ?? "latest";
|
|
1623
|
-
if (nodeVersion === "latest") {
|
|
1624
|
-
generateOptions.nodeVersion = await getLatestNodeVersion();
|
|
1625
|
-
}
|
|
1626
|
-
const savedAiFiles = getAiFiles();
|
|
1627
|
-
let selectedAiFiles = [];
|
|
1628
|
-
if (savedAiFiles && savedAiFiles.length > 0) {
|
|
1629
|
-
const aiFileLabels = {
|
|
1630
|
-
"cursor-rules": ".cursor/rules",
|
|
1631
|
-
"agents-md": "AGENTS.md",
|
|
1632
|
-
"claude-md": "CLAUDE.md",
|
|
1633
|
-
"copilot-md": ".github/copilot-instructions.md"
|
|
1634
|
-
};
|
|
1635
|
-
const savedLabels = savedAiFiles.map((f) => aiFileLabels[f]).join(", ");
|
|
1636
|
-
const useDefault = await p.confirm({
|
|
1637
|
-
message: `Generate AI instruction files? ${color.dim(
|
|
1638
|
-
`(${savedLabels})`
|
|
1639
|
-
)}`,
|
|
1640
|
-
initialValue: true
|
|
1641
|
-
});
|
|
1642
|
-
if (!p.isCancel(useDefault) && useDefault) {
|
|
1643
|
-
selectedAiFiles = savedAiFiles;
|
|
1644
|
-
}
|
|
1645
|
-
} else {
|
|
1646
|
-
const aiFilesChoice = await p.multiselect({
|
|
1647
|
-
message: "Generate AI instruction files?",
|
|
1648
|
-
options: [
|
|
1649
|
-
{ value: "cursor-rules", label: ".cursor/rules", hint: "Cursor AI" },
|
|
1650
|
-
{
|
|
1651
|
-
value: "agents-md",
|
|
1652
|
-
label: "AGENTS.md",
|
|
1653
|
-
hint: "GitHub Copilot, general"
|
|
1654
|
-
},
|
|
1655
|
-
{ value: "claude-md", label: "CLAUDE.md", hint: "Claude" },
|
|
1656
|
-
{
|
|
1657
|
-
value: "copilot-md",
|
|
1658
|
-
label: ".github/copilot-instructions.md",
|
|
1659
|
-
hint: "GitHub Copilot"
|
|
1660
|
-
}
|
|
1661
|
-
],
|
|
1662
|
-
required: false
|
|
1663
|
-
});
|
|
1664
|
-
if (!p.isCancel(aiFilesChoice) && aiFilesChoice.length > 0) {
|
|
1665
|
-
selectedAiFiles = aiFilesChoice;
|
|
1666
|
-
const saveChoice = await p.confirm({
|
|
1667
|
-
message: "Save as default for future monorepos?",
|
|
1668
|
-
initialValue: true
|
|
1669
|
-
});
|
|
1670
|
-
if (!p.isCancel(saveChoice) && saveChoice) {
|
|
1671
|
-
setAiFiles(selectedAiFiles);
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
const projectPath = join(cwd(), generateOptions.name);
|
|
1676
|
-
const spinner = p.spinner();
|
|
1677
|
-
spinner.start("Creating monorepo workspace...");
|
|
1678
|
-
try {
|
|
1679
|
-
const { files } = generateMonorepo({
|
|
1680
|
-
name: generateOptions.name,
|
|
1681
|
-
linter: generateOptions.linter ?? "oxlint",
|
|
1682
|
-
formatter: generateOptions.formatter ?? "oxfmt",
|
|
1683
|
-
packageManager,
|
|
1684
|
-
pnpmVersion: generateOptions.pnpmVersion,
|
|
1685
|
-
pnpmManageVersions: generateOptions.pnpmManageVersions,
|
|
1686
|
-
nodeVersion: generateOptions.nodeVersion,
|
|
1687
|
-
aiFiles: selectedAiFiles.length > 0 ? selectedAiFiles : void 0
|
|
1688
|
-
});
|
|
1689
|
-
const filePaths = Object.keys(files).sort();
|
|
1690
|
-
for (const filePath of filePaths) {
|
|
1691
|
-
const fullFilePath = join(projectPath, filePath);
|
|
1692
|
-
await mkdir(dirname(fullFilePath), { recursive: true });
|
|
1693
|
-
const file = files[filePath];
|
|
1694
|
-
if (file.type === "text") {
|
|
1695
|
-
await writeFile(fullFilePath, file.content);
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
spinner.stop(color.green.inverse(" \u2713 Monorepo workspace created! "));
|
|
1699
|
-
const newMonorepoTooling = {
|
|
1700
|
-
linter: generateOptions.linter,
|
|
1701
|
-
formatter: generateOptions.formatter
|
|
1702
|
-
};
|
|
1703
|
-
const scope = generateOptions.name;
|
|
1704
|
-
let addMore = true;
|
|
1705
|
-
while (addMore) {
|
|
1706
|
-
addMore = await createPackageInWorkspace(
|
|
1707
|
-
projectPath,
|
|
1708
|
-
packageManager,
|
|
1709
|
-
newMonorepoTooling,
|
|
1710
|
-
scope
|
|
1711
|
-
);
|
|
1712
|
-
}
|
|
1713
|
-
const nextSteps = [
|
|
1714
|
-
`cd ${generateOptions.name}`,
|
|
1715
|
-
`${packageManager} install`,
|
|
1716
|
-
`${packageManager} run dev`
|
|
1717
|
-
].join("\n");
|
|
1718
|
-
p.note(nextSteps, "Next steps");
|
|
1719
|
-
await promptAndOpenEditor(projectPath);
|
|
1720
|
-
p.outro(color.green("Happy coding! \u2728"));
|
|
1721
|
-
process.exit(0);
|
|
1722
|
-
} catch (error) {
|
|
1723
|
-
spinner.stop("Failed to create monorepo workspace");
|
|
1724
|
-
p.log.error(String(error));
|
|
1725
|
-
process.exit(1);
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
async function handleStandaloneProjectCreation(generateOptions) {
|
|
1729
|
-
const base = generateOptions.template ? getBaseTemplate(generateOptions.template) : "vanilla";
|
|
1730
|
-
const defaultFallbackName = base === "vanilla" ? "vanilla-app" : base === "react" ? "react-app" : "react-three-app";
|
|
1731
|
-
generateOptions.name ??= defaultFallbackName;
|
|
1732
|
-
const packageManager = generateOptions.packageManager || "pnpm";
|
|
1733
|
-
if (packageManager === "pnpm") {
|
|
1734
|
-
generateOptions.pnpmVersion = await getLatestPnpmVersion();
|
|
1735
|
-
} else if (packageManager === "yarn") {
|
|
1736
|
-
generateOptions.yarnVersion = await getLatestYarnVersion();
|
|
1737
|
-
} else if (packageManager === "npm") {
|
|
1738
|
-
generateOptions.npmVersion = await getLatestNpmCliVersion();
|
|
1739
|
-
}
|
|
1740
|
-
const nodeVersion = generateOptions.nodeVersion ?? "latest";
|
|
1741
|
-
if (nodeVersion === "latest") {
|
|
1742
|
-
generateOptions.nodeVersion = await getLatestNodeVersion();
|
|
1743
|
-
}
|
|
1744
|
-
const versions = {};
|
|
1745
|
-
const versionPromises = [];
|
|
1746
|
-
const isLibrary = generateOptions.projectType === "library";
|
|
1747
|
-
const testing = generateOptions.testing ?? (isLibrary ? "vitest" : "none");
|
|
1748
|
-
if (testing === "vitest") {
|
|
1749
|
-
versionPromises.push(
|
|
1750
|
-
getLatestNpmVersion("vitest", "4.0.0").then((v) => {
|
|
1751
|
-
versions.vitest = v;
|
|
1752
|
-
})
|
|
1753
|
-
);
|
|
1754
|
-
}
|
|
1755
|
-
if (!isLibrary) {
|
|
1756
|
-
versionPromises.push(
|
|
1757
|
-
getLatestNpmVersion("vite", "6.3.4").then((v) => {
|
|
1758
|
-
versions.vite = v;
|
|
1759
|
-
})
|
|
1760
|
-
);
|
|
1761
|
-
}
|
|
1762
|
-
const linter = generateOptions.linter ?? "oxlint";
|
|
1763
|
-
if (linter === "eslint") {
|
|
1764
|
-
versionPromises.push(
|
|
1765
|
-
getLatestNpmVersion("eslint", "9.17.0").then((v) => {
|
|
1766
|
-
versions.eslint = v;
|
|
1767
|
-
})
|
|
1768
|
-
);
|
|
1769
|
-
} else if (linter === "oxlint") {
|
|
1770
|
-
versionPromises.push(
|
|
1771
|
-
getLatestNpmVersion("oxlint", "0.16.0").then((v) => {
|
|
1772
|
-
versions.oxlint = v;
|
|
1773
|
-
})
|
|
1774
|
-
);
|
|
1775
|
-
} else if (linter === "biome") {
|
|
1776
|
-
versionPromises.push(
|
|
1777
|
-
getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
|
|
1778
|
-
versions.biome = v;
|
|
1779
|
-
})
|
|
1780
|
-
);
|
|
1781
|
-
}
|
|
1782
|
-
const formatter = generateOptions.formatter ?? "oxfmt";
|
|
1783
|
-
if (formatter === "prettier") {
|
|
1784
|
-
versionPromises.push(
|
|
1785
|
-
getLatestNpmVersion("prettier", "3.4.2").then((v) => {
|
|
1786
|
-
versions.prettier = v;
|
|
1787
|
-
})
|
|
1788
|
-
);
|
|
1789
|
-
} else if (formatter === "oxfmt") {
|
|
1790
|
-
versionPromises.push(
|
|
1791
|
-
getLatestNpmVersion("oxfmt", "0.1.0").then((v) => {
|
|
1792
|
-
versions.oxfmt = v;
|
|
1793
|
-
})
|
|
1794
|
-
);
|
|
1795
|
-
} else if (formatter === "biome" && linter !== "biome") {
|
|
1796
|
-
versionPromises.push(
|
|
1797
|
-
getLatestNpmVersion("@biomejs/biome", "1.9.4").then((v) => {
|
|
1798
|
-
versions.biome = v;
|
|
1799
|
-
})
|
|
1800
|
-
);
|
|
1801
|
-
}
|
|
1802
|
-
await Promise.all(versionPromises);
|
|
1803
|
-
generateOptions.versions = versions;
|
|
1804
|
-
const projectPath = join(cwd(), generateOptions.name);
|
|
1805
|
-
const spinner = p.spinner();
|
|
1806
|
-
spinner.start("Creating project...");
|
|
1807
|
-
try {
|
|
1808
|
-
const files = generate(generateOptions);
|
|
1809
|
-
await writeGeneratedFiles(projectPath, files);
|
|
1810
|
-
spinner.stop(color.green.inverse(" \u2713 Project created! "));
|
|
1811
|
-
const nextSteps = isLibrary ? [
|
|
1812
|
-
`cd ${generateOptions.name}`,
|
|
1813
|
-
`${packageManager} install`,
|
|
1814
|
-
`${packageManager} run build`
|
|
1815
|
-
].join("\n") : [
|
|
1816
|
-
`cd ${generateOptions.name}`,
|
|
1817
|
-
`${packageManager} install`,
|
|
1818
|
-
`${packageManager} run dev`
|
|
1819
|
-
].join("\n");
|
|
1820
|
-
p.note(nextSteps, "Next steps");
|
|
1821
|
-
await promptAndOpenEditor(projectPath);
|
|
1822
|
-
p.outro(color.green("Happy coding! \u2728"));
|
|
1823
|
-
} catch (error) {
|
|
1824
|
-
spinner.stop("Failed to create project");
|
|
1825
|
-
p.log.error(String(error));
|
|
1826
|
-
process.exit(1);
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
async function handleInteractiveMonorepoMode(monorepoRoot) {
|
|
1830
|
-
const choice = await p.select({
|
|
1831
|
-
message: "Detected monorepo workspace",
|
|
1832
|
-
options: [
|
|
1833
|
-
{ value: "add", label: "Add new package to this workspace" },
|
|
1834
|
-
{ value: "standalone", label: "Create standalone project" }
|
|
1835
|
-
],
|
|
1836
|
-
initialValue: "add"
|
|
1837
|
-
});
|
|
1838
|
-
if (p.isCancel(choice)) {
|
|
1839
|
-
p.cancel("Operation cancelled.");
|
|
1840
|
-
process.exit(0);
|
|
1841
|
-
}
|
|
1842
|
-
if (choice === "add") {
|
|
1843
|
-
const inheritedTooling = await detectWorkspaceTooling(monorepoRoot);
|
|
1844
|
-
if (inheritedTooling.linter || inheritedTooling.formatter) {
|
|
1845
|
-
const toolingInfo = [
|
|
1846
|
-
inheritedTooling.linter && `linter: ${inheritedTooling.linter}`,
|
|
1847
|
-
inheritedTooling.formatter && `formatter: ${inheritedTooling.formatter}`
|
|
1848
|
-
].filter(Boolean).join(", ");
|
|
1849
|
-
p.log.info(`Using workspace tooling (${toolingInfo})`);
|
|
1850
|
-
}
|
|
1851
|
-
const scope = await getMonorepoScope(monorepoRoot);
|
|
1852
|
-
let addMore = true;
|
|
1853
|
-
while (addMore) {
|
|
1854
|
-
addMore = await createPackageInWorkspace(
|
|
1855
|
-
monorepoRoot,
|
|
1856
|
-
"pnpm",
|
|
1857
|
-
inheritedTooling,
|
|
1858
|
-
scope
|
|
1859
|
-
);
|
|
1860
|
-
}
|
|
1861
|
-
p.note(
|
|
1862
|
-
[`cd ${monorepoRoot}`, "pnpm install", "pnpm run dev"].join("\n"),
|
|
1863
|
-
"Next steps"
|
|
1864
|
-
);
|
|
1865
|
-
await promptAndOpenEditor(monorepoRoot);
|
|
1866
|
-
p.outro(color.green("Happy coding! \u2728"));
|
|
1867
|
-
process.exit(0);
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
async function main() {
|
|
1871
|
-
const program = new Command().name("create-krispya").description(
|
|
1872
|
-
"CLI for creating Vanilla, React, and React Three Fiber projects"
|
|
1873
|
-
).argument("[name]", "name for the project").option("--type <type>", "project type: app or library (default: app)").option(
|
|
1874
|
-
"--bundler <bundler>",
|
|
1875
|
-
"library bundler: unbuild or tsdown (default: unbuild, only for libraries)"
|
|
1876
|
-
).option(
|
|
1877
|
-
"--template <type>",
|
|
1878
|
-
"project template: vanilla, vanilla-js, react, react-js, r3f, r3f-js (default: vanilla)"
|
|
1879
|
-
).option(
|
|
1880
|
-
"--linter <type>",
|
|
1881
|
-
"linter: eslint, oxlint, or biome (default: oxlint)"
|
|
1882
|
-
).option(
|
|
1883
|
-
"--formatter <type>",
|
|
1884
|
-
"formatter: prettier, oxfmt, or biome (default: oxfmt)"
|
|
1885
|
-
).option("--drei", "add @react-three/drei (r3f only)").option("--handle", "add @react-three/handle (r3f only)").option("--leva", "add leva (r3f only)").option("--postprocessing", "add @react-three/postprocessing (r3f only)").option("--rapier", "add @react-three/rapier (r3f only)").option("--xr", "add @react-three/xr (r3f only)").option("--uikit", "add @react-three/uikit (r3f only)").option("--offscreen", "add @react-three/offscreen (r3f only)").option("--zustand", "add zustand (r3f only)").option("--koota", "add koota (r3f only)").option("--triplex", "set up triplex development environment (r3f only)").option("--viverse", "set up viverse deployment (r3f only)").option(
|
|
1886
|
-
"--package-manager <manager>",
|
|
1887
|
-
"specify package manager (e.g. npm, yarn, pnpm)"
|
|
1888
|
-
).option(
|
|
1889
|
-
"--pnpm-manage-versions",
|
|
1890
|
-
"enable manage-package-manager-versions in pnpm-workspace.yaml (default: true)"
|
|
1891
|
-
).option(
|
|
1892
|
-
"--no-pnpm-manage-versions",
|
|
1893
|
-
"disable manage-package-manager-versions in pnpm-workspace.yaml"
|
|
1894
|
-
).option(
|
|
1895
|
-
"--node-version <version>",
|
|
1896
|
-
'set Node.js version for engines.node field (default: "latest")'
|
|
1897
|
-
).option(
|
|
1898
|
-
"--workspace",
|
|
1899
|
-
"Add package to current monorepo workspace (non-interactive)"
|
|
1900
|
-
).option(
|
|
1901
|
-
"--dir <directory>",
|
|
1902
|
-
"Target directory for --workspace (default: apps/ or packages/)"
|
|
1903
|
-
).option("--clear-config", "Clear saved preferences (e.g. editor choice)").option("--config-path", "Print the path to the config file").option(
|
|
1904
|
-
"--check",
|
|
1905
|
-
"Check if current directory is in a valid monorepo workspace"
|
|
1906
|
-
).option("--fix", "Fix monorepo by generating missing .config packages").option(
|
|
1907
|
-
"--path <directory>",
|
|
1908
|
-
"Run in specified directory instead of current working directory"
|
|
1909
|
-
).action(async (name, options) => {
|
|
1910
|
-
if (options.path) {
|
|
1911
|
-
process.chdir(options.path);
|
|
1912
|
-
}
|
|
1913
|
-
if (options.clearConfig) {
|
|
1914
|
-
clearConfig();
|
|
1915
|
-
console.log("Configuration cleared.");
|
|
1916
|
-
process.exit(0);
|
|
1917
|
-
}
|
|
1918
|
-
if (options.configPath) {
|
|
1919
|
-
console.log(getConfigPath());
|
|
1920
|
-
process.exit(0);
|
|
1921
|
-
}
|
|
1922
|
-
if (name?.startsWith("-")) {
|
|
1923
|
-
switch (name) {
|
|
1924
|
-
case "--version":
|
|
1925
|
-
case "-V":
|
|
1926
|
-
console.log(pkg.version);
|
|
1927
|
-
process.exit(0);
|
|
1928
|
-
case "--help":
|
|
1929
|
-
case "-h":
|
|
1930
|
-
program.help();
|
|
1931
|
-
break;
|
|
1932
|
-
case "--clear-config":
|
|
1933
|
-
clearConfig();
|
|
1934
|
-
console.log("Configuration cleared.");
|
|
1935
|
-
process.exit(0);
|
|
1936
|
-
case "--config-path":
|
|
1937
|
-
console.log(getConfigPath());
|
|
1938
|
-
process.exit(0);
|
|
1939
|
-
case "--check":
|
|
1940
|
-
await handleCheckCommand();
|
|
1941
|
-
break;
|
|
1942
|
-
case "--fix":
|
|
1943
|
-
options.fix = true;
|
|
1944
|
-
break;
|
|
1945
|
-
default:
|
|
1946
|
-
console.error(color.red(`Unknown option: ${name}`));
|
|
1947
|
-
process.exit(1);
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
if (options.check) {
|
|
1951
|
-
await handleCheckCommand();
|
|
1952
|
-
}
|
|
1953
|
-
if (options.fix) {
|
|
1954
|
-
await handleFixCommand(options);
|
|
1955
|
-
}
|
|
1956
|
-
if (options.dir && !options.workspace) {
|
|
1957
|
-
console.error(color.red("Error:") + " --dir requires --workspace flag");
|
|
1958
|
-
console.log(
|
|
1959
|
-
color.dim(
|
|
1960
|
-
" Example: pnpm create krispya my-lib --workspace --dir examples"
|
|
1961
|
-
)
|
|
1962
|
-
);
|
|
1963
|
-
process.exit(1);
|
|
1964
|
-
}
|
|
1965
|
-
if (options.workspace) {
|
|
1966
|
-
await handleWorkspaceCommand(name, options);
|
|
1967
|
-
}
|
|
1968
|
-
console.clear();
|
|
1969
|
-
p.intro(color.bgCyan(color.black(` create-krispya v${pkg.version} `)));
|
|
1970
|
-
const monorepoRoot = await detectMonorepoRoot();
|
|
1971
|
-
if (monorepoRoot && Object.keys(options).length === 0) {
|
|
1972
|
-
await handleInteractiveMonorepoMode(monorepoRoot);
|
|
1973
|
-
}
|
|
1974
|
-
let generateOptions;
|
|
1975
|
-
if (Object.keys(options).length > 0) {
|
|
1976
|
-
const template = options.template ?? "vanilla";
|
|
1977
|
-
const baseTemplate = getBaseTemplate(template);
|
|
1978
|
-
const defaultName = getDefaultProjectName(template);
|
|
1979
|
-
const projectType = options.type ?? "app";
|
|
1980
|
-
generateOptions = {
|
|
1981
|
-
name: name || defaultName,
|
|
1982
|
-
projectType,
|
|
1983
|
-
libraryBundler: projectType === "library" ? options.bundler ?? "unbuild" : void 0,
|
|
1984
|
-
template,
|
|
1985
|
-
linter: options.linter ?? "oxlint",
|
|
1986
|
-
formatter: options.formatter ?? "oxfmt",
|
|
1987
|
-
...baseTemplate === "r3f" && {
|
|
1988
|
-
drei: options.drei ? {} : void 0,
|
|
1989
|
-
handle: options.handle ? {} : void 0,
|
|
1990
|
-
leva: options.leva ? {} : void 0,
|
|
1991
|
-
postprocessing: options.postprocessing ? {} : void 0,
|
|
1992
|
-
rapier: options.rapier ? {} : void 0,
|
|
1993
|
-
xr: options.xr ? {} : void 0,
|
|
1994
|
-
uikit: options.uikit ? {} : void 0,
|
|
1995
|
-
offscreen: options.offscreen ? {} : void 0,
|
|
1996
|
-
zustand: options.zustand ? {} : void 0,
|
|
1997
|
-
koota: options.koota ? {} : void 0,
|
|
1998
|
-
viverse: options.viverse ? {} : void 0,
|
|
1999
|
-
triplex: options.triplex ? {} : void 0
|
|
2000
|
-
},
|
|
2001
|
-
packageManager: options.packageManager,
|
|
2002
|
-
pnpmManageVersions: options.pnpmManageVersions,
|
|
2003
|
-
nodeVersion: options.nodeVersion ?? "latest"
|
|
2004
|
-
};
|
|
2005
|
-
} else {
|
|
2006
|
-
generateOptions = await promptForOptions(name);
|
|
2007
|
-
}
|
|
2008
|
-
if (generateOptions.projectType === "monorepo") {
|
|
2009
|
-
await handleMonorepoCreation(generateOptions);
|
|
2010
|
-
} else {
|
|
2011
|
-
await handleStandaloneProjectCreation(generateOptions);
|
|
2012
|
-
}
|
|
2013
|
-
});
|
|
2014
|
-
await program.parseAsync();
|
|
2015
|
-
}
|
|
2016
|
-
main().catch(console.error);
|