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