create-krispya 0.8.0 → 0.10.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.
@@ -9,62 +9,7 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
9
9
 
10
10
  const color__default = /*#__PURE__*/_interopDefaultCompat(color);
11
11
 
12
- const HtmlContent = `<!DOCTYPE html>
13
- <html lang="en">
14
- <head>
15
- <meta charset="UTF-8">
16
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
17
- <title>$title</title>
18
- </head>
19
- <body style="margin: 0; overscroll-behavior: none; user-select: none; touch-action: none;">
20
- <script type="module" src="$indexPath"><\/script>
21
- <div style="width: 100dvw; height: 100dvh; overflow: hidden;" id="root"></div>
22
- </body>
23
- </html>`;
24
- const ViteHtmlContent = `<!DOCTYPE html>
25
- <html lang="en">
26
- <head>
27
- <meta charset="UTF-8">
28
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
29
- <title>$title</title>
30
- </head>
31
- <body>
32
- <div id="app"></div>
33
- <script type="module" src="$indexPath"><\/script>
34
- </body>
35
- </html>`;
36
- const IndexContent = `import { StrictMode } from 'react'
37
- import { createRoot } from 'react-dom/client'
38
- import { App } from './app.js'
39
-
40
- createRoot(document.getElementById('root')!).render(
41
- <StrictMode>
42
- <App />
43
- </StrictMode>,
44
- )`;
45
- const ViteIndexContent = `import './style.css'
46
-
47
- document.querySelector('#app')!.innerHTML = \`
48
- <h1>Hello Vite!</h1>
49
- <p>Edit src/main.ts and save to see HMR in action.</p>
50
- \``;
51
- const ViteStyleContent = `body {
52
- font-family: system-ui, -apple-system, sans-serif;
53
- margin: 0;
54
- padding: 2rem;
55
- min-height: 100vh;
56
- background: #1a1a1a;
57
- color: #fff;
58
- }
59
-
60
- h1 {
61
- color: #646cff;
62
- }
63
-
64
- a {
65
- color: #646cff;
66
- }`;
67
- const GitAttributes = [
12
+ const gitAttributesContent = [
68
13
  "* text eol=lf",
69
14
  "*.png binary",
70
15
  "*.jpg binary",
@@ -89,55 +34,6 @@ const GitAttributes = [
89
34
  "*.glb binary",
90
35
  "*.gltf binary"
91
36
  ].join("\n");
92
- const defaultFormatterConfig = {
93
- printWidth: 102,
94
- tabWidth: 2,
95
- useTabs: false,
96
- semi: true,
97
- singleQuote: true,
98
- trailingComma: "es5",
99
- bracketSpacing: true,
100
- arrowParens: "always"
101
- };
102
- const defaultPrettierConfig = {
103
- $schema: "https://json.schemastore.org/prettierrc",
104
- ...defaultFormatterConfig,
105
- overrides: [
106
- {
107
- files: ["*.md", "**/*.md"],
108
- options: { semi: false }
109
- },
110
- {
111
- files: ["*.yml", "*.yaml", "**/*.yml", "**/*.yaml"],
112
- options: { semi: false }
113
- }
114
- ]
115
- };
116
- const defaultOxfmtConfig = {
117
- printWidth: defaultFormatterConfig.printWidth,
118
- tabWidth: defaultFormatterConfig.tabWidth,
119
- useTabs: defaultFormatterConfig.useTabs,
120
- semi: defaultFormatterConfig.semi,
121
- singleQuote: defaultFormatterConfig.singleQuote,
122
- trailingComma: defaultFormatterConfig.trailingComma,
123
- bracketSpacing: defaultFormatterConfig.bracketSpacing,
124
- arrowParens: defaultFormatterConfig.arrowParens
125
- };
126
- const defaultLinterConfig = {
127
- ignorePatterns: ["dist"],
128
- rules: {
129
- noUnusedVars: {
130
- level: "warn",
131
- argsIgnorePattern: "^_",
132
- varsIgnorePattern: "^_",
133
- caughtErrorsIgnorePattern: "^_"
134
- },
135
- noUnusedExpressions: {
136
- level: "warn",
137
- allowShortCircuit: true
138
- }
139
- }
140
- };
141
37
 
142
38
  const ALL_AI_PLATFORMS = ["agents", "claude"];
143
39
  const AI_PLATFORM_LABELS = {
@@ -148,12 +44,15 @@ const AI_PLATFORM_HINTS = {
148
44
  agents: "OpenAI, Cursor, Windsurf, etc.",
149
45
  claude: "Claude Code"
150
46
  };
151
- function generateAiFiles(files, params) {
152
- const { platforms, isMonorepo, configStrategy, ...rest } = params;
47
+ function renderAiFiles(files, params) {
48
+ const { platforms, isMonorepo, configStrategy, hasTypecheck, ...rest } = params;
153
49
  if (platforms.length === 0) return;
154
50
  const content = generateWorkspace({
155
51
  ...rest,
156
- isMonorepo: !!isMonorepo});
52
+ isMonorepo: !!isMonorepo,
53
+ configStrategy: configStrategy ?? "stealth",
54
+ hasTypecheck: hasTypecheck ?? false
55
+ });
157
56
  const pointer = "See [`AGENTS.md`](./Agents.md) for agent context.\n";
158
57
  const hasAgents = platforms.includes("agents");
159
58
  const hasClaude = platforms.includes("claude");
@@ -168,31 +67,115 @@ function generateAiFiles(files, params) {
168
67
  }
169
68
  }
170
69
  function generateWorkspace(ctx) {
171
- const { name, packageManager, linter, formatter, isMonorepo} = ctx;
70
+ const { packageManager, linter, formatter, hasTypecheck } = ctx;
71
+ const exampleFiles = "src/App.tsx src/core/systems/move-entity.ts";
72
+ const commands = getAfterEditingCommands(ctx, exampleFiles);
172
73
  const sections = [
173
- `# ${name}`,
74
+ "# Workspace Tools",
174
75
  "",
175
- `- **Type:** ${isMonorepo ? "pnpm monorepo" : "standalone project"}`,
176
76
  `- **Package Manager:** ${packageManager}`,
177
77
  `- **Linter:** ${linter}`,
178
78
  `- **Formatter:** ${formatter}`,
179
79
  "",
180
- "## Commands",
181
- "",
182
- `- \`${packageManager} test\` \u2014 run tests`,
183
- `- \`${packageManager} build\` \u2014 build`,
184
- `- \`${packageManager} lint\` and \`${packageManager} format\` \u2014 run before committing`
80
+ "## After Editing",
81
+ ""
185
82
  ];
186
- if (isMonorepo) {
83
+ if (hasTypecheck) {
187
84
  sections.push(
188
- "",
189
- "- Use `workspace:*` for internal dependencies",
190
- `- New packages: \`${packageManager} create krispya <name> --workspace\``
85
+ "\u2705 After editing files, check the types for errors and then format and lint only the files changed for the current task."
86
+ );
87
+ } else {
88
+ sections.push(
89
+ "\u2705 After editing files, format and lint only the files changed for the current task."
191
90
  );
192
91
  }
193
- sections.push("");
92
+ sections.push("", "```sh", "# Example");
93
+ if (hasTypecheck) {
94
+ sections.push(runScript(packageManager, "typecheck"));
95
+ }
96
+ sections.push(
97
+ "# Run format and lint for only files modified",
98
+ commands.format,
99
+ commands.lint,
100
+ "```",
101
+ "",
102
+ "\u274C Avoid unless explicitly approved:",
103
+ "",
104
+ "```sh",
105
+ runScript(packageManager, "format"),
106
+ runScript(packageManager, "lint"),
107
+ "```",
108
+ ""
109
+ );
194
110
  return sections.join("\n");
195
111
  }
112
+ function getAfterEditingCommands(ctx, files) {
113
+ return {
114
+ format: getFormatChangedFilesCommand(ctx, files),
115
+ lint: getLintChangedFilesCommand(ctx, files)
116
+ };
117
+ }
118
+ function getFormatChangedFilesCommand(ctx, files) {
119
+ const exec = getExecCommand(ctx.packageManager);
120
+ if (ctx.formatter === "prettier") {
121
+ const configPath = getPrettierConfigPath(ctx);
122
+ const ignorePath = getPrettierIgnorePath(ctx);
123
+ const configFlag2 = configPath == null ? "" : ` --config ${configPath}`;
124
+ const ignoreFlag = ignorePath == null ? "" : ` --ignore-path ${ignorePath}`;
125
+ return `${exec} prettier${configFlag2}${ignoreFlag} --write ${files}`;
126
+ }
127
+ if (ctx.formatter === "oxfmt") {
128
+ const configPath = getOxfmtConfigPath(ctx);
129
+ return `${exec} oxfmt -c ${configPath} --write ${files}`;
130
+ }
131
+ const configFlag = ctx.isMonorepo || ctx.configStrategy === "root" ? "" : " --config-path .config";
132
+ return `${exec} biome format${configFlag} --write ${files}`;
133
+ }
134
+ function getLintChangedFilesCommand(ctx, files) {
135
+ const exec = getExecCommand(ctx.packageManager);
136
+ if (ctx.linter === "oxlint") {
137
+ if (!ctx.isMonorepo) {
138
+ return runScript(ctx.packageManager, "lint", files);
139
+ }
140
+ return `${exec} oxlint ${files}`;
141
+ }
142
+ if (ctx.linter === "eslint") {
143
+ const configFlag2 = ctx.configStrategy === "stealth" ? " --config .config/eslint.config.js" : "";
144
+ return `${exec} eslint${configFlag2} ${files}`;
145
+ }
146
+ const configFlag = ctx.isMonorepo || ctx.configStrategy === "root" ? "" : " --config-path .config";
147
+ return `${exec} biome lint${configFlag} ${files}`;
148
+ }
149
+ function getPrettierConfigPath(ctx) {
150
+ if (ctx.isMonorepo) return ".config/prettier/base.json";
151
+ if (ctx.configStrategy === "stealth") return ".config/prettier.json";
152
+ return void 0;
153
+ }
154
+ function getPrettierIgnorePath(ctx) {
155
+ if (ctx.isMonorepo) return ".config/prettier/prettierignore";
156
+ if (ctx.configStrategy === "stealth") return ".config/prettierignore";
157
+ return void 0;
158
+ }
159
+ function getOxfmtConfigPath(ctx) {
160
+ if (ctx.isMonorepo) return ".config/oxfmt/base.json";
161
+ if (ctx.configStrategy === "stealth") return ".config/oxfmt.json";
162
+ return "oxfmt.json";
163
+ }
164
+ function runScript(packageManager, script, args) {
165
+ const suffix = args == null ? "" : ` ${args}`;
166
+ if (packageManager === "npm") {
167
+ return `npm run ${script}${args == null ? "" : ` --${suffix}`}`;
168
+ }
169
+ if (packageManager === "yarn") {
170
+ return `yarn ${script}${suffix}`;
171
+ }
172
+ return `${packageManager} ${script}${args == null ? "" : ` --${suffix}`}`;
173
+ }
174
+ function getExecCommand(packageManager) {
175
+ if (packageManager === "npm") return "npm exec --";
176
+ if (packageManager === "yarn") return "yarn exec";
177
+ return `${packageManager} exec`;
178
+ }
196
179
 
197
180
  function getLanguageFromTemplate(template) {
198
181
  return template.endsWith("-js") ? "javascript" : "typescript";
@@ -201,37 +184,51 @@ function getBaseTemplate(template) {
201
184
  return template.replace("-js", "");
202
185
  }
203
186
 
204
- async function getLatestNpmVersion(packageName, fallback) {
205
- try {
206
- const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
207
- const data = await response.json();
208
- return data.version;
209
- } catch {
210
- return fallback;
187
+ function unique(...array) {
188
+ const set = /* @__PURE__ */ new Set();
189
+ for (const arr of array) {
190
+ for (const item of arr) {
191
+ set.add(item);
192
+ }
211
193
  }
194
+ return Array.from(set);
212
195
  }
213
- async function getLatestPnpmVersion() {
214
- return getLatestNpmVersion("pnpm", "10.11.0");
215
- }
216
- async function getLatestYarnVersion() {
217
- return getLatestNpmVersion("yarn", "4.6.0");
218
- }
219
- async function getLatestNpmCliVersion() {
220
- return getLatestNpmVersion("npm", "11.0.0");
221
- }
222
- async function getLatestNodeVersion() {
223
- try {
224
- const response = await fetch("https://nodejs.org/dist/index.json");
225
- const data = await response.json();
226
- const latestVersion = data[0];
227
- if (latestVersion) {
228
- return latestVersion.version.replace(/^v/, "");
196
+
197
+ function merge(target, modification) {
198
+ const targetLabel = JSON.stringify(target);
199
+ const modificationLabel = JSON.stringify(modification);
200
+ if (modification == null) {
201
+ throw new Error(
202
+ `Cannot merge "${modificationLabel}" modification into target "${targetLabel}"`
203
+ );
204
+ }
205
+ if (target == null) {
206
+ return modification;
207
+ }
208
+ if (Array.isArray(target)) {
209
+ if (!Array.isArray(modification)) {
210
+ throw new Error(
211
+ `Cannot merge non-array modification "${modificationLabel}" into array target "${targetLabel}"`
212
+ );
229
213
  }
230
- return "25.0.0";
231
- } catch {
232
- return "25.0.0";
214
+ return [...target, ...modification];
215
+ }
216
+ if (typeof target === "object") {
217
+ if (typeof modification !== "object") {
218
+ throw new Error(
219
+ `Cannot merge non-object modification "${modificationLabel}" into object target "${targetLabel}"`
220
+ );
221
+ }
222
+ const result = { ...target };
223
+ for (const modificationKey in modification) {
224
+ result[modificationKey] = merge(target[modificationKey], modification[modificationKey]);
225
+ }
226
+ return result;
233
227
  }
228
+ console.warn(`target "${targetLabel}" is overwritten with modification "${modificationLabel}"`);
229
+ return modification;
234
230
  }
231
+
235
232
  function validateNameSegment(segment, label) {
236
233
  if (!segment.length) {
237
234
  return `${label} is required`;
@@ -275,103 +272,7 @@ function validatePackageName(name) {
275
272
  }
276
273
  return validateNameSegment(name, "Package name");
277
274
  }
278
- function parseWorkspaceYamlContent(content) {
279
- const directories = [];
280
- let inPackagesSection = false;
281
- for (const line of content.split("\n")) {
282
- const trimmed = line.trim();
283
- if (trimmed === "packages:") {
284
- inPackagesSection = true;
285
- continue;
286
- }
287
- if (inPackagesSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ") && !trimmed.startsWith("-")) {
288
- break;
289
- }
290
- if (inPackagesSection && trimmed.startsWith("-")) {
291
- const entry = trimmed.slice(1).trim().replace(/^["']|["']$/g, "").replace(/^\.\//, "").replace(/\/\*.*$/, "");
292
- if (entry && !entry.startsWith(".")) {
293
- directories.push(entry);
294
- }
295
- }
296
- }
297
- return directories;
298
- }
299
- async function pathExists(path) {
300
- try {
301
- await promises.access(path, fs.constants.F_OK);
302
- return true;
303
- } catch {
304
- return false;
305
- }
306
- }
307
- function detectLinterFromScript(script) {
308
- if (!script) return void 0;
309
- if (script.includes("oxlint")) return "oxlint";
310
- if (script.includes("eslint")) return "eslint";
311
- if (script.includes("biome check") || script.includes("biome lint")) return "biome";
312
- return void 0;
313
- }
314
- function detectFormatterFromScript(script) {
315
- if (!script) return void 0;
316
- if (script.includes("prettier")) return "prettier";
317
- if (script.includes("oxfmt")) return "oxfmt";
318
- if (script.includes("biome format")) return "biome";
319
- return void 0;
320
- }
321
- async function detectLinterFromConfig(root) {
322
- if (await pathExists(path.join(root, ".config/oxlint"))) return "oxlint";
323
- if (await pathExists(path.join(root, ".config/eslint"))) return "eslint";
324
- if (await pathExists(path.join(root, "biome.json"))) {
325
- try {
326
- const content = await promises.readFile(path.join(root, "biome.json"), "utf-8");
327
- const config = JSON.parse(content);
328
- if (config.linter?.enabled !== false) return "biome";
329
- } catch {
330
- return "biome";
331
- }
332
- }
333
- return void 0;
334
- }
335
- async function detectFormatterFromConfig(root) {
336
- if (await pathExists(path.join(root, ".config/prettier"))) return "prettier";
337
- if (await pathExists(path.join(root, ".config/oxfmt"))) return "oxfmt";
338
- if (await pathExists(path.join(root, "biome.json"))) {
339
- try {
340
- const content = await promises.readFile(path.join(root, "biome.json"), "utf-8");
341
- const config = JSON.parse(content);
342
- if (config.formatter?.enabled !== false) return "biome";
343
- } catch {
344
- return "biome";
345
- }
346
- }
347
- return void 0;
348
- }
349
- function detectLinterFromDeps(devDeps) {
350
- if (!devDeps) return void 0;
351
- if (devDeps["@biomejs/biome"]) return "biome";
352
- if (devDeps.eslint) return "eslint";
353
- if (devDeps.oxlint) return "oxlint";
354
- return void 0;
355
- }
356
- function detectFormatterFromDeps(devDeps) {
357
- if (!devDeps) return void 0;
358
- if (devDeps["@biomejs/biome"]) return "biome";
359
- if (devDeps.prettier) return "prettier";
360
- if (devDeps.oxfmt) return "oxfmt";
361
- return void 0;
362
- }
363
- async function detectTooling(root) {
364
- try {
365
- const pkgPath = path.join(root, "package.json");
366
- const content = await promises.readFile(pkgPath, "utf-8");
367
- const pkg = JSON.parse(content);
368
- const linter = detectLinterFromScript(pkg.scripts?.lint) ?? await detectLinterFromConfig(root) ?? detectLinterFromDeps(pkg.devDependencies);
369
- const formatter = detectFormatterFromScript(pkg.scripts?.format) ?? await detectFormatterFromConfig(root) ?? detectFormatterFromDeps(pkg.devDependencies);
370
- return { linter, formatter };
371
- } catch {
372
- return { linter: void 0, formatter: void 0 };
373
- }
374
- }
275
+
375
276
  function generateRandomName() {
376
277
  const adjectives = [
377
278
  "red",
@@ -448,6 +349,159 @@ function generateRandomName() {
448
349
  return `${randomAdjective}-${randomNoun}`;
449
350
  }
450
351
 
352
+ async function pathExists(path) {
353
+ try {
354
+ await promises.access(path, fs.constants.F_OK);
355
+ return true;
356
+ } catch {
357
+ return false;
358
+ }
359
+ }
360
+ function detectLinterFromScript(script) {
361
+ if (!script) return void 0;
362
+ if (script.includes("oxlint")) return "oxlint";
363
+ if (script.includes("eslint")) return "eslint";
364
+ if (script.includes("biome check") || script.includes("biome lint")) return "biome";
365
+ return void 0;
366
+ }
367
+ function detectFormatterFromScript(script) {
368
+ if (!script) return void 0;
369
+ if (script.includes("prettier")) return "prettier";
370
+ if (script.includes("oxfmt")) return "oxfmt";
371
+ if (script.includes("biome format")) return "biome";
372
+ return void 0;
373
+ }
374
+ async function detectLinterFromConfig(root) {
375
+ if (await pathExists(path.join(root, ".config/oxlint"))) return "oxlint";
376
+ if (await pathExists(path.join(root, ".config/eslint"))) return "eslint";
377
+ if (await pathExists(path.join(root, "biome.json"))) {
378
+ try {
379
+ const content = await promises.readFile(path.join(root, "biome.json"), "utf-8");
380
+ const config = JSON.parse(content);
381
+ if (config.linter?.enabled !== false) return "biome";
382
+ } catch {
383
+ return "biome";
384
+ }
385
+ }
386
+ return void 0;
387
+ }
388
+ async function detectFormatterFromConfig(root) {
389
+ if (await pathExists(path.join(root, ".config/prettier"))) return "prettier";
390
+ if (await pathExists(path.join(root, ".config/oxfmt"))) return "oxfmt";
391
+ if (await pathExists(path.join(root, "biome.json"))) {
392
+ try {
393
+ const content = await promises.readFile(path.join(root, "biome.json"), "utf-8");
394
+ const config = JSON.parse(content);
395
+ if (config.formatter?.enabled !== false) return "biome";
396
+ } catch {
397
+ return "biome";
398
+ }
399
+ }
400
+ return void 0;
401
+ }
402
+ function detectLinterFromDeps(devDeps) {
403
+ if (!devDeps) return void 0;
404
+ if (devDeps["@biomejs/biome"]) return "biome";
405
+ if (devDeps.eslint) return "eslint";
406
+ if (devDeps.oxlint) return "oxlint";
407
+ return void 0;
408
+ }
409
+ function detectFormatterFromDeps(devDeps) {
410
+ if (!devDeps) return void 0;
411
+ if (devDeps["@biomejs/biome"]) return "biome";
412
+ if (devDeps.prettier) return "prettier";
413
+ if (devDeps.oxfmt) return "oxfmt";
414
+ return void 0;
415
+ }
416
+ async function detectTooling(root) {
417
+ try {
418
+ const pkgPath = path.join(root, "package.json");
419
+ const content = await promises.readFile(pkgPath, "utf-8");
420
+ const pkg = JSON.parse(content);
421
+ const linter = detectLinterFromScript(pkg.scripts?.lint) ?? await detectLinterFromConfig(root) ?? detectLinterFromDeps(pkg.devDependencies);
422
+ const formatter = detectFormatterFromScript(pkg.scripts?.format) ?? await detectFormatterFromConfig(root) ?? detectFormatterFromDeps(pkg.devDependencies);
423
+ return { linter, formatter };
424
+ } catch {
425
+ return { linter: void 0, formatter: void 0 };
426
+ }
427
+ }
428
+
429
+ async function getLatestNpmVersion(packageName, fallback) {
430
+ try {
431
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
432
+ const data = await response.json();
433
+ return data.version;
434
+ } catch {
435
+ return fallback;
436
+ }
437
+ }
438
+ function compareNumericSemver(a, b) {
439
+ const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
440
+ const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
441
+ const maxLength = Math.max(aParts.length, bParts.length);
442
+ for (let index = 0; index < maxLength; index += 1) {
443
+ const difference = (aParts[index] ?? 0) - (bParts[index] ?? 0);
444
+ if (difference !== 0) {
445
+ return difference;
446
+ }
447
+ }
448
+ return 0;
449
+ }
450
+ async function getLatestNpmMajorVersion(packageName, majorVersion, fallback) {
451
+ try {
452
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`);
453
+ const data = await response.json();
454
+ const latestMatchingVersion = Object.keys(data.versions ?? {}).filter((version) => version.split(".")[0] === majorVersion).sort((a, b) => compareNumericSemver(b, a))[0];
455
+ return latestMatchingVersion ?? fallback;
456
+ } catch {
457
+ return fallback;
458
+ }
459
+ }
460
+ async function getLatestPnpmVersion() {
461
+ return getLatestNpmVersion("pnpm", "10.11.0");
462
+ }
463
+ async function getLatestYarnVersion() {
464
+ return getLatestNpmVersion("yarn", "4.6.0");
465
+ }
466
+ async function getLatestNpmCliVersion() {
467
+ return getLatestNpmVersion("npm", "11.0.0");
468
+ }
469
+ async function getLatestNodeVersion() {
470
+ try {
471
+ const response = await fetch("https://nodejs.org/dist/index.json");
472
+ const data = await response.json();
473
+ const latestVersion = data[0];
474
+ if (latestVersion) {
475
+ return latestVersion.version.replace(/^v/, "");
476
+ }
477
+ return "25.0.0";
478
+ } catch {
479
+ return "25.0.0";
480
+ }
481
+ }
482
+
483
+ function parseWorkspaceYamlContent(content) {
484
+ const directories = [];
485
+ let inPackagesSection = false;
486
+ for (const line of content.split("\n")) {
487
+ const trimmed = line.trim();
488
+ if (trimmed === "packages:") {
489
+ inPackagesSection = true;
490
+ continue;
491
+ }
492
+ if (inPackagesSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ") && !trimmed.startsWith("-")) {
493
+ break;
494
+ }
495
+ if (inPackagesSection && trimmed.startsWith("-")) {
496
+ const entry = trimmed.slice(1).trim().replace(/^["']|["']$/g, "").replace(/^\.\//, "").replace(/\/\*.*$/, "");
497
+ if (entry && !entry.startsWith(".")) {
498
+ directories.push(entry);
499
+ }
500
+ }
501
+ }
502
+ return directories;
503
+ }
504
+
451
505
  const PACKAGE_VERSION_DEFINITIONS = {
452
506
  "@biomejs/biome": { fallbackVersion: "2.0.0" },
453
507
  "@react-three/drei": { fallbackVersion: "10.0.0" },
@@ -473,11 +527,13 @@ const PACKAGE_VERSION_DEFINITIONS = {
473
527
  leva: { fallbackVersion: "0.10.0" },
474
528
  oxfmt: { fallbackVersion: "0.21.0" },
475
529
  oxlint: { fallbackVersion: "1.36.0" },
530
+ "oxlint-tsgolint": { fallbackVersion: "0.22.1" },
476
531
  prettier: { fallbackVersion: "3.4.2" },
477
532
  react: { fallbackVersion: "19.0.0" },
478
533
  "react-dom": { fallbackVersion: "19.0.0" },
479
534
  three: { fallbackVersion: "0.175.0", prefix: "~" },
480
535
  tsdown: { fallbackVersion: "0.12.0" },
536
+ typescript: { fallbackVersion: "5.9.3" },
481
537
  "typescript-eslint": { fallbackVersion: "8.18.0" },
482
538
  unbuild: { fallbackVersion: "3.5.0" },
483
539
  vite: { fallbackVersion: "6.3.4" },
@@ -569,11 +625,35 @@ async function resolvePackageManager(options) {
569
625
  }
570
626
  async function resolveEngine(options) {
571
627
  const engine = getEngineSpec(options.engine);
572
- if (engine.version == null && engine.name === "node") {
628
+ if ((engine.version == null || engine.version === "latest") && engine.name === "node") {
573
629
  engine.version = await getLatestNodeVersion();
574
630
  }
575
631
  return engine;
576
632
  }
633
+ function formatNodeTypesVersion(versions = {}, engine) {
634
+ const resolvedVersion = versions["@types/node"];
635
+ if (resolvedVersion != null) {
636
+ return `^${resolvedVersion}`;
637
+ }
638
+ const engineSpec = getEngineSpec(engine);
639
+ if (engineSpec.name === "node" && engineSpec.version) {
640
+ const majorVersion = engineSpec.version.split(".")[0];
641
+ return `^${majorVersion}.0.0`;
642
+ }
643
+ return "^22.0.0";
644
+ }
645
+ async function resolveNodeTypesVersion(engine, versions = {}) {
646
+ if (versions["@types/node"] != null) {
647
+ return versions["@types/node"];
648
+ }
649
+ const engineSpec = getEngineSpec(engine);
650
+ if (engineSpec.name !== "node") {
651
+ return void 0;
652
+ }
653
+ const nodeVersion = engineSpec.version ?? await getLatestNodeVersion();
654
+ const majorVersion = nodeVersion.split(".")[0];
655
+ return getLatestNpmMajorVersion("@types/node", majorVersion, `${majorVersion}.0.0`);
656
+ }
577
657
  async function resolvePackageVersions(packageNames, existingVersions = {}) {
578
658
  const versions = { ...existingVersions };
579
659
  const uniquePackageNames = [...new Set(packageNames)];
@@ -589,16 +669,33 @@ async function resolvePackageVersions(packageNames, existingVersions = {}) {
589
669
  return versions;
590
670
  }
591
671
  async function resolveProjectPackageVersions(options) {
592
- return resolvePackageVersions(collectProjectPackageNames(options), options.versions);
672
+ const packageNames = collectProjectPackageNames(options);
673
+ const versions = await resolvePackageVersions(
674
+ packageNames.filter((packageName) => packageName !== "@types/node"),
675
+ options.versions
676
+ );
677
+ const nodeTypesVersion = await resolveNodeTypesVersion(options.engine, versions);
678
+ if (nodeTypesVersion != null) {
679
+ versions["@types/node"] = nodeTypesVersion;
680
+ }
681
+ return versions;
593
682
  }
594
683
  async function resolveMonorepoRootPackageVersions(params) {
595
684
  const packageNames = /* @__PURE__ */ new Set();
596
685
  const explicitVersions = new Set(Object.keys(params.versions ?? {}));
597
686
  addPackageName(packageNames, explicitVersions, getLinterPackage(params.linter));
687
+ if (params.linter === "oxlint") {
688
+ addPackageName(packageNames, explicitVersions, "oxlint-tsgolint");
689
+ }
598
690
  if (params.formatter !== "biome" || params.linter !== "biome") {
599
691
  addPackageName(packageNames, explicitVersions, getFormatterPackage(params.formatter));
600
692
  }
601
- return resolvePackageVersions(packageNames, params.versions);
693
+ const versions = await resolvePackageVersions(packageNames, params.versions);
694
+ const nodeTypesVersion = await resolveNodeTypesVersion(params.engine, versions);
695
+ if (nodeTypesVersion != null) {
696
+ versions["@types/node"] = nodeTypesVersion;
697
+ }
698
+ return versions;
602
699
  }
603
700
  function collectProjectPackageNames(options) {
604
701
  const packageNames = /* @__PURE__ */ new Set();
@@ -616,6 +713,13 @@ function collectProjectPackageNames(options) {
616
713
  const formatter = options.formatter ?? "prettier";
617
714
  const bundler = options.libraryBundler ?? "unbuild";
618
715
  const packageManager = getPackageManagerName(options.packageManager);
716
+ const engine = getEngineSpec(options.engine);
717
+ if (getEngineName(engine) === "node") {
718
+ addPackageName(packageNames, explicitVersions, "@types/node");
719
+ }
720
+ if (isTypescript) {
721
+ addPackageName(packageNames, explicitVersions, "typescript");
722
+ }
619
723
  if (!isLibrary) {
620
724
  addPackageName(packageNames, explicitVersions, "vite");
621
725
  }
@@ -692,6 +796,9 @@ function collectProjectPackageNames(options) {
692
796
  } else if (linter === "oxlint") {
693
797
  if (!inWorkspace) {
694
798
  addPackageName(packageNames, explicitVersions, "oxlint");
799
+ if (isTypescript) {
800
+ addPackageName(packageNames, explicitVersions, "oxlint-tsgolint");
801
+ }
695
802
  }
696
803
  } else if (linter === "biome") {
697
804
  addPackageName(packageNames, explicitVersions, "@biomejs/biome");
@@ -726,7 +833,7 @@ function isEnabledOption(option) {
726
833
  return option != null && option !== false;
727
834
  }
728
835
 
729
- function generateTypescriptConfig(baseTemplateOrParams) {
836
+ function renderTypescriptConfig(baseTemplateOrParams) {
730
837
  const params = typeof baseTemplateOrParams === "string" ? { baseTemplate: baseTemplateOrParams } : baseTemplateOrParams;
731
838
  const {
732
839
  baseTemplate,
@@ -739,9 +846,9 @@ function generateTypescriptConfig(baseTemplateOrParams) {
739
846
  const isR3f = baseTemplate === "r3f";
740
847
  const files = {};
741
848
  const devDependencies = {};
742
- if (getEngineName(engine) === "node" && getEngineSpec(engine).version) {
743
- const majorVersion = getEngineSpec(engine).version.split(".")[0];
744
- devDependencies["@types/node"] = `^${majorVersion}.0.0`;
849
+ assignResolvedPackageVersion(devDependencies, versions, "typescript");
850
+ if (getEngineName(engine) === "node") {
851
+ devDependencies["@types/node"] = formatNodeTypesVersion(versions, engine);
745
852
  } else {
746
853
  devDependencies["@types/node"] = "^22.0.0";
747
854
  }
@@ -796,6 +903,7 @@ function generateTypescriptConfig(baseTemplateOrParams) {
796
903
  composite: true,
797
904
  rewriteRelativeImportExtensions: true,
798
905
  erasableSyntaxOnly: true,
906
+ noEmit: true,
799
907
  ...isReact || isR3f ? { jsx: "react-jsx" } : {}
800
908
  },
801
909
  include: ["../src", "../tests"]
@@ -817,7 +925,8 @@ function generateTypescriptConfig(baseTemplateOrParams) {
817
925
  skipLibCheck: true,
818
926
  composite: true,
819
927
  rewriteRelativeImportExtensions: true,
820
- erasableSyntaxOnly: true
928
+ erasableSyntaxOnly: true,
929
+ noEmit: true
821
930
  },
822
931
  include: ["../*.config.ts", "./*.ts"]
823
932
  };
@@ -849,6 +958,7 @@ function generateTypescriptConfig(baseTemplateOrParams) {
849
958
  composite: true,
850
959
  rewriteRelativeImportExtensions: true,
851
960
  erasableSyntaxOnly: true,
961
+ noEmit: true,
852
962
  ...isReact || isR3f ? { jsx: "react-jsx" } : {}
853
963
  },
854
964
  include: ["src", "tests"]
@@ -870,7 +980,8 @@ function generateTypescriptConfig(baseTemplateOrParams) {
870
980
  skipLibCheck: true,
871
981
  composite: true,
872
982
  rewriteRelativeImportExtensions: true,
873
- erasableSyntaxOnly: true
983
+ erasableSyntaxOnly: true,
984
+ noEmit: true
874
985
  },
875
986
  include: ["*.config.ts"]
876
987
  };
@@ -882,8 +993,108 @@ function generateTypescriptConfig(baseTemplateOrParams) {
882
993
  return { files, devDependencies };
883
994
  }
884
995
 
996
+ const packageJsonScripts = {
997
+ appBase: {
998
+ dev: "vite",
999
+ build: "vite build"
1000
+ },
1001
+ typescript: {
1002
+ typecheck: "tsc --build --noEmit",
1003
+ "typecheck:watch": "tsc --build --watch"
1004
+ },
1005
+ test: {
1006
+ vitest: {
1007
+ test: "vitest"
1008
+ }
1009
+ },
1010
+ build: {
1011
+ unbuild(configPath) {
1012
+ return {
1013
+ build: configPath == null ? "unbuild" : `unbuild --config ${configPath}`
1014
+ };
1015
+ },
1016
+ tsdown: {
1017
+ build: "tsdown"
1018
+ }
1019
+ },
1020
+ lint: {
1021
+ oxlint(configPath) {
1022
+ return {
1023
+ lint: configPath == null ? "oxlint" : `oxlint -c ${configPath}`
1024
+ };
1025
+ },
1026
+ eslint(configPath) {
1027
+ return {
1028
+ lint: configPath == null ? "eslint ." : `eslint --config ${configPath} .`
1029
+ };
1030
+ },
1031
+ biome(configPath) {
1032
+ return {
1033
+ lint: configPath == null ? "biome lint ." : `biome lint --config-path ${configPath} .`
1034
+ };
1035
+ }
1036
+ },
1037
+ format: {
1038
+ prettier(configPath, ignorePath) {
1039
+ const configFlag = configPath == null ? "" : ` --config ${configPath}`;
1040
+ const ignoreFlag = ignorePath == null ? "" : ` --ignore-path ${ignorePath}`;
1041
+ return {
1042
+ format: `prettier${configFlag}${ignoreFlag} --write .`
1043
+ };
1044
+ },
1045
+ oxfmt(configPath) {
1046
+ return {
1047
+ format: `oxfmt -c ${configPath} --write .`
1048
+ };
1049
+ },
1050
+ biome(configPath) {
1051
+ return {
1052
+ format: configPath == null ? "biome format --write ." : `biome format --config-path ${configPath} --write .`
1053
+ };
1054
+ }
1055
+ },
1056
+ release(packageManagerName) {
1057
+ return {
1058
+ release: `${packageManagerName} run build && ${packageManagerName} publish`
1059
+ };
1060
+ },
1061
+ monorepoRoot(linter, formatter) {
1062
+ return mergePackageJsonScripts(
1063
+ {
1064
+ dev: "pnpm --filter './apps/*' run dev",
1065
+ build: "pnpm --filter './packages/*' run build && pnpm --filter './apps/*' run build",
1066
+ test: "pnpm -r run test"
1067
+ },
1068
+ linter === "oxlint" ? {
1069
+ lint: "oxlint ."
1070
+ } : linter === "biome" ? {
1071
+ lint: "biome check ."
1072
+ } : {
1073
+ lint: "eslint ."
1074
+ },
1075
+ formatter === "oxfmt" ? {
1076
+ format: "oxfmt -c .config/oxfmt/base.json ."
1077
+ } : formatter === "biome" ? {
1078
+ format: "biome format . --write"
1079
+ } : {
1080
+ format: "prettier --config .config/prettier/base.json --ignore-path .config/prettier/prettierignore --write ."
1081
+ }
1082
+ );
1083
+ }
1084
+ };
1085
+ function mergePackageJsonScripts(...scriptSets) {
1086
+ return Object.assign({}, ...scriptSets.filter((scriptSet) => scriptSet != null));
1087
+ }
1088
+ function resolveDefaultPackageJsonScripts(params) {
1089
+ return mergePackageJsonScripts(
1090
+ params.isLibrary ? void 0 : packageJsonScripts.appBase,
1091
+ params.language === "typescript" ? packageJsonScripts.typescript : void 0,
1092
+ params.isLibrary ? packageJsonScripts.release(params.packageManagerName) : void 0
1093
+ );
1094
+ }
1095
+
885
1096
  const DEFAULT_LIBRARY_VERSION = "0.1.0";
886
- function generatePackageJson(params) {
1097
+ function renderPackageJson(params) {
887
1098
  const {
888
1099
  name,
889
1100
  language,
@@ -891,13 +1102,20 @@ function generatePackageJson(params) {
891
1102
  dependencies,
892
1103
  devDependencies,
893
1104
  peerDependencies,
894
- scripts,
895
1105
  options,
896
1106
  workspaceDependencies
897
1107
  } = params;
898
1108
  const files = {};
899
1109
  const packageManager = getPackageManagerSpec(options.packageManager);
900
1110
  const isPnpm = packageManager.name === "pnpm";
1111
+ const resolvedScripts = mergePackageJsonScripts(
1112
+ resolveDefaultPackageJsonScripts({
1113
+ language,
1114
+ isLibrary,
1115
+ packageManagerName: packageManager.name
1116
+ }),
1117
+ params.scripts
1118
+ );
901
1119
  const packageJson = {
902
1120
  name,
903
1121
  description: "Built with \u{1F339} create-krispya",
@@ -929,10 +1147,12 @@ function generatePackageJson(params) {
929
1147
  const allDevDependencies = { ...devDependencies };
930
1148
  const engine = getEngineSpec(options.engine);
931
1149
  if (getEngineName(engine) === "node" && engine.version) {
932
- const majorVersion = engine.version.split(".")[0];
933
- allDevDependencies["@types/node"] ??= `^${majorVersion}.0.0`;
1150
+ allDevDependencies["@types/node"] ??= formatNodeTypesVersion(
1151
+ options.versions,
1152
+ options.engine
1153
+ );
934
1154
  }
935
- packageJson.scripts = scripts;
1155
+ packageJson.scripts = resolvedScripts;
936
1156
  packageJson.dependencies = sortKeys(allDependencies);
937
1157
  if (Object.keys(allDevDependencies).length > 0) {
938
1158
  packageJson.devDependencies = sortKeys(allDevDependencies);
@@ -975,7 +1195,7 @@ function generatePackageJson(params) {
975
1195
  return { files };
976
1196
  }
977
1197
 
978
- function generateReadme(params) {
1198
+ function renderReadme(params) {
979
1199
  const { name, baseTemplate, isLibrary, libraryBundler, packageManager, codeSnippets } = params;
980
1200
  const isVanilla = baseTemplate === "vanilla";
981
1201
  const isReact = baseTemplate === "react";
@@ -1068,7 +1288,63 @@ function generateReadme(params) {
1068
1288
  return { type: "text", content };
1069
1289
  }
1070
1290
 
1071
- function generateSourceFiles(params) {
1291
+ const htmlContent = `<!DOCTYPE html>
1292
+ <html lang="en">
1293
+ <head>
1294
+ <meta charset="UTF-8">
1295
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1296
+ <title>$title</title>
1297
+ </head>
1298
+ <body style="margin: 0; overscroll-behavior: none; user-select: none; touch-action: none;">
1299
+ <script type="module" src="$indexPath"><\/script>
1300
+ <div style="width: 100dvw; height: 100dvh; overflow: hidden;" id="root"></div>
1301
+ </body>
1302
+ </html>`;
1303
+ const viteHtmlContent = `<!DOCTYPE html>
1304
+ <html lang="en">
1305
+ <head>
1306
+ <meta charset="UTF-8">
1307
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1308
+ <title>$title</title>
1309
+ </head>
1310
+ <body>
1311
+ <div id="app"></div>
1312
+ <script type="module" src="$indexPath"><\/script>
1313
+ </body>
1314
+ </html>`;
1315
+ const indexContent = `import { StrictMode } from 'react'
1316
+ import { createRoot } from 'react-dom/client'
1317
+ import { App } from './app.js'
1318
+
1319
+ createRoot(document.getElementById('root')!).render(
1320
+ <StrictMode>
1321
+ <App />
1322
+ </StrictMode>,
1323
+ )`;
1324
+ const viteIndexContent = `import './style.css'
1325
+
1326
+ document.querySelector('#app')!.innerHTML = \`
1327
+ <h1>Hello Vite!</h1>
1328
+ <p>Edit src/main.ts and save to see HMR in action.</p>
1329
+ \``;
1330
+ const viteStyleContent = `body {
1331
+ font-family: system-ui, -apple-system, sans-serif;
1332
+ margin: 0;
1333
+ padding: 2rem;
1334
+ min-height: 100vh;
1335
+ background: #1a1a1a;
1336
+ color: #fff;
1337
+ }
1338
+
1339
+ h1 {
1340
+ color: #646cff;
1341
+ }
1342
+
1343
+ a {
1344
+ color: #646cff;
1345
+ }`;
1346
+
1347
+ function renderSourceFiles(params) {
1072
1348
  const { name, baseTemplate, language, isLibrary, codeSnippets, replacements } = params;
1073
1349
  const files = {};
1074
1350
  const ext = language === "typescript" ? "ts" : "js";
@@ -1108,19 +1384,13 @@ function generateSourceFiles(params) {
1108
1384
  }
1109
1385
  files[`src/index.${libExt}`] = { type: "text", content: libContent };
1110
1386
  } else if (isVanilla) {
1111
- files[`src/main.${ext}`] = { type: "text", content: ViteIndexContent };
1112
- files["src/style.css"] = { type: "text", content: ViteStyleContent };
1113
- const indexHtml = ViteHtmlContent.replace("$indexPath", `./src/main.${ext}`).replace(
1114
- "$title",
1115
- name
1116
- );
1387
+ files[`src/main.${ext}`] = { type: "text", content: viteIndexContent };
1388
+ files["src/style.css"] = { type: "text", content: viteStyleContent };
1389
+ const indexHtml = viteHtmlContent.replace("$indexPath", `./src/main.${ext}`).replace("$title", name);
1117
1390
  files["index.html"] = { type: "text", content: indexHtml };
1118
1391
  } else {
1119
- files[`src/index.tsx`] = { type: "text", content: IndexContent };
1120
- const indexHtml = HtmlContent.replace(
1121
- "$indexPath",
1122
- language === "javascript" ? "./src/index.jsx" : "./src/index.tsx"
1123
- ).replace("$title", name);
1392
+ files[`src/index.tsx`] = { type: "text", content: indexContent };
1393
+ const indexHtml = htmlContent.replace("$indexPath", language === "javascript" ? "./src/index.jsx" : "./src/index.tsx").replace("$title", name);
1124
1394
  files["index.html"] = { type: "text", content: indexHtml };
1125
1395
  codeSnippets["dom-end"]?.reverse();
1126
1396
  codeSnippets["global-end"]?.reverse();
@@ -1167,7 +1437,7 @@ function generateSourceFiles(params) {
1167
1437
  return files;
1168
1438
  }
1169
1439
 
1170
- function generateTestFiles(params) {
1440
+ function renderTestFiles(params) {
1171
1441
  const { baseTemplate, language, isLibrary } = params;
1172
1442
  const files = {};
1173
1443
  const ext = language === "typescript" ? "ts" : "js";
@@ -1274,21 +1544,184 @@ const COMMON_GITIGNORE_LINES = [
1274
1544
  "*.tsbuildinfo",
1275
1545
  ".env",
1276
1546
  ".env.*",
1277
- "!.env.example"
1547
+ "!.env.example",
1548
+ ".pnpm-store"
1278
1549
  ];
1279
- function generateGitignore(variant) {
1550
+ function renderGitignore(variant) {
1280
1551
  const lines = variant === "workspace-root" ? [...COMMON_GITIGNORE_LINES, ".DS_Store"] : COMMON_GITIGNORE_LINES;
1281
1552
  return {
1282
1553
  type: "text",
1283
1554
  content: lines.join("\n")
1284
1555
  };
1285
1556
  }
1286
-
1287
- function generateVscodeFiles$1(params) {
1288
- const { codeSnippets, vscodeSettings } = params;
1557
+
1558
+ const defaultFormatterMetaConfig = {
1559
+ printWidth: 102,
1560
+ tabWidth: 2,
1561
+ useTabs: false,
1562
+ semi: true,
1563
+ singleQuote: true,
1564
+ trailingComma: "es5",
1565
+ bracketSpacing: true,
1566
+ arrowParens: "always",
1567
+ ignorePatterns: [
1568
+ "package-lock.json",
1569
+ "npm-shrinkwrap.json",
1570
+ "pnpm-lock.yaml",
1571
+ "pnpm-lock.json",
1572
+ "yarn.lock",
1573
+ "bun.lock",
1574
+ "bun.lockb"
1575
+ ]
1576
+ };
1577
+
1578
+ function renderEditorConfig(config = defaultFormatterMetaConfig) {
1579
+ const indentStyle = config.useTabs ? "tab" : "space";
1580
+ const indentSize = config.useTabs ? "tab" : String(config.tabWidth);
1581
+ return {
1582
+ type: "text",
1583
+ content: [
1584
+ "root = true",
1585
+ "",
1586
+ "[*]",
1587
+ "charset = utf-8",
1588
+ "end_of_line = lf",
1589
+ "insert_final_newline = true",
1590
+ `indent_style = ${indentStyle}`,
1591
+ `indent_size = ${indentSize}`,
1592
+ `tab_width = ${config.tabWidth}`,
1593
+ `max_line_length = ${config.printWidth}`
1594
+ ].join("\n")
1595
+ };
1596
+ }
1597
+ function renderVscodeEditorSettings(config = defaultFormatterMetaConfig) {
1598
+ return {
1599
+ "editor.detectIndentation": false,
1600
+ "editor.insertSpaces": !config.useTabs,
1601
+ "editor.tabSize": config.tabWidth,
1602
+ "files.eol": "\n",
1603
+ "files.insertFinalNewline": true
1604
+ };
1605
+ }
1606
+
1607
+ const DEFAULT_VSCODE_SETTINGS = {
1608
+ ...renderVscodeEditorSettings(),
1609
+ "explorer.fileNesting.enabled": true,
1610
+ "explorer.fileNesting.expand": false,
1611
+ "explorer.fileNesting.patterns": {
1612
+ ".gitignore": ".gitattributes",
1613
+ "AGENTS.md": "CLAUDE.md"
1614
+ }
1615
+ };
1616
+ const OXFMT_LANGUAGE_SETTINGS = {
1617
+ "[json]": {
1618
+ "editor.defaultFormatter": "vscode.json-language-features"
1619
+ },
1620
+ "[jsonc]": {
1621
+ "editor.defaultFormatter": "vscode.json-language-features"
1622
+ },
1623
+ "[markdown]": {
1624
+ "editor.defaultFormatter": "vscode.markdown-language-features"
1625
+ },
1626
+ "[yaml]": {
1627
+ "editor.defaultFormatter": "redhat.vscode-yaml"
1628
+ }
1629
+ };
1630
+ function resolvePackageJsonNestedFiles(packageManager) {
1631
+ if (packageManager === "pnpm") {
1632
+ return ["pnpm-lock.yaml", "pnpm-workspace.yaml"];
1633
+ }
1634
+ if (packageManager === "npm") {
1635
+ return ["package-lock.json", "npm-shrinkwrap.json"];
1636
+ }
1637
+ if (packageManager === "yarn") {
1638
+ return ["yarn.lock"];
1639
+ }
1640
+ return [];
1641
+ }
1642
+ function resolveVscodeRecommendations(linter, formatter) {
1643
+ const recommendations = [];
1644
+ if (linter === "oxlint" || formatter === "oxfmt") {
1645
+ recommendations.push("oxc.oxc-vscode");
1646
+ }
1647
+ if (linter === "eslint") {
1648
+ recommendations.push("dbaeumer.vscode-eslint");
1649
+ }
1650
+ if (linter === "biome" || formatter === "biome") {
1651
+ recommendations.push("biomejs.biome");
1652
+ }
1653
+ if (formatter === "prettier") {
1654
+ recommendations.push("esbenp.prettier-vscode");
1655
+ }
1656
+ return recommendations;
1657
+ }
1658
+ function resolveVscodeSettings(params) {
1659
+ const { linter, formatter, configStrategy, isMonorepo, packageManager } = params;
1660
+ const settings = { ...DEFAULT_VSCODE_SETTINGS };
1661
+ const isStealth = !isMonorepo && (configStrategy ?? "stealth") === "stealth";
1662
+ const packageJsonNestedFiles = resolvePackageJsonNestedFiles(packageManager);
1663
+ if (packageJsonNestedFiles.length > 0) {
1664
+ settings["explorer.fileNesting.patterns"] = {
1665
+ ...settings["explorer.fileNesting.patterns"],
1666
+ "package.json": packageJsonNestedFiles.join(", ")
1667
+ };
1668
+ }
1669
+ if (linter === "eslint") {
1670
+ settings["eslint.enable"] = true;
1671
+ settings["oxc.enable"] = false;
1672
+ settings["biome.enabled"] = false;
1673
+ if (isStealth) {
1674
+ settings["eslint.options"] = {
1675
+ overrideConfigFile: ".config/eslint.config.js"
1676
+ };
1677
+ }
1678
+ } else if (linter === "oxlint") {
1679
+ settings["oxc.enable"] = true;
1680
+ settings["eslint.enable"] = false;
1681
+ settings["biome.enabled"] = false;
1682
+ if (isStealth) {
1683
+ settings["oxc.configPath"] = ".config/oxlint.json";
1684
+ }
1685
+ } else if (linter === "biome") {
1686
+ settings["biome.enabled"] = true;
1687
+ settings["eslint.enable"] = false;
1688
+ settings["oxc.enable"] = false;
1689
+ if (isStealth) {
1690
+ settings["biome.linter.configPath"] = ".config/biome.json";
1691
+ }
1692
+ }
1693
+ if (formatter === "prettier") {
1694
+ settings["editor.defaultFormatter"] = "esbenp.prettier-vscode";
1695
+ if (isStealth) {
1696
+ settings["prettier.configPath"] = ".config/prettier.json";
1697
+ settings["prettier.ignorePath"] = ".config/prettierignore";
1698
+ }
1699
+ } else if (formatter === "oxfmt") {
1700
+ settings["editor.defaultFormatter"] = "oxc.oxc-vscode";
1701
+ Object.assign(settings, OXFMT_LANGUAGE_SETTINGS);
1702
+ if (isStealth) {
1703
+ settings["oxc.fmt.configPath"] = ".config/oxfmt.json";
1704
+ }
1705
+ } else if (formatter === "biome") {
1706
+ settings["biome.enabled"] = true;
1707
+ settings["eslint.enable"] = false;
1708
+ settings["oxc.enable"] = false;
1709
+ settings["editor.defaultFormatter"] = "biomejs.biome";
1710
+ if (isStealth) {
1711
+ settings["biome.linter.configPath"] = ".config/biome.json";
1712
+ }
1713
+ }
1714
+ return settings;
1715
+ }
1716
+ function renderVscodeFiles$1(params) {
1717
+ const { codeSnippets = {}, vscodeSettings = {} } = params;
1289
1718
  const files = {};
1290
- if (codeSnippets["vscode-extension-suggestion"]?.length) {
1291
- const uniqueRecommendations = [...new Set(codeSnippets["vscode-extension-suggestion"])];
1719
+ const recommendations = [
1720
+ ...codeSnippets["vscode-extension-suggestion"] ?? [],
1721
+ ...resolveVscodeRecommendations(params.linter, params.formatter)
1722
+ ];
1723
+ if (recommendations.length > 0) {
1724
+ const uniqueRecommendations = [...new Set(recommendations)];
1292
1725
  files[".vscode/extensions.json"] = {
1293
1726
  type: "text",
1294
1727
  content: JSON.stringify(
@@ -1300,9 +1733,13 @@ function generateVscodeFiles$1(params) {
1300
1733
  )
1301
1734
  };
1302
1735
  }
1303
- if (Object.keys(vscodeSettings).length > 0) {
1736
+ const resolvedSettings = {
1737
+ ...resolveVscodeSettings(params),
1738
+ ...vscodeSettings
1739
+ };
1740
+ if (Object.keys(resolvedSettings).length > 0) {
1304
1741
  const sortedSettings = Object.fromEntries(
1305
- Object.entries(vscodeSettings).sort(([a], [b]) => a.localeCompare(b))
1742
+ Object.entries(resolvedSettings).sort(([a], [b]) => a.localeCompare(b))
1306
1743
  );
1307
1744
  files[".vscode/settings.json"] = {
1308
1745
  type: "text",
@@ -1346,7 +1783,7 @@ ${spaces}}`;
1346
1783
  }
1347
1784
  return String(value);
1348
1785
  }
1349
- function generateViteConfig(params) {
1786
+ function renderViteConfig(params) {
1350
1787
  const { viteConfig, codeSnippets } = params;
1351
1788
  const configBody = formatValue(viteConfig, 0);
1352
1789
  const viteConfigContent = [
@@ -1359,7 +1796,93 @@ function generateViteConfig(params) {
1359
1796
  return { type: "text", content: viteConfigContent };
1360
1797
  }
1361
1798
 
1362
- function generateTypescriptConfigPackage(files) {
1799
+ function toPrettierConfig(config = defaultFormatterMetaConfig) {
1800
+ return {
1801
+ $schema: "https://json.schemastore.org/prettierrc",
1802
+ printWidth: config.printWidth,
1803
+ tabWidth: config.tabWidth,
1804
+ useTabs: config.useTabs,
1805
+ semi: config.semi,
1806
+ singleQuote: config.singleQuote,
1807
+ trailingComma: config.trailingComma,
1808
+ bracketSpacing: config.bracketSpacing,
1809
+ arrowParens: config.arrowParens,
1810
+ overrides: [
1811
+ {
1812
+ files: ["*.md", "**/*.md"],
1813
+ options: { semi: false }
1814
+ },
1815
+ {
1816
+ files: ["*.yml", "*.yaml", "**/*.yml", "**/*.yaml"],
1817
+ options: { semi: false }
1818
+ }
1819
+ ]
1820
+ };
1821
+ }
1822
+ function toPrettierIgnoreContent(config = defaultFormatterMetaConfig) {
1823
+ return config.ignorePatterns.join("\n");
1824
+ }
1825
+ function toOxfmtConfig(config = defaultFormatterMetaConfig) {
1826
+ return {
1827
+ printWidth: config.printWidth,
1828
+ tabWidth: config.tabWidth,
1829
+ useTabs: config.useTabs,
1830
+ semi: config.semi,
1831
+ singleQuote: config.singleQuote,
1832
+ trailingComma: config.trailingComma,
1833
+ bracketSpacing: config.bracketSpacing,
1834
+ arrowParens: config.arrowParens,
1835
+ ignorePatterns: config.ignorePatterns
1836
+ };
1837
+ }
1838
+
1839
+ const defaultLinterMetaConfig = {
1840
+ ignorePatterns: ["dist"],
1841
+ rules: {
1842
+ noUnusedVars: {
1843
+ level: "warn",
1844
+ argsIgnorePattern: "^_",
1845
+ varsIgnorePattern: "^_",
1846
+ caughtErrorsIgnorePattern: "^_"
1847
+ },
1848
+ noUnusedExpressions: {
1849
+ level: "warn",
1850
+ allowShortCircuit: true
1851
+ }
1852
+ }
1853
+ };
1854
+
1855
+ function renderOxlintConfig(params) {
1856
+ const config = params.config ?? defaultLinterMetaConfig;
1857
+ const { rules } = config;
1858
+ const plugins = ["unicorn", "typescript", "oxc"];
1859
+ if (params.react === true) {
1860
+ plugins.push("react");
1861
+ }
1862
+ return {
1863
+ $schema: params.schemaPath,
1864
+ plugins,
1865
+ ...params.typescript === true ? { options: { typeAware: true } } : {},
1866
+ rules: {
1867
+ "no-unused-vars": [
1868
+ rules.noUnusedVars.level,
1869
+ {
1870
+ argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1871
+ varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1872
+ caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1873
+ }
1874
+ ],
1875
+ "no-useless-escape": "off",
1876
+ "no-unused-expressions": [
1877
+ rules.noUnusedExpressions.level,
1878
+ { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1879
+ ]
1880
+ },
1881
+ ignorePatterns: config.ignorePatterns
1882
+ };
1883
+ }
1884
+
1885
+ function renderTypescriptConfigPackage(files) {
1363
1886
  const basePath = ".config/typescript";
1364
1887
  files[`${basePath}/package.json`] = {
1365
1888
  type: "text",
@@ -1464,9 +1987,8 @@ In your package's \`tsconfig.json\`:
1464
1987
  )
1465
1988
  };
1466
1989
  }
1467
- function generateOxlintConfigPackage(files) {
1990
+ function renderOxlintConfigPackage(files) {
1468
1991
  const basePath = ".config/oxlint";
1469
- const { rules } = defaultLinterConfig;
1470
1992
  files[`${basePath}/package.json`] = {
1471
1993
  type: "text",
1472
1994
  content: JSON.stringify(
@@ -1503,26 +2025,10 @@ oxlint -c node_modules/@config/oxlint/base.json
1503
2025
  files[`${basePath}/base.json`] = {
1504
2026
  type: "text",
1505
2027
  content: JSON.stringify(
1506
- {
1507
- $schema: "./node_modules/oxlint/configuration_schema.json",
1508
- plugins: ["unicorn", "typescript", "oxc"],
1509
- rules: {
1510
- "no-unused-vars": [
1511
- rules.noUnusedVars.level,
1512
- {
1513
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1514
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1515
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1516
- }
1517
- ],
1518
- "no-useless-escape": "off",
1519
- "no-unused-expressions": [
1520
- rules.noUnusedExpressions.level,
1521
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1522
- ]
1523
- },
1524
- ignorePatterns: defaultLinterConfig.ignorePatterns
1525
- },
2028
+ renderOxlintConfig({
2029
+ schemaPath: "./node_modules/oxlint/configuration_schema.json",
2030
+ typescript: true
2031
+ }),
1526
2032
  null,
1527
2033
  2
1528
2034
  )
@@ -1530,32 +2036,17 @@ oxlint -c node_modules/@config/oxlint/base.json
1530
2036
  files[`${basePath}/react.json`] = {
1531
2037
  type: "text",
1532
2038
  content: JSON.stringify(
1533
- {
1534
- $schema: "./node_modules/oxlint/configuration_schema.json",
1535
- plugins: ["unicorn", "typescript", "oxc", "react"],
1536
- rules: {
1537
- "no-unused-vars": [
1538
- rules.noUnusedVars.level,
1539
- {
1540
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1541
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1542
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1543
- }
1544
- ],
1545
- "no-useless-escape": "off",
1546
- "no-unused-expressions": [
1547
- rules.noUnusedExpressions.level,
1548
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1549
- ]
1550
- },
1551
- ignorePatterns: defaultLinterConfig.ignorePatterns
1552
- },
2039
+ renderOxlintConfig({
2040
+ schemaPath: "./node_modules/oxlint/configuration_schema.json",
2041
+ react: true,
2042
+ typescript: true
2043
+ }),
1553
2044
  null,
1554
2045
  2
1555
2046
  )
1556
2047
  };
1557
2048
  }
1558
- function generateEslintConfigPackage(files) {
2049
+ function renderEslintConfigPackage(files) {
1559
2050
  const basePath = ".config/eslint";
1560
2051
  files[`${basePath}/package.json`] = {
1561
2052
  type: "text",
@@ -1662,7 +2153,7 @@ export default tseslint.config(
1662
2153
  `
1663
2154
  };
1664
2155
  }
1665
- function generatePrettierConfigPackage(files) {
2156
+ function renderPrettierConfigPackage(files) {
1666
2157
  const basePath = ".config/prettier";
1667
2158
  files[`${basePath}/package.json`] = {
1668
2159
  type: "text",
@@ -1675,7 +2166,7 @@ function generatePrettierConfigPackage(files) {
1675
2166
  exports: {
1676
2167
  ".": "./base.json"
1677
2168
  },
1678
- files: ["base.json"]
2169
+ files: ["base.json", "prettierignore"]
1679
2170
  },
1680
2171
  null,
1681
2172
  2
@@ -1710,10 +2201,14 @@ Or in \`.prettierrc.json\`:
1710
2201
  };
1711
2202
  files[`${basePath}/base.json`] = {
1712
2203
  type: "text",
1713
- content: JSON.stringify(defaultPrettierConfig, null, 2)
2204
+ content: JSON.stringify(toPrettierConfig(), null, 2)
2205
+ };
2206
+ files[`${basePath}/prettierignore`] = {
2207
+ type: "text",
2208
+ content: toPrettierIgnoreContent()
1714
2209
  };
1715
2210
  }
1716
- function generateOxfmtConfigPackage(files) {
2211
+ function renderOxfmtConfigPackage(files) {
1717
2212
  const basePath = ".config/oxfmt";
1718
2213
  files[`${basePath}/package.json`] = {
1719
2214
  type: "text",
@@ -1749,11 +2244,11 @@ oxfmt -c node_modules/@config/oxfmt/base.json --write .
1749
2244
  };
1750
2245
  files[`${basePath}/base.json`] = {
1751
2246
  type: "text",
1752
- content: JSON.stringify(defaultOxfmtConfig, null, 2)
2247
+ content: JSON.stringify(toOxfmtConfig(), null, 2)
1753
2248
  };
1754
2249
  }
1755
2250
 
1756
- function generateMonorepo(params) {
2251
+ function renderMonorepo(params) {
1757
2252
  const {
1758
2253
  name,
1759
2254
  linter,
@@ -1762,19 +2257,20 @@ function generateMonorepo(params) {
1762
2257
  pnpmManageVersions,
1763
2258
  engine,
1764
2259
  versions = {},
2260
+ ide = "vscode",
1765
2261
  aiPlatforms
1766
2262
  } = params;
1767
2263
  const files = {};
1768
2264
  const isPnpm = packageManager.name === "pnpm";
1769
2265
  const devDependencies = {};
1770
2266
  if (engine?.name === "node" && engine.version) {
1771
- const majorVersion = engine.version.split(".")[0];
1772
- devDependencies["@types/node"] = `^${majorVersion}.0.0`;
2267
+ devDependencies["@types/node"] = formatNodeTypesVersion(versions, engine);
1773
2268
  } else {
1774
2269
  devDependencies["@types/node"] = "^22.0.0";
1775
2270
  }
1776
2271
  if (linter === "oxlint") {
1777
2272
  assignResolvedPackageVersion(devDependencies, versions, "oxlint");
2273
+ assignResolvedPackageVersion(devDependencies, versions, "oxlint-tsgolint");
1778
2274
  } else if (linter === "eslint") {
1779
2275
  assignResolvedPackageVersion(devDependencies, versions, "eslint");
1780
2276
  } else if (linter === "biome") {
@@ -1790,13 +2286,7 @@ function generateMonorepo(params) {
1790
2286
  version: "0.0.0",
1791
2287
  private: true,
1792
2288
  type: "module",
1793
- scripts: {
1794
- dev: "pnpm --filter './apps/*' run dev",
1795
- build: "pnpm --filter './packages/*' run build && pnpm --filter './apps/*' run build",
1796
- test: "pnpm -r run test",
1797
- lint: linter === "oxlint" ? "oxlint ." : linter === "biome" ? "biome check ." : "eslint .",
1798
- format: formatter === "oxfmt" ? "oxfmt -c .config/oxfmt/base.json ." : formatter === "biome" ? "biome format . --write" : "prettier --config .config/prettier/base.json --write ."
1799
- },
2289
+ scripts: packageJsonScripts.monorepoRoot(linter, formatter),
1800
2290
  devDependencies
1801
2291
  };
1802
2292
  const engines = {};
@@ -1842,9 +2332,9 @@ function generateMonorepo(params) {
1842
2332
  2
1843
2333
  )
1844
2334
  };
1845
- generateTypescriptConfigPackage(files);
2335
+ renderTypescriptConfigPackage(files);
1846
2336
  if (linter === "oxlint") {
1847
- generateOxlintConfigPackage(files);
2337
+ renderOxlintConfigPackage(files);
1848
2338
  files["oxlint.json"] = {
1849
2339
  type: "text",
1850
2340
  content: JSON.stringify(
@@ -1857,7 +2347,7 @@ function generateMonorepo(params) {
1857
2347
  )
1858
2348
  };
1859
2349
  } else if (linter === "eslint") {
1860
- generateEslintConfigPackage(files);
2350
+ renderEslintConfigPackage(files);
1861
2351
  files["eslint.config.js"] = {
1862
2352
  type: "text",
1863
2353
  content: `import base from "@config/eslint/base";
@@ -1890,11 +2380,12 @@ export default [...base];
1890
2380
  };
1891
2381
  }
1892
2382
  if (formatter === "oxfmt") {
1893
- generateOxfmtConfigPackage(files);
2383
+ renderOxfmtConfigPackage(files);
1894
2384
  } else if (formatter === "prettier") {
1895
- generatePrettierConfigPackage(files);
2385
+ renderPrettierConfigPackage(files);
1896
2386
  }
1897
- files[".gitignore"] = generateGitignore("workspace-root");
2387
+ files[".editorconfig"] = renderEditorConfig();
2388
+ files[".gitignore"] = renderGitignore("workspace-root");
1898
2389
  files[".gitattributes"] = {
1899
2390
  type: "text",
1900
2391
  content: `* text=auto eol=lf
@@ -1902,7 +2393,9 @@ export default [...base];
1902
2393
  *.{bat,[bB][aA][tT]} text eol=crlf
1903
2394
  `
1904
2395
  };
1905
- generateVscodeFiles(files, linter, formatter);
2396
+ if (ide === "vscode") {
2397
+ renderVscodeFiles(files, linter, formatter, packageManager.name);
2398
+ }
1906
2399
  files["README.md"] = {
1907
2400
  type: "text",
1908
2401
  content: `# ${name}
@@ -1930,104 +2423,50 @@ To add a new package to this workspace, run create-krispya from this directory a
1930
2423
  `
1931
2424
  };
1932
2425
  if (aiPlatforms && aiPlatforms.length > 0) {
1933
- generateAiFiles(files, {
2426
+ renderAiFiles(files, {
1934
2427
  name,
1935
2428
  packageManager: packageManager.name,
1936
2429
  linter,
1937
2430
  formatter,
1938
2431
  isMonorepo: true,
2432
+ hasTypecheck: false,
1939
2433
  platforms: aiPlatforms
1940
2434
  });
1941
2435
  }
1942
2436
  return { files };
1943
2437
  }
1944
- function generateVscodeFiles(files, linter, formatter) {
1945
- const recommendations = [];
1946
- const settings = {};
1947
- if (linter === "oxlint") {
1948
- recommendations.push("oxc.oxc-vscode");
1949
- settings["oxc.enable"] = true;
1950
- settings["eslint.enable"] = false;
1951
- settings["biome.enabled"] = false;
1952
- } else if (linter === "eslint") {
1953
- recommendations.push("dbaeumer.vscode-eslint");
1954
- settings["eslint.enable"] = true;
1955
- settings["oxc.enable"] = false;
1956
- settings["biome.enabled"] = false;
1957
- } else if (linter === "biome") {
1958
- recommendations.push("biomejs.biome");
1959
- settings["biome.enabled"] = true;
1960
- settings["eslint.enable"] = false;
1961
- settings["oxc.enable"] = false;
1962
- }
1963
- if (formatter === "oxfmt") {
1964
- if (!recommendations.includes("oxc.oxc-vscode")) {
1965
- recommendations.push("oxc.oxc-vscode");
1966
- }
1967
- settings["editor.defaultFormatter"] = "oxc.oxc-vscode";
1968
- settings["[json]"] = {
1969
- "editor.defaultFormatter": "vscode.json-language-features"
1970
- };
1971
- settings["[jsonc]"] = {
1972
- "editor.defaultFormatter": "vscode.json-language-features"
1973
- };
1974
- } else if (formatter === "prettier") {
1975
- recommendations.push("esbenp.prettier-vscode");
1976
- settings["editor.defaultFormatter"] = "esbenp.prettier-vscode";
1977
- } else if (formatter === "biome") {
1978
- if (!recommendations.includes("biomejs.biome")) {
1979
- recommendations.push("biomejs.biome");
1980
- }
1981
- settings["editor.defaultFormatter"] = "biomejs.biome";
1982
- }
1983
- files[".vscode/extensions.json"] = {
1984
- type: "text",
1985
- content: JSON.stringify({ recommendations }, null, 2)
1986
- };
1987
- const codeSnippets = {};
1988
- if (recommendations.length > 0) {
1989
- codeSnippets["vscode-extension-suggestion"] = recommendations;
1990
- }
2438
+ function renderVscodeFiles(files, linter, formatter, packageManager = "pnpm") {
1991
2439
  Object.assign(
1992
2440
  files,
1993
- generateVscodeFiles$1({
1994
- codeSnippets,
1995
- vscodeSettings: settings
2441
+ renderVscodeFiles$1({
2442
+ linter,
2443
+ formatter,
2444
+ isMonorepo: true,
2445
+ packageManager
1996
2446
  })
1997
2447
  );
1998
2448
  }
1999
2449
 
2000
- const monorepo = {
2001
- __proto__: null,
2002
- generateEslintConfigPackage: generateEslintConfigPackage,
2003
- generateMonorepo: generateMonorepo,
2004
- generateOxfmtConfigPackage: generateOxfmtConfigPackage,
2005
- generateOxlintConfigPackage: generateOxlintConfigPackage,
2006
- generatePrettierConfigPackage: generatePrettierConfigPackage,
2007
- generateTypescriptConfigPackage: generateTypescriptConfigPackage,
2008
- generateVscodeFiles: generateVscodeFiles
2009
- };
2010
-
2011
2450
  function toBiomeLevel(level) {
2012
2451
  return level;
2013
2452
  }
2014
- function generateBiome(generator, options) {
2453
+ function planBiome(builder, options) {
2015
2454
  if (options == null || !options.linter && !options.formatter) {
2016
2455
  return;
2017
2456
  }
2018
- const version = generator.getVersion("@biomejs/biome");
2019
- generator.addDevDependency("@biomejs/biome");
2020
- const { rules } = defaultLinterConfig;
2457
+ const version = builder.getVersion("@biomejs/biome");
2458
+ builder.addDevDependency("@biomejs/biome");
2021
2459
  const biomeConfig = {
2022
2460
  $schema: `https://biomejs.dev/schemas/${version}/schema.json`
2023
2461
  };
2024
2462
  if (options.linter) {
2463
+ const linterConfig = options.linter.config;
2025
2464
  biomeConfig.linter = {
2026
2465
  enabled: true,
2027
2466
  rules: {
2028
2467
  recommended: true,
2029
2468
  correctness: {
2030
- noUnusedVariables: toBiomeLevel(rules.noUnusedVars.level)
2469
+ noUnusedVariables: toBiomeLevel(linterConfig.rules.noUnusedVars.level)
2031
2470
  }
2032
2471
  }
2033
2472
  };
@@ -2037,19 +2476,20 @@ function generateBiome(generator, options) {
2037
2476
  };
2038
2477
  }
2039
2478
  if (options.formatter) {
2479
+ const formatterConfig = options.formatter.config;
2040
2480
  biomeConfig.formatter = {
2041
2481
  enabled: true,
2042
- lineWidth: defaultFormatterConfig.printWidth,
2043
- indentWidth: defaultFormatterConfig.tabWidth,
2044
- indentStyle: "space"
2482
+ lineWidth: formatterConfig.printWidth,
2483
+ indentWidth: formatterConfig.tabWidth,
2484
+ indentStyle: formatterConfig.useTabs ? "tab" : "space"
2045
2485
  };
2046
2486
  biomeConfig.javascript = {
2047
2487
  formatter: {
2048
- semicolons: "always" ,
2049
- quoteStyle: "single" ,
2050
- trailingCommas: defaultFormatterConfig.trailingComma,
2051
- bracketSpacing: defaultFormatterConfig.bracketSpacing,
2052
- arrowParentheses: "always"
2488
+ semicolons: formatterConfig.semi ? "always" : "asNeeded",
2489
+ quoteStyle: formatterConfig.singleQuote ? "single" : "double",
2490
+ trailingCommas: formatterConfig.trailingComma,
2491
+ bracketSpacing: formatterConfig.bracketSpacing,
2492
+ arrowParentheses: formatterConfig.arrowParens === "always" ? "always" : "asNeeded"
2053
2493
  }
2054
2494
  };
2055
2495
  biomeConfig.json = {
@@ -2062,53 +2502,48 @@ function generateBiome(generator, options) {
2062
2502
  enabled: false
2063
2503
  };
2064
2504
  }
2065
- const isStealth = generator.isStealthConfig();
2505
+ const isStealth = builder.isStealthConfig();
2066
2506
  if (isStealth) {
2067
- generator.addFile(".config/biome.json", {
2507
+ builder.addFile(".config/biome.json", {
2068
2508
  type: "text",
2069
2509
  content: JSON.stringify(biomeConfig, null, 2)
2070
2510
  });
2071
2511
  if (options.linter) {
2072
- generator.addScript("lint", "biome lint --config-path .config .");
2512
+ builder.addScripts(packageJsonScripts.lint.biome(".config"));
2073
2513
  }
2074
2514
  if (options.formatter) {
2075
- generator.addScript("format", "biome format --config-path .config --write .");
2515
+ builder.addScripts(packageJsonScripts.format.biome(".config"));
2076
2516
  }
2077
- generator.addVscodeSetting("biome.linter.configPath", ".config/biome.json");
2078
2517
  } else {
2079
- generator.addFile("biome.json", {
2518
+ builder.addFile("biome.json", {
2080
2519
  type: "text",
2081
2520
  content: JSON.stringify(biomeConfig, null, 2)
2082
2521
  });
2083
2522
  if (options.linter) {
2084
- generator.addScript("lint", "biome lint .");
2523
+ builder.addScripts(packageJsonScripts.lint.biome());
2085
2524
  }
2086
2525
  if (options.formatter) {
2087
- generator.addScript("format", "biome format --write .");
2526
+ builder.addScripts(packageJsonScripts.format.biome());
2088
2527
  }
2089
2528
  }
2090
2529
  const roles = [];
2091
2530
  if (options.linter) roles.push("linter");
2092
2531
  if (options.formatter) roles.push("formatter");
2093
- generator.inject(
2532
+ builder.inject(
2094
2533
  "readme-tools",
2095
2534
  `[Biome](https://biomejs.dev/) - Fast ${roles.join(" and ")} for JavaScript and TypeScript`
2096
2535
  );
2097
- generator.inject("vscode-extension-suggestion", "biomejs.biome");
2098
- generator.addVscodeSetting("biome.enabled", true);
2099
- if (options.formatter) {
2100
- generator.addVscodeSetting("editor.defaultFormatter", "biomejs.biome");
2101
- }
2536
+ builder.inject("vscode-extension-suggestion", "biomejs.biome");
2102
2537
  }
2103
2538
 
2104
- function generateDrei(generator, options) {
2539
+ function planDrei(builder, options) {
2105
2540
  if (options == null) {
2106
2541
  return;
2107
2542
  }
2108
- generator.addDependency("@react-three/drei");
2109
- generator.inject("import", `import { Environment } from "@react-three/drei"`);
2110
- generator.inject("scene", '<Environment background preset="city" />');
2111
- generator.inject(
2543
+ builder.addDependency("@react-three/drei");
2544
+ builder.inject("import", `import { Environment } from "@react-three/drei"`);
2545
+ builder.inject("scene", '<Environment background preset="city" />');
2546
+ builder.inject(
2112
2547
  "readme-libraries",
2113
2548
  `[@react-three/drei](https://drei.docs.pmnd.rs/) - Useful helpers for @react-three/fiber`
2114
2549
  );
@@ -2117,25 +2552,28 @@ function generateDrei(generator, options) {
2117
2552
  function toEslintLevel(level) {
2118
2553
  return level;
2119
2554
  }
2120
- function generateEslint(generator, options) {
2121
- generator.addDevDependency("eslint");
2122
- const template = generator.options.template ?? "vanilla";
2555
+ function planEslint(builder, options) {
2556
+ if (options == null) {
2557
+ return;
2558
+ }
2559
+ builder.addDevDependency("eslint");
2560
+ const template = builder.options.template ?? "vanilla";
2123
2561
  const baseTemplate = getBaseTemplate(template);
2124
2562
  const isTypescript = getLanguageFromTemplate(template) === "typescript";
2125
2563
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2126
- const { rules } = defaultLinterConfig;
2564
+ const { rules } = options.config;
2127
2565
  const imports = ['import js from "@eslint/js"'];
2128
2566
  const configs = ["js.configs.recommended"];
2129
2567
  if (isTypescript) {
2130
- generator.addDevDependency("typescript-eslint");
2568
+ builder.addDevDependency("typescript-eslint");
2131
2569
  imports.push('import tseslint from "typescript-eslint"');
2132
2570
  configs.push("...tseslint.configs.recommended");
2133
2571
  }
2134
2572
  if (isReact) {
2135
- generator.addDevDependency("eslint-plugin-react-hooks");
2573
+ builder.addDevDependency("eslint-plugin-react-hooks");
2136
2574
  imports.push('import reactHooks from "eslint-plugin-react-hooks"');
2137
2575
  }
2138
- const ignoresArray = JSON.stringify(defaultLinterConfig.ignorePatterns);
2576
+ const ignoresArray = JSON.stringify(options.config.ignorePatterns);
2139
2577
  const unusedVarsRule = isTypescript ? "@typescript-eslint/no-unused-vars" : "no-unused-vars";
2140
2578
  const rulesConfig = {
2141
2579
  [unusedVarsRule]: [
@@ -2169,34 +2607,30 @@ function generateEslint(generator, options) {
2169
2607
  },`,
2170
2608
  "]"
2171
2609
  ].filter(Boolean).join("\n");
2172
- const isStealth = generator.isStealthConfig();
2610
+ const isStealth = builder.isStealthConfig();
2173
2611
  if (isStealth) {
2174
- generator.addFile(".config/eslint.config.js", {
2612
+ builder.addFile(".config/eslint.config.js", {
2175
2613
  type: "text",
2176
2614
  content: configContent
2177
2615
  });
2178
- generator.addScript("lint", "eslint --config .config/eslint.config.js .");
2179
- generator.addVscodeSetting("eslint.options", {
2180
- overrideConfigFile: ".config/eslint.config.js"
2181
- });
2616
+ builder.addScripts(packageJsonScripts.lint.eslint(".config/eslint.config.js"));
2182
2617
  } else {
2183
- generator.addFile("eslint.config.js", {
2618
+ builder.addFile("eslint.config.js", {
2184
2619
  type: "text",
2185
2620
  content: configContent
2186
2621
  });
2187
- generator.addScript("lint", "eslint .");
2622
+ builder.addScripts(packageJsonScripts.lint.eslint());
2188
2623
  }
2189
- generator.inject(
2624
+ builder.inject(
2190
2625
  "readme-tools",
2191
2626
  "[ESLint](https://eslint.org/) - Linter for JavaScript and TypeScript"
2192
2627
  );
2193
- generator.inject("vscode-extension-suggestion", "dbaeumer.vscode-eslint");
2194
- generator.addVscodeSetting("eslint.enable", true);
2628
+ builder.inject("vscode-extension-suggestion", "dbaeumer.vscode-eslint");
2195
2629
  }
2196
2630
 
2197
- function generateFiber(generator, _options) {
2198
- generator.inject("import", `import { Box } from "./box.js"`);
2199
- generator.inject(
2631
+ function planFiber(builder, _options) {
2632
+ builder.inject("import", `import { Box } from "./box.js"`);
2633
+ builder.inject(
2200
2634
  "scene",
2201
2635
  [
2202
2636
  `<ambientLight intensity={Math.PI / 2} />`,
@@ -2206,7 +2640,7 @@ function generateFiber(generator, _options) {
2206
2640
  `<Box position={[1.2, 0, 0]} />`
2207
2641
  ].join("\n")
2208
2642
  );
2209
- generator.addFile("src/box.tsx", {
2643
+ builder.addFile("src/box.tsx", {
2210
2644
  type: "text",
2211
2645
  content: `import type { Mesh } from 'three'
2212
2646
  import { useRef, useState } from 'react'
@@ -2233,11 +2667,11 @@ export function Box(props: ThreeElements['mesh']) {
2233
2667
  });
2234
2668
  }
2235
2669
 
2236
- function generateGithubPages(generator, options) {
2237
- if (options === false || getPackageManagerName(generator.options.packageManager) !== "npm") {
2670
+ function planGithubPages(builder, options) {
2671
+ if (options === false || getPackageManagerName(builder.options.packageManager) !== "npm") {
2238
2672
  return;
2239
2673
  }
2240
- generator.addFile(".github/workflows/gh-pages.yml", {
2674
+ builder.addFile(".github/workflows/gh-pages.yml", {
2241
2675
  type: "text",
2242
2676
  content: `name: Deploy to Github Pages
2243
2677
 
@@ -2283,10 +2717,10 @@ jobs:
2283
2717
  uses: actions/deploy-pages@v4
2284
2718
  `
2285
2719
  });
2286
- generator.inject("readme-start", `A github pages deployment action is configurd.`);
2287
- if (generator.options.githubUserName != null && generator.options.githubRepoName != null) {
2288
- const address = `${generator.options.githubUserName}.github.io/${generator.options.githubRepoName}`;
2289
- generator.inject(
2720
+ builder.inject("readme-start", `A github pages deployment action is configurd.`);
2721
+ if (builder.options.githubUserName != null && builder.options.githubRepoName != null) {
2722
+ const address = `${builder.options.githubUserName}.github.io/${builder.options.githubRepoName}`;
2723
+ builder.inject(
2290
2724
  "readme-start",
2291
2725
  `Your app will be publish at [${address}](https://${address}) once the github action is finished.
2292
2726
  `
@@ -2294,228 +2728,199 @@ jobs:
2294
2728
  }
2295
2729
  }
2296
2730
 
2297
- function generateHandle(generator, options) {
2731
+ function planHandle(builder, options) {
2298
2732
  if (options == null) {
2299
2733
  return;
2300
2734
  }
2301
- generator.addDependency("@react-three/handle");
2302
- generator.inject(
2735
+ builder.addDependency("@react-three/handle");
2736
+ builder.inject(
2303
2737
  "readme-libraries",
2304
2738
  `[@react-three/handle](https://pmndrs.github.io/xr/docs/handles/introduction) - interactive controls and handles for your 3D objects`
2305
2739
  );
2306
2740
  }
2307
2741
 
2308
- function generateKoota(generator, options) {
2742
+ function planKoota(builder, options) {
2309
2743
  if (options == null) {
2310
2744
  return;
2311
2745
  }
2312
- generator.addDependency("koota");
2313
- generator.inject(
2746
+ builder.addDependency("koota");
2747
+ builder.inject(
2314
2748
  "readme-libraries",
2315
2749
  `[koota](https://github.com/pmndrs/koota) - ECS-based state management library optimized for real-time apps, games, and XR experiences`
2316
2750
  );
2317
2751
  }
2318
2752
 
2319
- function generateLeva(generator, options) {
2753
+ function planLeva(builder, options) {
2320
2754
  if (options == null) {
2321
2755
  return;
2322
2756
  }
2323
- generator.addDependency("leva");
2324
- generator.inject(
2757
+ builder.addDependency("leva");
2758
+ builder.inject(
2325
2759
  "readme-libraries",
2326
2760
  `[leva](https://github.com/pmndrs/leva) - HTML GUI panel for React with lightweight, beautiful and extensible controls`
2327
2761
  );
2328
2762
  }
2329
2763
 
2330
- function generateOffscreen(generator, options) {
2764
+ function planOffscreen(builder, options) {
2331
2765
  if (options == null) {
2332
2766
  return;
2333
2767
  }
2334
- if (generator.options.xr != null) {
2768
+ if (builder.options.xr != null) {
2335
2769
  console.info(
2336
2770
  color__default.blue("Info:"),
2337
2771
  "@react-three/offscreen is disabled because it is not supported with XR"
2338
2772
  );
2339
2773
  return;
2340
2774
  }
2341
- generator.addDependency("@react-three/offscreen");
2342
- generator.inject(
2775
+ builder.addDependency("@react-three/offscreen");
2776
+ builder.inject(
2343
2777
  "readme-libraries",
2344
2778
  `[@react-three/offscreen](https://github.com/pmndrs/offscreen) - Offload your scene to a worker thread for better performance`
2345
2779
  );
2346
2780
  }
2347
2781
 
2348
- function generateOxfmt(generator, options) {
2349
- const isMonorepo = generator.options.workspaceRoot != null;
2782
+ function planOxfmt(builder, options) {
2783
+ if (options == null) {
2784
+ return;
2785
+ }
2786
+ const isMonorepo = builder.options.workspaceRoot != null;
2350
2787
  if (isMonorepo) {
2351
- generator.addDevDependency("@config/oxfmt", { version: "workspace:*" });
2788
+ builder.addDevDependency("@config/oxfmt", { version: "workspace:*" });
2352
2789
  const configPath = "node_modules/@config/oxfmt/base.json";
2353
- generator.addScript("format", `oxfmt -c ${configPath} --write .`);
2354
- generator.addVscodeSetting("oxc.fmt.configPath", configPath);
2790
+ builder.addScripts(packageJsonScripts.format.oxfmt(configPath));
2355
2791
  } else {
2356
- generator.addDevDependency("oxfmt");
2357
- const isStealth = generator.isStealthConfig();
2792
+ builder.addDevDependency("oxfmt");
2793
+ const isStealth = builder.isStealthConfig();
2358
2794
  if (isStealth) {
2359
- generator.addFile(".config/oxfmt.json", {
2795
+ builder.addFile(".config/oxfmt.json", {
2360
2796
  type: "text",
2361
- content: JSON.stringify(defaultOxfmtConfig, null, 2)
2797
+ content: JSON.stringify(toOxfmtConfig(options.config), null, 2)
2362
2798
  });
2363
- generator.addScript("format", "oxfmt -c .config/oxfmt.json --write .");
2364
- generator.addVscodeSetting("oxc.fmt.configPath", ".config/oxfmt.json");
2799
+ builder.addScripts(packageJsonScripts.format.oxfmt(".config/oxfmt.json"));
2365
2800
  } else {
2366
- generator.addFile("oxfmt.json", {
2801
+ builder.addFile("oxfmt.json", {
2367
2802
  type: "text",
2368
- content: JSON.stringify(defaultOxfmtConfig, null, 2)
2803
+ content: JSON.stringify(toOxfmtConfig(options.config), null, 2)
2369
2804
  });
2370
- generator.addScript("format", "oxfmt -c oxfmt.json --write .");
2805
+ builder.addScripts(packageJsonScripts.format.oxfmt("oxfmt.json"));
2371
2806
  }
2372
2807
  }
2373
- generator.inject(
2808
+ builder.inject(
2374
2809
  "readme-tools",
2375
2810
  "[Oxfmt](https://oxc.rs/docs/guide/usage/formatter) - Fast Prettier-compatible code formatter"
2376
2811
  );
2377
- generator.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2378
- generator.addVscodeSetting("editor.defaultFormatter", "oxc.oxc-vscode");
2379
- generator.addVscodeSetting("[json]", {
2380
- "editor.defaultFormatter": "vscode.json-language-features"
2381
- });
2382
- generator.addVscodeSetting("[jsonc]", {
2383
- "editor.defaultFormatter": "vscode.json-language-features"
2384
- });
2385
- generator.addVscodeSetting("[markdown]", {
2386
- "editor.defaultFormatter": "vscode.markdown-language-features"
2387
- });
2388
- generator.addVscodeSetting("[yaml]", {
2389
- "editor.defaultFormatter": "redhat.vscode-yaml"
2390
- });
2812
+ builder.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2391
2813
  }
2392
2814
 
2393
- function toOxlintLevel(level) {
2394
- return level;
2395
- }
2396
- function generateOxlint(generator, options) {
2397
- const template = generator.options.template ?? "vanilla";
2815
+ function planOxlint(builder, options) {
2816
+ if (options == null) {
2817
+ return;
2818
+ }
2819
+ const template = builder.options.template ?? "vanilla";
2398
2820
  const baseTemplate = getBaseTemplate(template);
2821
+ const isTypescript = getLanguageFromTemplate(template) === "typescript";
2399
2822
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2400
- const isMonorepo = generator.options.workspaceRoot != null;
2823
+ const isMonorepo = builder.options.workspaceRoot != null;
2401
2824
  if (isMonorepo) {
2402
- generator.addDevDependency("@config/oxlint", { version: "workspace:*" });
2825
+ builder.addDevDependency("@config/oxlint", { version: "workspace:*" });
2403
2826
  const configPath = isReact ? "node_modules/@config/oxlint/react.json" : "node_modules/@config/oxlint/base.json";
2404
- generator.addScript("lint", `oxlint -c ${configPath}`);
2405
- generator.addVscodeSetting("oxc.configPath", configPath);
2827
+ builder.addScripts(packageJsonScripts.lint.oxlint(configPath));
2406
2828
  } else {
2407
- generator.addDevDependency("oxlint");
2408
- const isStealth = generator.isStealthConfig();
2409
- const { rules } = defaultLinterConfig;
2410
- const plugins = ["unicorn", "typescript", "oxc"];
2411
- if (isReact) {
2412
- plugins.push("react");
2829
+ builder.addDevDependency("oxlint");
2830
+ if (isTypescript) {
2831
+ builder.addDevDependency("oxlint-tsgolint");
2413
2832
  }
2414
- const oxlintConfig = {
2415
- $schema: isStealth ? "../node_modules/oxlint/configuration_schema.json" : "./node_modules/oxlint/configuration_schema.json",
2416
- plugins,
2417
- rules: {
2418
- "no-unused-vars": [
2419
- toOxlintLevel(rules.noUnusedVars.level),
2420
- {
2421
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
2422
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
2423
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
2424
- }
2425
- ],
2426
- "no-useless-escape": "off",
2427
- "no-unused-expressions": [
2428
- toOxlintLevel(rules.noUnusedExpressions.level),
2429
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
2430
- ]
2431
- },
2432
- ignorePatterns: defaultLinterConfig.ignorePatterns
2433
- };
2833
+ const isStealth = builder.isStealthConfig();
2834
+ const oxlintConfig = renderOxlintConfig({
2835
+ schemaPath: isStealth ? "../node_modules/oxlint/configuration_schema.json" : "./node_modules/oxlint/configuration_schema.json",
2836
+ react: isReact,
2837
+ typescript: isTypescript,
2838
+ config: options.config
2839
+ });
2434
2840
  if (isStealth) {
2435
- generator.addFile(".config/oxlint.json", {
2841
+ builder.addFile(".config/oxlint.json", {
2436
2842
  type: "text",
2437
2843
  content: JSON.stringify(oxlintConfig, null, 2)
2438
2844
  });
2439
- generator.addScript("lint", "oxlint -c .config/oxlint.json");
2440
- generator.addVscodeSetting("oxc.configPath", ".config/oxlint.json");
2845
+ builder.addScripts(packageJsonScripts.lint.oxlint(".config/oxlint.json"));
2441
2846
  } else {
2442
- generator.addFile("oxlint.json", {
2847
+ builder.addFile("oxlint.json", {
2443
2848
  type: "text",
2444
2849
  content: JSON.stringify(oxlintConfig, null, 2)
2445
2850
  });
2446
- generator.addScript("lint", "oxlint");
2851
+ builder.addScripts(packageJsonScripts.lint.oxlint());
2447
2852
  }
2448
2853
  }
2449
- generator.inject(
2854
+ builder.inject(
2450
2855
  "readme-tools",
2451
2856
  "[Oxlint](https://oxc.rs/docs/guide/usage/linter) - A fast linter for JavaScript and TypeScript"
2452
2857
  );
2453
- generator.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2454
- generator.addVscodeSetting("oxc.enable", true);
2858
+ builder.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2455
2859
  }
2456
2860
 
2457
- function generatePostprocessing(generator, options) {
2861
+ function planPostprocessing(builder, options) {
2458
2862
  if (options == null) {
2459
2863
  return;
2460
2864
  }
2461
- if (generator.options.xr != null) {
2865
+ if (builder.options.xr != null) {
2462
2866
  console.info(
2463
2867
  color__default.blue("Info:"),
2464
2868
  "@react-three/postprocessing is disabled because it is not supported with XR"
2465
2869
  );
2466
2870
  return;
2467
2871
  }
2468
- generator.addDependency("@react-three/postprocessing");
2469
- generator.inject(
2872
+ builder.addDependency("@react-three/postprocessing");
2873
+ builder.inject(
2470
2874
  "readme-libraries",
2471
2875
  `[@react-three/postprocessing](https://react-postprocessing.docs.pmnd.rs/) - Post-processing effects for @react-three/fiber`
2472
2876
  );
2473
2877
  }
2474
2878
 
2475
- function generatePrettier(generator, options) {
2476
- generator.addDevDependency("prettier");
2477
- const isStealth = generator.isStealthConfig();
2879
+ function planPrettier(builder, options) {
2880
+ if (options == null) {
2881
+ return;
2882
+ }
2883
+ builder.addDevDependency("prettier");
2884
+ const isStealth = builder.isStealthConfig();
2478
2885
  if (isStealth) {
2479
- generator.addFile(".config/prettier.json", {
2886
+ builder.addFile(".config/prettier.json", {
2887
+ type: "text",
2888
+ content: JSON.stringify(toPrettierConfig(options.config), null, 2)
2889
+ });
2890
+ builder.addFile(".config/prettierignore", {
2480
2891
  type: "text",
2481
- content: JSON.stringify(defaultPrettierConfig, null, 2)
2892
+ content: toPrettierIgnoreContent(options.config)
2482
2893
  });
2483
- generator.addScript("format", "prettier --config .config/prettier.json --write .");
2484
- generator.addVscodeSetting("prettier.configPath", ".config/prettier.json");
2894
+ builder.addScripts(
2895
+ packageJsonScripts.format.prettier(".config/prettier.json", ".config/prettierignore")
2896
+ );
2485
2897
  } else {
2486
- generator.addFile(".prettierrc", {
2898
+ builder.addFile(".prettierrc", {
2487
2899
  type: "text",
2488
- content: JSON.stringify(defaultPrettierConfig, null, 2)
2900
+ content: JSON.stringify(toPrettierConfig(options.config), null, 2)
2489
2901
  });
2490
- generator.addScript("format", "prettier --write .");
2902
+ builder.addFile(".prettierignore", {
2903
+ type: "text",
2904
+ content: toPrettierIgnoreContent(options.config)
2905
+ });
2906
+ builder.addScripts(packageJsonScripts.format.prettier());
2491
2907
  }
2492
- generator.inject("readme-tools", "[Prettier](https://prettier.io/) - Opinionated code formatter");
2493
- generator.inject("vscode-extension-suggestion", "esbenp.prettier-vscode");
2494
- generator.addVscodeSetting("editor.defaultFormatter", "esbenp.prettier-vscode");
2908
+ builder.inject("readme-tools", "[Prettier](https://prettier.io/) - Opinionated code formatter");
2909
+ builder.inject("vscode-extension-suggestion", "esbenp.prettier-vscode");
2495
2910
  }
2496
2911
 
2497
- function generateRapier(generator, options) {
2912
+ function planRapier(builder, options) {
2498
2913
  if (options == null) {
2499
2914
  return;
2500
2915
  }
2501
- generator.addDependency("@react-three/rapier");
2502
- generator.inject(
2916
+ builder.addDependency("@react-three/rapier");
2917
+ builder.inject(
2503
2918
  "readme-libraries",
2504
2919
  `[@react-three/rapier](https://github.com/pmndrs/react-three-rapier) - Physics based on Rapier for your @react-three/fiber scene`
2505
2920
  );
2506
2921
  }
2507
2922
 
2508
- function unique(...array) {
2509
- const set = /* @__PURE__ */ new Set();
2510
- for (const arr of array) {
2511
- for (const item of arr) {
2512
- set.add(item);
2513
- }
2514
- }
2515
- return Array.from(set);
2516
- }
2517
-
2518
- function generateProvidersModule(generator) {
2923
+ function generateProvidersModule(builder) {
2519
2924
  const canvasProviders = [];
2520
2925
  const globalProviders = [];
2521
2926
  const providerDefs = {
@@ -2581,13 +2986,13 @@ function generateProvidersModule(generator) {
2581
2986
  ]
2582
2987
  }
2583
2988
  };
2584
- if (generator.options.rapier) {
2989
+ if (builder.options.rapier) {
2585
2990
  canvasProviders.push("rapier");
2586
2991
  }
2587
- if (!!generator.options.postprocessing && !generator.options.xr) {
2992
+ if (!!builder.options.postprocessing && !builder.options.xr) {
2588
2993
  canvasProviders.push("postprocessing");
2589
2994
  }
2590
- if (generator.options.uikit) {
2995
+ if (builder.options.uikit) {
2591
2996
  globalProviders.push("uikit");
2592
2997
  }
2593
2998
  function generateProviderFunction(name, { jsdoc, providers }) {
@@ -2650,20 +3055,20 @@ ${jsdoc.split("\n").map((line) => ` * ${line}`).join("\n")}
2650
3055
  ${canvas.code}
2651
3056
  `;
2652
3057
  }
2653
- function generateTriplex(generator, options) {
3058
+ function planTriplex(builder, options) {
2654
3059
  if (options == null) {
2655
3060
  return;
2656
3061
  }
2657
- generator.inject("vscode-extension-suggestion", "trytriplex.triplex-vsce");
2658
- generator.inject(
3062
+ builder.inject("vscode-extension-suggestion", "trytriplex.triplex-vsce");
3063
+ builder.inject(
2659
3064
  "readme-tools",
2660
3065
  `[Triplex](https://triplex.dev) - Your visual workspace for React / Three Fiber. Get started by installing [Triplex for VS Code](https://triplex.dev/docs/get-started/vscode). Don't use Visual Studio Code? Download [Triplex Standalone](https://triplex.dev/docs/get-started/standalone).`
2661
3066
  );
2662
- generator.addFile(".triplex/providers.tsx", {
2663
- content: generateProvidersModule(generator),
3067
+ builder.addFile(".triplex/providers.tsx", {
3068
+ content: generateProvidersModule(builder),
2664
3069
  type: "text"
2665
3070
  });
2666
- generator.addFile(".triplex/config.json", {
3071
+ builder.addFile(".triplex/config.json", {
2667
3072
  content: JSON.stringify(
2668
3073
  {
2669
3074
  $schema: "https://triplex.dev/config.schema.json",
@@ -2676,9 +3081,9 @@ function generateTriplex(generator, options) {
2676
3081
  });
2677
3082
  }
2678
3083
 
2679
- function generateTsdown(generator) {
2680
- generator.addDevDependency("tsdown");
2681
- const template = generator.options.template ?? "vanilla";
3084
+ function planTsdown(builder) {
3085
+ builder.addDevDependency("tsdown");
3086
+ const template = builder.options.template ?? "vanilla";
2682
3087
  const baseTemplate = getBaseTemplate(template);
2683
3088
  const language = getLanguageFromTemplate(template);
2684
3089
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
@@ -2698,36 +3103,36 @@ function generateTsdown(generator) {
2698
3103
  configLines.push(` },`);
2699
3104
  }
2700
3105
  configLines.push(`})`);
2701
- generator.addFile(`tsdown.config.${ext}`, {
3106
+ builder.addFile(`tsdown.config.${ext}`, {
2702
3107
  type: "text",
2703
3108
  content: configLines.join("\n")
2704
3109
  });
2705
- generator.addScript("build", "tsdown");
2706
- generator.inject(
3110
+ builder.addScripts(packageJsonScripts.build.tsdown);
3111
+ builder.inject(
2707
3112
  "readme-libraries",
2708
3113
  "[tsdown](https://github.com/nicepkg/tsdown) - Fast TypeScript bundler powered by esbuild"
2709
3114
  );
2710
3115
  }
2711
3116
 
2712
- function generateUikit(generator, options) {
3117
+ function planUikit(builder, options) {
2713
3118
  if (options == null) {
2714
3119
  return;
2715
3120
  }
2716
- generator.addDependency("@react-three/uikit");
2717
- generator.inject(
3121
+ builder.addDependency("@react-three/uikit");
3122
+ builder.inject(
2718
3123
  "readme-libraries",
2719
3124
  `[@react-three/uikit](https://pmndrs.github.io/uikit/docs/) - UI primitives for React Three Fiber`
2720
3125
  );
2721
3126
  }
2722
3127
 
2723
- function generateUnbuild(generator) {
2724
- generator.addDevDependency("unbuild");
2725
- const template = generator.options.template ?? "vanilla";
3128
+ function planUnbuild(builder) {
3129
+ builder.addDevDependency("unbuild");
3130
+ const template = builder.options.template ?? "vanilla";
2726
3131
  const baseTemplate = getBaseTemplate(template);
2727
3132
  const language = getLanguageFromTemplate(template);
2728
3133
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2729
3134
  const ext = language === "typescript" ? "ts" : "js";
2730
- const isMonorepo = generator.options.workspaceRoot != null;
3135
+ const isMonorepo = builder.options.workspaceRoot != null;
2731
3136
  const buildConfigLines = [
2732
3137
  `import { defineBuildConfig } from "unbuild"`,
2733
3138
  ``,
@@ -2745,51 +3150,54 @@ function generateUnbuild(generator) {
2745
3150
  }
2746
3151
  buildConfigLines.push(` },`);
2747
3152
  buildConfigLines.push(`})`);
2748
- const isStealth = generator.isStealthConfig() && !isMonorepo;
3153
+ const isStealth = builder.isStealthConfig() && !isMonorepo;
2749
3154
  if (isStealth) {
2750
- generator.addFile(`.config/build.config.${ext}`, {
3155
+ builder.addFile(`.config/build.config.${ext}`, {
2751
3156
  type: "text",
2752
3157
  content: buildConfigLines.join("\n")
2753
3158
  });
2754
- generator.addScript("build", `unbuild --config .config/build.config.${ext}`);
3159
+ builder.addScripts(packageJsonScripts.build.unbuild(`.config/build.config.${ext}`));
2755
3160
  } else {
2756
- generator.addFile(`build.config.${ext}`, {
3161
+ builder.addFile(`build.config.${ext}`, {
2757
3162
  type: "text",
2758
3163
  content: buildConfigLines.join("\n")
2759
3164
  });
2760
- generator.addScript("build", "unbuild");
3165
+ builder.addScripts(packageJsonScripts.build.unbuild());
2761
3166
  }
2762
- generator.inject(
3167
+ builder.inject(
2763
3168
  "readme-libraries",
2764
3169
  "[unbuild](https://github.com/unjs/unbuild) - Unified JavaScript build system"
2765
3170
  );
2766
3171
  }
2767
3172
 
2768
- function generateVitest(generator) {
2769
- generator.addDevDependency("vitest");
2770
- const template = generator.options.template ?? "vanilla";
3173
+ function planVitest(builder, options) {
3174
+ if (options == null) {
3175
+ return;
3176
+ }
3177
+ builder.addDevDependency("vitest");
3178
+ const template = builder.options.template ?? "vanilla";
2771
3179
  const baseTemplate = getBaseTemplate(template);
2772
3180
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2773
3181
  if (isReact) {
2774
- generator.addDevDependency("@testing-library/react");
2775
- generator.addDevDependency("@testing-library/dom");
2776
- generator.addDevDependency("jsdom");
3182
+ builder.addDevDependency("@testing-library/react");
3183
+ builder.addDevDependency("@testing-library/dom");
3184
+ builder.addDevDependency("jsdom");
2777
3185
  }
2778
3186
  if (isReact) {
2779
- generator.configureVite({ test: { environment: "jsdom" } });
3187
+ builder.configureVite({ test: { environment: "jsdom" } });
2780
3188
  }
2781
- generator.addScript("test", "vitest");
2782
- generator.inject(
3189
+ builder.addScripts(packageJsonScripts.test.vitest);
3190
+ builder.inject(
2783
3191
  "readme-tools",
2784
3192
  "[Vitest](https://vitest.dev/) - Fast unit test framework powered by Vite"
2785
3193
  );
2786
3194
  }
2787
3195
 
2788
- function generateViverse(generator, options) {
2789
- if (options == null || getPackageManagerName(generator.options.packageManager) !== "npm") {
3196
+ function planViverse(builder, options) {
3197
+ if (options == null || getPackageManagerName(builder.options.packageManager) !== "npm") {
2790
3198
  return;
2791
3199
  }
2792
- generator.addFile(".github/workflows/viverse.yml", {
3200
+ builder.addFile(".github/workflows/viverse.yml", {
2793
3201
  type: "text",
2794
3202
  content: `name: Deploy to Viverse
2795
3203
 
@@ -2840,12 +3248,12 @@ jobs:
2840
3248
  run: npx viverse-cli auth login -e \${{ secrets.VIVERSE_EMAIL }} -p \${{ secrets.VIVERSE_PASSWORD }}
2841
3249
 
2842
3250
  - name: Deploy to Viverse
2843
- run: npx viverse-cli app publish ./dist --auto-create-app --name ${generator.options.name}
3251
+ run: npx viverse-cli app publish ./dist --auto-create-app --name ${builder.options.name}
2844
3252
 
2845
3253
  `
2846
3254
  });
2847
- generator.addDependency("@viverse/cli");
2848
- generator.inject(
3255
+ builder.addDependency("@viverse/cli");
3256
+ builder.inject(
2849
3257
  "readme-start",
2850
3258
  `A GitHub CI/CD workflow for publishing to Viverse is configured.
2851
3259
 
@@ -2858,36 +3266,36 @@ You can also upload your project manually using the Viverse CLI:
2858
3266
  \`\`\`bash
2859
3267
  viverse-cli auth login -e <email> -p <password>
2860
3268
  npm run build
2861
- viverse-cli app publish ./dist --auto-create-app --name ${generator.options.name}
3269
+ viverse-cli app publish ./dist --auto-create-app --name ${builder.options.name}
2862
3270
  \`\`\`
2863
3271
  `
2864
3272
  );
2865
3273
  }
2866
3274
 
2867
- function generateXr(generator, options) {
3275
+ function planXr(builder, options) {
2868
3276
  if (options == null || options === false) {
2869
3277
  return;
2870
3278
  }
2871
3279
  if (options === true) {
2872
3280
  options = {};
2873
3281
  }
2874
- generator.addDependency("@react-three/xr");
2875
- generator.addDependency("@vitejs/plugin-basic-ssl");
2876
- generator.inject("import", "import { XR, createXRStore } from '@react-three/xr'");
2877
- generator.inject(
3282
+ builder.addDependency("@react-three/xr");
3283
+ builder.addDependency("@vitejs/plugin-basic-ssl");
3284
+ builder.inject("import", "import { XR, createXRStore } from '@react-three/xr'");
3285
+ builder.inject(
2878
3286
  `global-start`,
2879
3287
  `const store = createXRStore(${JSON.stringify(options.storeOptions ?? {})})`
2880
3288
  );
2881
- generator.inject("scene-start", "<XR store={store}>");
2882
- generator.inject("scene-end", "</XR>");
2883
- generator.inject("vite-config-import", "import basicSsl from '@vitejs/plugin-basic-ssl'");
2884
- generator.configureVite({
3289
+ builder.inject("scene-start", "<XR store={store}>");
3290
+ builder.inject("scene-end", "</XR>");
3291
+ builder.inject("vite-config-import", "import basicSsl from '@vitejs/plugin-basic-ssl'");
3292
+ builder.configureVite({
2885
3293
  server: {
2886
3294
  host: true
2887
3295
  },
2888
3296
  plugins: ["$raw:basicSsl()"]
2889
3297
  });
2890
- generator.inject(
3298
+ builder.inject(
2891
3299
  "dom-start",
2892
3300
  `<div style={{
2893
3301
  display: "flex",
@@ -2919,55 +3327,226 @@ function generateXr(generator, options) {
2919
3327
  Enter VR
2920
3328
  </button></div>`
2921
3329
  );
2922
- generator.inject(
3330
+ builder.inject(
2923
3331
  "readme-libraries",
2924
3332
  `[@react-three/xr](https://pmndrs.github.io/xr/docs/) - VR/AR support for @react-three/fiber`
2925
3333
  );
2926
3334
  }
2927
3335
 
2928
- function generateZustand(generator, options) {
3336
+ function planZustand(builder, options) {
2929
3337
  if (options == null) {
2930
3338
  return;
2931
3339
  }
2932
- generator.addDependency("zustand");
2933
- generator.inject(
3340
+ builder.addDependency("zustand");
3341
+ builder.inject(
2934
3342
  "readme-libraries",
2935
3343
  `[zustand](https://zustand.docs.pmnd.rs/) - small, fast and scalable state-management solution`
2936
3344
  );
2937
3345
  }
2938
3346
 
2939
- function merge(target, modification) {
2940
- if (modification == null) {
2941
- throw new Error(`Cannot merge "${modification}" modification into target "${target}"`);
2942
- }
2943
- if (target == null) {
2944
- return modification;
2945
- }
2946
- if (Array.isArray(target)) {
2947
- if (!Array.isArray(modification)) {
2948
- throw new Error(
2949
- `Cannot merge non-array modification "${modification}" into array target "${target}"`
2950
- );
2951
- }
2952
- return [...target, ...modification];
2953
- }
2954
- if (typeof target === "object") {
2955
- if (typeof modification != "object") {
2956
- throw new Error(
2957
- `Cannot merge non-object modification "${modification}" into object target "${target}"`
2958
- );
3347
+ function resolveProjectPlanInput(options) {
3348
+ const packageManager = options.packageManager ?? { name: "pnpm" };
3349
+ return {
3350
+ project: {
3351
+ githubUserName: options.githubUserName,
3352
+ githubRepoName: options.githubRepoName,
3353
+ name: options.name,
3354
+ projectType: options.projectType,
3355
+ template: options.template
3356
+ },
3357
+ aiAgents: {
3358
+ tool: "ai-agents",
3359
+ config: {
3360
+ platforms: options.aiPlatforms ?? []
3361
+ }
3362
+ },
3363
+ formatter: {
3364
+ tool: options.formatter ?? "prettier",
3365
+ config: structuredClone(defaultFormatterMetaConfig)
3366
+ },
3367
+ linter: {
3368
+ tool: options.linter ?? "oxlint",
3369
+ config: structuredClone(defaultLinterMetaConfig)
3370
+ },
3371
+ testing: {
3372
+ tool: options.testing ?? (options.projectType === "library" ? "vitest" : "none"),
3373
+ config: {}
3374
+ },
3375
+ typescript: {
3376
+ tool: "typescript",
3377
+ config: {
3378
+ configStrategy: options.configStrategy
3379
+ }
3380
+ },
3381
+ ide: {
3382
+ tool: options.ide ?? "vscode",
3383
+ config: {}
3384
+ },
3385
+ packageManager: {
3386
+ tool: packageManager.name,
3387
+ config: {
3388
+ version: packageManager.version,
3389
+ pnpmManageVersions: options.pnpmManageVersions
3390
+ }
3391
+ },
3392
+ libraryBundler: {
3393
+ tool: options.libraryBundler ?? "unbuild",
3394
+ config: {}
3395
+ },
3396
+ features: {
3397
+ fiber: options.fiber,
3398
+ handle: options.handle,
3399
+ drei: options.drei,
3400
+ koota: options.koota,
3401
+ leva: options.leva,
3402
+ offscreen: options.offscreen,
3403
+ postprocessing: options.postprocessing,
3404
+ rapier: options.rapier,
3405
+ triplex: options.triplex,
3406
+ viverse: options.viverse,
3407
+ uikit: options.uikit,
3408
+ xr: options.xr,
3409
+ zustand: options.zustand,
3410
+ githubPages: options.githubPages
3411
+ },
3412
+ context: {
3413
+ dependencies: options.dependencies,
3414
+ engine: options.engine,
3415
+ files: options.files,
3416
+ injections: options.injections,
3417
+ replacements: options.replacements,
3418
+ versions: options.versions,
3419
+ workspaceRoot: options.workspaceRoot,
3420
+ workspaceDependencies: options.workspaceDependencies
2959
3421
  }
2960
- const result = { ...target };
2961
- for (const modificationKey in modification) {
2962
- result[modificationKey] = merge(target[modificationKey], modification[modificationKey]);
3422
+ };
3423
+ }
3424
+ function projectPlanInputToOptions(input) {
3425
+ return {
3426
+ ...input.project,
3427
+ aiPlatforms: input.aiAgents.config.platforms.length > 0 ? input.aiAgents.config.platforms : void 0,
3428
+ formatter: input.formatter.tool,
3429
+ linter: input.linter.tool,
3430
+ testing: input.testing.tool,
3431
+ configStrategy: input.typescript.config.configStrategy,
3432
+ ide: input.ide.tool,
3433
+ packageManager: packageManagerSpecFromInput(input.packageManager),
3434
+ pnpmManageVersions: input.packageManager.config.pnpmManageVersions,
3435
+ libraryBundler: input.libraryBundler.tool,
3436
+ ...input.features,
3437
+ dependencies: input.context.dependencies,
3438
+ engine: input.context.engine,
3439
+ files: input.context.files,
3440
+ injections: input.context.injections,
3441
+ replacements: input.context.replacements,
3442
+ versions: input.context.versions,
3443
+ workspaceRoot: input.context.workspaceRoot,
3444
+ workspaceDependencies: input.context.workspaceDependencies
3445
+ };
3446
+ }
3447
+ function resolveWorkspacePlanInput(params) {
3448
+ return {
3449
+ project: {
3450
+ name: params.name
3451
+ },
3452
+ aiAgents: {
3453
+ tool: "ai-agents",
3454
+ config: {
3455
+ platforms: params.aiPlatforms ?? []
3456
+ }
3457
+ },
3458
+ formatter: {
3459
+ tool: params.formatter,
3460
+ config: structuredClone(defaultFormatterMetaConfig)
3461
+ },
3462
+ linter: {
3463
+ tool: params.linter,
3464
+ config: structuredClone(defaultLinterMetaConfig)
3465
+ },
3466
+ ide: {
3467
+ tool: params.ide ?? "vscode",
3468
+ config: {}
3469
+ },
3470
+ packageManager: {
3471
+ tool: params.packageManager.name,
3472
+ config: {
3473
+ version: params.packageManager.version,
3474
+ pnpmManageVersions: params.pnpmManageVersions
3475
+ }
3476
+ },
3477
+ context: {
3478
+ engine: params.engine,
3479
+ pnpmManageVersions: params.pnpmManageVersions,
3480
+ versions: params.versions
2963
3481
  }
2964
- return result;
2965
- }
2966
- console.warn(`target "${target}" is overwritten with modification "${modification}"`);
2967
- return modification;
3482
+ };
3483
+ }
3484
+ function workspacePlanInputToMonorepoParams(input) {
3485
+ return {
3486
+ name: input.project.name,
3487
+ linter: input.linter.tool,
3488
+ formatter: input.formatter.tool,
3489
+ packageManager: packageManagerSpecFromInput(input.packageManager),
3490
+ pnpmManageVersions: input.context.pnpmManageVersions,
3491
+ engine: input.context.engine,
3492
+ versions: input.context.versions,
3493
+ aiPlatforms: input.aiAgents.config.platforms.length > 0 ? input.aiAgents.config.platforms : void 0,
3494
+ ide: input.ide.tool
3495
+ };
3496
+ }
3497
+ function isProjectPlanInput(input) {
3498
+ return "project" in input && "formatter" in input && typeof input.formatter === "object";
3499
+ }
3500
+ function isWorkspacePlanInput(input) {
3501
+ return "project" in input && "formatter" in input && typeof input.formatter === "object";
3502
+ }
3503
+ function packageManagerSpecFromInput(packageManager) {
3504
+ return {
3505
+ name: packageManager.tool,
3506
+ version: packageManager.config.version
3507
+ };
3508
+ }
3509
+
3510
+ async function resolveProjectFacts(input) {
3511
+ const options = projectPlanInputToOptions(input);
3512
+ options.packageManager = await resolvePackageManager(options);
3513
+ options.engine = await resolveEngine(options);
3514
+ options.versions = await resolveProjectPackageVersions(options);
3515
+ return resolveProjectPlanInput(options);
3516
+ }
3517
+ async function resolveWorkspaceFacts(input) {
3518
+ const params = workspacePlanInputToMonorepoParams(input);
3519
+ const options = {
3520
+ name: params.name,
3521
+ linter: params.linter,
3522
+ formatter: params.formatter,
3523
+ packageManager: params.packageManager,
3524
+ engine: params.engine,
3525
+ pnpmManageVersions: params.pnpmManageVersions,
3526
+ versions: params.versions
3527
+ };
3528
+ const packageManager = await resolvePackageManager(options);
3529
+ const engine = await resolveEngine(options);
3530
+ const versions = await resolveMonorepoRootPackageVersions({
3531
+ linter: params.linter,
3532
+ formatter: params.formatter,
3533
+ engine,
3534
+ versions: params.versions
3535
+ });
3536
+ return resolveWorkspacePlanInput({
3537
+ ...params,
3538
+ packageManager,
3539
+ engine,
3540
+ versions
3541
+ });
2968
3542
  }
2969
3543
 
2970
- function generate(options) {
3544
+ async function planProject(input) {
3545
+ const planInput = isProjectPlanInput(input) ? input : resolveProjectPlanInput(input);
3546
+ return createProjectPlan(await resolveProjectFacts(planInput));
3547
+ }
3548
+ function createProjectPlan(planInput) {
3549
+ const options = projectPlanInputToOptions(planInput);
2971
3550
  const clonedOptions = structuredClone(options);
2972
3551
  const template = clonedOptions.template ?? "vanilla";
2973
3552
  const baseTemplate = getBaseTemplate(template);
@@ -2976,7 +3555,8 @@ function generate(options) {
2976
3555
  const isReact = baseTemplate === "react";
2977
3556
  const isR3f = baseTemplate === "r3f";
2978
3557
  const isLibrary = clonedOptions.projectType === "library";
2979
- const libraryBundler = clonedOptions.libraryBundler ?? "unbuild";
3558
+ const libraryBundler = planInput.libraryBundler.tool;
3559
+ const ide = planInput.ide.tool;
2980
3560
  const files = {
2981
3561
  ...clonedOptions.files
2982
3562
  };
@@ -3010,7 +3590,7 @@ function generate(options) {
3010
3590
  }
3011
3591
  }
3012
3592
  if (language === "typescript") {
3013
- const tsResult = generateTypescriptConfig({
3593
+ const tsResult = renderTypescriptConfig({
3014
3594
  baseTemplate,
3015
3595
  useConfigPackage: clonedOptions.workspaceRoot != null,
3016
3596
  configStrategy: clonedOptions.configStrategy,
@@ -3022,10 +3602,7 @@ function generate(options) {
3022
3602
  }
3023
3603
  const codeSnippets = {};
3024
3604
  const vscodeSettings = {};
3025
- const scripts = isLibrary ? {} : {
3026
- dev: "vite",
3027
- build: "vite build"
3028
- };
3605
+ const scripts = {};
3029
3606
  if (!isLibrary && (isReact || isR3f)) {
3030
3607
  codeSnippets["vite-config-import"] = ["import react from '@vitejs/plugin-react'"];
3031
3608
  }
@@ -3044,7 +3621,7 @@ function generate(options) {
3044
3621
  viteConfig.resolve = { dedupe: ["three"] };
3045
3622
  }
3046
3623
  const isMonorepoPackage = clonedOptions.workspaceRoot != null;
3047
- const generator = {
3624
+ const builder = {
3048
3625
  options: clonedOptions,
3049
3626
  versions,
3050
3627
  getVersion(name2) {
@@ -3074,8 +3651,11 @@ function generate(options) {
3074
3651
  addFile(path, content) {
3075
3652
  files[path] = content;
3076
3653
  },
3654
+ addScripts(nextScripts) {
3655
+ Object.assign(scripts, nextScripts);
3656
+ },
3077
3657
  addScript(name2, command) {
3078
- scripts[name2] = command;
3658
+ this.addScripts({ [name2]: command });
3079
3659
  },
3080
3660
  inject(location, code) {
3081
3661
  let entries = codeSnippets[location];
@@ -3095,71 +3675,61 @@ function generate(options) {
3095
3675
  }
3096
3676
  };
3097
3677
  if (isR3f) {
3098
- generateDrei(generator, clonedOptions.drei);
3099
- generateHandle(generator, clonedOptions.handle);
3100
- generateKoota(generator, clonedOptions.koota);
3101
- generateLeva(generator, clonedOptions.leva);
3102
- generateOffscreen(generator, clonedOptions.offscreen);
3103
- generatePostprocessing(generator, clonedOptions.postprocessing);
3104
- generateRapier(generator, clonedOptions.rapier);
3105
- generateUikit(generator, clonedOptions.uikit);
3106
- generateXr(generator, clonedOptions.xr);
3107
- generateZustand(generator, clonedOptions.zustand);
3108
- generateFiber(generator, clonedOptions.fiber);
3109
- generateTriplex(generator, clonedOptions.triplex);
3110
- generateViverse(generator, clonedOptions.viverse);
3678
+ planDrei(builder, planInput.features.drei);
3679
+ planHandle(builder, planInput.features.handle);
3680
+ planKoota(builder, planInput.features.koota);
3681
+ planLeva(builder, planInput.features.leva);
3682
+ planOffscreen(builder, planInput.features.offscreen);
3683
+ planPostprocessing(builder, planInput.features.postprocessing);
3684
+ planRapier(builder, planInput.features.rapier);
3685
+ planUikit(builder, planInput.features.uikit);
3686
+ planXr(builder, planInput.features.xr);
3687
+ planZustand(builder, planInput.features.zustand);
3688
+ planFiber(builder, planInput.features.fiber);
3689
+ planTriplex(builder, planInput.features.triplex);
3690
+ planViverse(builder, planInput.features.viverse);
3111
3691
  }
3112
3692
  if (!isLibrary) {
3113
- generateGithubPages(generator, clonedOptions.githubPages);
3693
+ planGithubPages(builder, planInput.features.githubPages);
3114
3694
  }
3115
3695
  if (isLibrary) {
3116
3696
  if (libraryBundler === "unbuild") {
3117
- generateUnbuild(generator);
3697
+ planUnbuild(builder);
3118
3698
  } else if (libraryBundler === "tsdown") {
3119
- generateTsdown(generator);
3699
+ planTsdown(builder);
3120
3700
  }
3121
- const packageManager2 = getPackageManagerName(clonedOptions.packageManager);
3122
- generator.addScript("release", `${packageManager2} run build && ${packageManager2} publish`);
3123
3701
  }
3124
- const testing = clonedOptions.testing ?? (isLibrary ? "vitest" : "none");
3702
+ const testing = planInput.testing.tool;
3125
3703
  if (testing === "vitest") {
3126
- generateVitest(generator);
3127
- }
3128
- const linter = clonedOptions.linter;
3129
- const formatter = clonedOptions.formatter;
3130
- if (linter === "eslint") {
3131
- generateEslint(generator);
3132
- generator.addVscodeSetting("biome.enabled", false);
3133
- generator.addVscodeSetting("oxc.enable", false);
3134
- } else if (linter === "oxlint") {
3135
- generateOxlint(generator);
3136
- generator.addVscodeSetting("eslint.enable", false);
3137
- generator.addVscodeSetting("biome.enabled", false);
3138
- } else if (linter === "biome") {
3139
- generateBiome(generator, {
3140
- linter: true,
3141
- formatter: formatter === "biome"
3704
+ planVitest(builder, planInput.testing);
3705
+ }
3706
+ const linter = planInput.linter.tool;
3707
+ const formatter = planInput.formatter.tool;
3708
+ if (planInput.linter.tool === "eslint") {
3709
+ planEslint(builder, planInput.linter);
3710
+ } else if (planInput.linter.tool === "oxlint") {
3711
+ planOxlint(builder, planInput.linter);
3712
+ } else if (planInput.linter.tool === "biome") {
3713
+ planBiome(builder, {
3714
+ linter: planInput.linter,
3715
+ formatter: planInput.formatter.tool === "biome" ? planInput.formatter : void 0
3142
3716
  });
3143
- generator.addVscodeSetting("eslint.enable", false);
3144
- generator.addVscodeSetting("oxc.enable", false);
3145
3717
  }
3146
- if (formatter === "prettier") {
3147
- generatePrettier(generator);
3148
- } else if (formatter === "oxfmt") {
3149
- generateOxfmt(generator);
3150
- } else if (formatter === "biome" && linter !== "biome") {
3151
- generateBiome(generator, { linter: false, formatter: true });
3152
- generator.addVscodeSetting("eslint.enable", false);
3153
- generator.addVscodeSetting("oxc.enable", false);
3718
+ if (planInput.formatter.tool === "prettier") {
3719
+ planPrettier(builder, planInput.formatter);
3720
+ } else if (planInput.formatter.tool === "oxfmt") {
3721
+ planOxfmt(builder, planInput.formatter);
3722
+ } else if (planInput.formatter.tool === "biome" && planInput.linter.tool !== "biome") {
3723
+ planBiome(builder, { formatter: planInput.formatter });
3154
3724
  }
3155
3725
  for (const { code, location } of clonedOptions.injections ?? []) {
3156
- generator.inject(location, code);
3726
+ builder.inject(location, code);
3157
3727
  }
3158
3728
  if (!isLibrary) {
3159
- files["vite.config.ts"] = generateViteConfig({ viteConfig, codeSnippets });
3729
+ files["vite.config.ts"] = renderViteConfig({ viteConfig, codeSnippets });
3160
3730
  }
3161
3731
  const packageManager = getPackageManagerName(options.packageManager);
3162
- files["README.md"] = generateReadme({
3732
+ files["README.md"] = renderReadme({
3163
3733
  name,
3164
3734
  baseTemplate,
3165
3735
  isLibrary,
@@ -3169,7 +3739,7 @@ function generate(options) {
3169
3739
  });
3170
3740
  Object.assign(
3171
3741
  files,
3172
- generateSourceFiles({
3742
+ renderSourceFiles({
3173
3743
  name,
3174
3744
  baseTemplate,
3175
3745
  language,
@@ -3181,7 +3751,7 @@ function generate(options) {
3181
3751
  if (testing === "vitest") {
3182
3752
  Object.assign(
3183
3753
  files,
3184
- generateTestFiles({
3754
+ renderTestFiles({
3185
3755
  baseTemplate,
3186
3756
  language,
3187
3757
  isLibrary
@@ -3190,7 +3760,7 @@ function generate(options) {
3190
3760
  }
3191
3761
  Object.assign(
3192
3762
  files,
3193
- generatePackageJson({
3763
+ renderPackageJson({
3194
3764
  name,
3195
3765
  language,
3196
3766
  isLibrary,
@@ -3202,25 +3772,54 @@ function generate(options) {
3202
3772
  workspaceDependencies: clonedOptions.workspaceDependencies
3203
3773
  }).files
3204
3774
  );
3205
- if (!isMonorepoPackage) {
3206
- Object.assign(files, generateVscodeFiles$1({ codeSnippets, vscodeSettings }));
3775
+ if (!isMonorepoPackage && ide === "vscode") {
3776
+ Object.assign(
3777
+ files,
3778
+ renderVscodeFiles$1({
3779
+ codeSnippets,
3780
+ vscodeSettings,
3781
+ linter,
3782
+ formatter,
3783
+ configStrategy: clonedOptions.configStrategy,
3784
+ isMonorepo: false,
3785
+ packageManager
3786
+ })
3787
+ );
3207
3788
  }
3208
3789
  if (!isMonorepoPackage) {
3209
- files[".gitignore"] = generateGitignore("standalone");
3210
- files[".gitattributes"] = { type: "text", content: GitAttributes };
3790
+ files[".editorconfig"] = renderEditorConfig();
3791
+ files[".gitignore"] = renderGitignore("standalone");
3792
+ files[".gitattributes"] = { type: "text", content: gitAttributesContent };
3211
3793
  }
3212
- if (!isMonorepoPackage && clonedOptions.aiPlatforms?.length) {
3213
- generateAiFiles(files, {
3794
+ if (!isMonorepoPackage && planInput.aiAgents.config.platforms.length > 0) {
3795
+ renderAiFiles(files, {
3214
3796
  name,
3215
3797
  packageManager: getPackageManagerName(clonedOptions.packageManager),
3216
3798
  linter: clonedOptions.linter ?? "oxlint",
3217
3799
  formatter: clonedOptions.formatter ?? "prettier",
3218
3800
  isMonorepo: false,
3219
3801
  configStrategy: clonedOptions.configStrategy,
3220
- platforms: clonedOptions.aiPlatforms
3802
+ hasTypecheck: language === "typescript",
3803
+ platforms: planInput.aiAgents.config.platforms
3221
3804
  });
3222
3805
  }
3223
- return files;
3806
+ return {
3807
+ files,
3808
+ dependencies,
3809
+ devDependencies,
3810
+ peerDependencies,
3811
+ scripts,
3812
+ vscodeSettings,
3813
+ vscodeExtensions: [...new Set(codeSnippets["vscode-extension-suggestion"] ?? [])],
3814
+ injections: Object.entries(codeSnippets).flatMap(
3815
+ ([location, entries]) => (entries ?? []).map((code) => ({
3816
+ location,
3817
+ code
3818
+ }))
3819
+ ),
3820
+ replacements,
3821
+ warnings: []
3822
+ };
3224
3823
  }
3225
3824
  function resolveDependencySemver(name, versions, options = {}) {
3226
3825
  if (options.version != null) {
@@ -3229,38 +3828,66 @@ function resolveDependencySemver(name, versions, options = {}) {
3229
3828
  return formatResolvedPackageVersion(versions, name, options.prefix);
3230
3829
  }
3231
3830
 
3831
+ async function planWorkspace(input) {
3832
+ const planInput = isWorkspacePlanInput(input) ? input : resolveWorkspacePlanInput(input);
3833
+ const resolvedInput = await resolveWorkspaceFacts(planInput);
3834
+ const { files } = renderMonorepo(workspacePlanInputToMonorepoParams(resolvedInput));
3835
+ return {
3836
+ files,
3837
+ dependencies: {},
3838
+ devDependencies: {},
3839
+ peerDependencies: {},
3840
+ scripts: {},
3841
+ vscodeSettings: {},
3842
+ vscodeExtensions: [],
3843
+ injections: [],
3844
+ replacements: [],
3845
+ warnings: []
3846
+ };
3847
+ }
3848
+
3232
3849
  exports.AI_PLATFORM_HINTS = AI_PLATFORM_HINTS;
3233
3850
  exports.AI_PLATFORM_LABELS = AI_PLATFORM_LABELS;
3234
3851
  exports.ALL_AI_PLATFORMS = ALL_AI_PLATFORMS;
3235
3852
  exports.detectTooling = detectTooling;
3236
3853
  exports.formatResolvedPackageVersion = formatResolvedPackageVersion;
3237
- exports.generate = generate;
3238
- exports.generateAiFiles = generateAiFiles;
3239
- exports.generateEslintConfigPackage = generateEslintConfigPackage;
3240
- exports.generateGitignore = generateGitignore;
3241
- exports.generateMonorepo = generateMonorepo;
3242
- exports.generateOxfmtConfigPackage = generateOxfmtConfigPackage;
3243
- exports.generateOxlintConfigPackage = generateOxlintConfigPackage;
3244
- exports.generatePrettierConfigPackage = generatePrettierConfigPackage;
3245
3854
  exports.generateRandomName = generateRandomName;
3246
- exports.generateTypescriptConfigPackage = generateTypescriptConfigPackage;
3247
- exports.generateVscodeFiles = generateVscodeFiles;
3248
3855
  exports.getBaseTemplate = getBaseTemplate;
3249
3856
  exports.getEngineName = getEngineName;
3250
3857
  exports.getLanguageFromTemplate = getLanguageFromTemplate;
3251
3858
  exports.getLatestNodeVersion = getLatestNodeVersion;
3252
3859
  exports.getLatestNpmCliVersion = getLatestNpmCliVersion;
3860
+ exports.getLatestNpmMajorVersion = getLatestNpmMajorVersion;
3253
3861
  exports.getLatestNpmVersion = getLatestNpmVersion;
3254
3862
  exports.getLatestPnpmVersion = getLatestPnpmVersion;
3255
3863
  exports.getLatestYarnVersion = getLatestYarnVersion;
3256
3864
  exports.getPackageManagerName = getPackageManagerName;
3257
3865
  exports.getResolvedPackageVersion = getResolvedPackageVersion;
3258
- exports.monorepo = monorepo;
3866
+ exports.merge = merge;
3867
+ exports.mergePackageJsonScripts = mergePackageJsonScripts;
3868
+ exports.packageJsonScripts = packageJsonScripts;
3259
3869
  exports.parseEngine = parseEngine;
3260
3870
  exports.parsePackageManager = parsePackageManager;
3261
3871
  exports.parseWorkspaceYamlContent = parseWorkspaceYamlContent;
3262
- exports.resolveEngine = resolveEngine;
3872
+ exports.planProject = planProject;
3873
+ exports.planWorkspace = planWorkspace;
3874
+ exports.projectPlanInputToOptions = projectPlanInputToOptions;
3875
+ exports.renderAiFiles = renderAiFiles;
3876
+ exports.renderEditorConfig = renderEditorConfig;
3877
+ exports.renderEslintConfigPackage = renderEslintConfigPackage;
3878
+ exports.renderGitignore = renderGitignore;
3879
+ exports.renderOxfmtConfigPackage = renderOxfmtConfigPackage;
3880
+ exports.renderOxlintConfig = renderOxlintConfig;
3881
+ exports.renderOxlintConfigPackage = renderOxlintConfigPackage;
3882
+ exports.renderPrettierConfigPackage = renderPrettierConfigPackage;
3883
+ exports.renderTypescriptConfigPackage = renderTypescriptConfigPackage;
3884
+ exports.renderVscodeFiles = renderVscodeFiles;
3885
+ exports.renderVscodeFiles$1 = renderVscodeFiles$1;
3886
+ exports.resolveDefaultPackageJsonScripts = resolveDefaultPackageJsonScripts;
3263
3887
  exports.resolveMonorepoRootPackageVersions = resolveMonorepoRootPackageVersions;
3264
- exports.resolvePackageManager = resolvePackageManager;
3265
- exports.resolveProjectPackageVersions = resolveProjectPackageVersions;
3888
+ exports.resolveProjectPlanInput = resolveProjectPlanInput;
3889
+ exports.resolveWorkspacePlanInput = resolveWorkspacePlanInput;
3890
+ exports.toPrettierIgnoreContent = toPrettierIgnoreContent;
3891
+ exports.unique = unique;
3266
3892
  exports.validatePackageName = validatePackageName;
3893
+ exports.workspacePlanInputToMonorepoParams = workspacePlanInputToMonorepoParams;