create-krispya 0.9.0 → 0.11.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,59 +178,49 @@ 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;
205
- }
206
- }
207
- function compareNumericSemver(a, b) {
208
- const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
209
- const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
210
- const maxLength = Math.max(aParts.length, bParts.length);
211
- for (let index = 0; index < maxLength; index += 1) {
212
- const difference = (aParts[index] ?? 0) - (bParts[index] ?? 0);
213
- if (difference !== 0) {
214
- return difference;
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);
215
186
  }
216
187
  }
217
- return 0;
188
+ return Array.from(set);
218
189
  }
219
- async function getLatestNpmMajorVersion(packageName, majorVersion, fallback) {
220
- try {
221
- const response = await fetch(`https://registry.npmjs.org/${packageName}`);
222
- const data = await response.json();
223
- const latestMatchingVersion = Object.keys(data.versions ?? {}).filter((version) => version.split(".")[0] === majorVersion).sort((a, b) => compareNumericSemver(b, a))[0];
224
- return latestMatchingVersion ?? fallback;
225
- } catch {
226
- return fallback;
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(`Cannot merge "${modificationLabel}" modification into target "${targetLabel}"`);
227
196
  }
228
- }
229
- async function getLatestPnpmVersion() {
230
- return getLatestNpmVersion("pnpm", "10.11.0");
231
- }
232
- async function getLatestYarnVersion() {
233
- return getLatestNpmVersion("yarn", "4.6.0");
234
- }
235
- async function getLatestNpmCliVersion() {
236
- return getLatestNpmVersion("npm", "11.0.0");
237
- }
238
- async function getLatestNodeVersion() {
239
- try {
240
- const response = await fetch("https://nodejs.org/dist/index.json");
241
- const data = await response.json();
242
- const latestVersion = data[0];
243
- if (latestVersion) {
244
- return latestVersion.version.replace(/^v/, "");
197
+ if (target == null) {
198
+ return modification;
199
+ }
200
+ if (Array.isArray(target)) {
201
+ if (!Array.isArray(modification)) {
202
+ throw new Error(
203
+ `Cannot merge non-array modification "${modificationLabel}" into array target "${targetLabel}"`
204
+ );
245
205
  }
246
- return "25.0.0";
247
- } catch {
248
- return "25.0.0";
206
+ return [...target, ...modification];
207
+ }
208
+ if (typeof target === "object") {
209
+ if (typeof modification !== "object") {
210
+ throw new Error(
211
+ `Cannot merge non-object modification "${modificationLabel}" into object target "${targetLabel}"`
212
+ );
213
+ }
214
+ const result = { ...target };
215
+ for (const modificationKey in modification) {
216
+ result[modificationKey] = merge(target[modificationKey], modification[modificationKey]);
217
+ }
218
+ return result;
249
219
  }
220
+ console.warn(`target "${targetLabel}" is overwritten with modification "${modificationLabel}"`);
221
+ return modification;
250
222
  }
223
+
251
224
  function validateNameSegment(segment, label) {
252
225
  if (!segment.length) {
253
226
  return `${label} is required`;
@@ -291,27 +264,83 @@ function validatePackageName(name) {
291
264
  }
292
265
  return validateNameSegment(name, "Package name");
293
266
  }
294
- function parseWorkspaceYamlContent(content) {
295
- const directories = [];
296
- let inPackagesSection = false;
297
- for (const line of content.split("\n")) {
298
- const trimmed = line.trim();
299
- if (trimmed === "packages:") {
300
- inPackagesSection = true;
301
- continue;
302
- }
303
- if (inPackagesSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ") && !trimmed.startsWith("-")) {
304
- break;
305
- }
306
- if (inPackagesSection && trimmed.startsWith("-")) {
307
- const entry = trimmed.slice(1).trim().replace(/^["']|["']$/g, "").replace(/^\.\//, "").replace(/\/\*.*$/, "");
308
- if (entry && !entry.startsWith(".")) {
309
- directories.push(entry);
310
- }
311
- }
312
- }
313
- return directories;
267
+
268
+ function generateRandomName() {
269
+ const adjectives = [
270
+ "red",
271
+ "blue",
272
+ "green",
273
+ "yellow",
274
+ "purple",
275
+ "orange",
276
+ "pink",
277
+ "black",
278
+ "white",
279
+ "tiny",
280
+ "big",
281
+ "small",
282
+ "large",
283
+ "huge",
284
+ "giant",
285
+ "mini",
286
+ "mega",
287
+ "super",
288
+ "happy",
289
+ "sad",
290
+ "angry",
291
+ "calm",
292
+ "quiet",
293
+ "loud",
294
+ "silent",
295
+ "noisy",
296
+ "shiny",
297
+ "dull",
298
+ "bright",
299
+ "dark",
300
+ "fuzzy",
301
+ "smooth",
302
+ "rough",
303
+ "soft"
304
+ ];
305
+ const nouns = [
306
+ "apple",
307
+ "banana",
308
+ "cherry",
309
+ "date",
310
+ "elderberry",
311
+ "fig",
312
+ "grape",
313
+ "honeydew",
314
+ "cat",
315
+ "dog",
316
+ "elephant",
317
+ "fox",
318
+ "giraffe",
319
+ "horse",
320
+ "iguana",
321
+ "jaguar",
322
+ "mountain",
323
+ "river",
324
+ "ocean",
325
+ "desert",
326
+ "forest",
327
+ "jungle",
328
+ "meadow",
329
+ "valley",
330
+ "star",
331
+ "moon",
332
+ "sun",
333
+ "planet",
334
+ "comet",
335
+ "asteroid",
336
+ "galaxy",
337
+ "universe"
338
+ ];
339
+ const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
340
+ const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
341
+ return `${randomAdjective}-${randomNoun}`;
314
342
  }
343
+
315
344
  async function pathExists(path) {
316
345
  try {
317
346
  await access(path, constants.F_OK);
@@ -387,81 +416,82 @@ async function detectTooling(root) {
387
416
  } catch {
388
417
  return { linter: void 0, formatter: void 0 };
389
418
  }
390
- }
391
- function generateRandomName() {
392
- const adjectives = [
393
- "red",
394
- "blue",
395
- "green",
396
- "yellow",
397
- "purple",
398
- "orange",
399
- "pink",
400
- "black",
401
- "white",
402
- "tiny",
403
- "big",
404
- "small",
405
- "large",
406
- "huge",
407
- "giant",
408
- "mini",
409
- "mega",
410
- "super",
411
- "happy",
412
- "sad",
413
- "angry",
414
- "calm",
415
- "quiet",
416
- "loud",
417
- "silent",
418
- "noisy",
419
- "shiny",
420
- "dull",
421
- "bright",
422
- "dark",
423
- "fuzzy",
424
- "smooth",
425
- "rough",
426
- "soft"
427
- ];
428
- const nouns = [
429
- "apple",
430
- "banana",
431
- "cherry",
432
- "date",
433
- "elderberry",
434
- "fig",
435
- "grape",
436
- "honeydew",
437
- "cat",
438
- "dog",
439
- "elephant",
440
- "fox",
441
- "giraffe",
442
- "horse",
443
- "iguana",
444
- "jaguar",
445
- "mountain",
446
- "river",
447
- "ocean",
448
- "desert",
449
- "forest",
450
- "jungle",
451
- "meadow",
452
- "valley",
453
- "star",
454
- "moon",
455
- "sun",
456
- "planet",
457
- "comet",
458
- "asteroid",
459
- "galaxy",
460
- "universe"
461
- ];
462
- const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
463
- const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
464
- return `${randomAdjective}-${randomNoun}`;
419
+ }
420
+
421
+ async function getLatestNpmVersion(packageName, fallback) {
422
+ try {
423
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
424
+ const data = await response.json();
425
+ return data.version;
426
+ } catch {
427
+ return fallback;
428
+ }
429
+ }
430
+ function compareNumericSemver(a, b) {
431
+ const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
432
+ const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
433
+ const maxLength = Math.max(aParts.length, bParts.length);
434
+ for (let index = 0; index < maxLength; index += 1) {
435
+ const difference = (aParts[index] ?? 0) - (bParts[index] ?? 0);
436
+ if (difference !== 0) {
437
+ return difference;
438
+ }
439
+ }
440
+ return 0;
441
+ }
442
+ async function getLatestNpmMajorVersion(packageName, majorVersion, fallback) {
443
+ try {
444
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`);
445
+ const data = await response.json();
446
+ const latestMatchingVersion = Object.keys(data.versions ?? {}).filter((version) => version.split(".")[0] === majorVersion).sort((a, b) => compareNumericSemver(b, a))[0];
447
+ return latestMatchingVersion ?? fallback;
448
+ } catch {
449
+ return fallback;
450
+ }
451
+ }
452
+ async function getLatestPnpmVersion() {
453
+ return getLatestNpmVersion("pnpm", "10.11.0");
454
+ }
455
+ async function getLatestYarnVersion() {
456
+ return getLatestNpmVersion("yarn", "4.6.0");
457
+ }
458
+ async function getLatestNpmCliVersion() {
459
+ return getLatestNpmVersion("npm", "11.0.0");
460
+ }
461
+ async function getLatestNodeVersion() {
462
+ try {
463
+ const response = await fetch("https://nodejs.org/dist/index.json");
464
+ const data = await response.json();
465
+ const latestVersion = data[0];
466
+ if (latestVersion) {
467
+ return latestVersion.version.replace(/^v/, "");
468
+ }
469
+ return "25.0.0";
470
+ } catch {
471
+ return "25.0.0";
472
+ }
473
+ }
474
+
475
+ function parseWorkspaceYamlContent(content) {
476
+ const directories = [];
477
+ let inPackagesSection = false;
478
+ for (const line of content.split("\n")) {
479
+ const trimmed = line.trim();
480
+ if (trimmed === "packages:") {
481
+ inPackagesSection = true;
482
+ continue;
483
+ }
484
+ if (inPackagesSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ") && !trimmed.startsWith("-")) {
485
+ break;
486
+ }
487
+ if (inPackagesSection && trimmed.startsWith("-")) {
488
+ const entry = trimmed.slice(1).trim().replace(/^["']|["']$/g, "").replace(/^\.\//, "").replace(/\/\*.*$/, "");
489
+ if (entry && !entry.startsWith(".")) {
490
+ directories.push(entry);
491
+ }
492
+ }
493
+ }
494
+ return directories;
465
495
  }
466
496
 
467
497
  const PACKAGE_VERSION_DEFINITIONS = {
@@ -489,6 +519,7 @@ const PACKAGE_VERSION_DEFINITIONS = {
489
519
  leva: { fallbackVersion: "0.10.0" },
490
520
  oxfmt: { fallbackVersion: "0.21.0" },
491
521
  oxlint: { fallbackVersion: "1.36.0" },
522
+ "oxlint-tsgolint": { fallbackVersion: "0.22.1" },
492
523
  prettier: { fallbackVersion: "3.4.2" },
493
524
  react: { fallbackVersion: "19.0.0" },
494
525
  "react-dom": { fallbackVersion: "19.0.0" },
@@ -645,6 +676,9 @@ async function resolveMonorepoRootPackageVersions(params) {
645
676
  const packageNames = /* @__PURE__ */ new Set();
646
677
  const explicitVersions = new Set(Object.keys(params.versions ?? {}));
647
678
  addPackageName(packageNames, explicitVersions, getLinterPackage(params.linter));
679
+ if (params.linter === "oxlint") {
680
+ addPackageName(packageNames, explicitVersions, "oxlint-tsgolint");
681
+ }
648
682
  if (params.formatter !== "biome" || params.linter !== "biome") {
649
683
  addPackageName(packageNames, explicitVersions, getFormatterPackage(params.formatter));
650
684
  }
@@ -754,6 +788,9 @@ function collectProjectPackageNames(options) {
754
788
  } else if (linter === "oxlint") {
755
789
  if (!inWorkspace) {
756
790
  addPackageName(packageNames, explicitVersions, "oxlint");
791
+ if (isTypescript) {
792
+ addPackageName(packageNames, explicitVersions, "oxlint-tsgolint");
793
+ }
757
794
  }
758
795
  } else if (linter === "biome") {
759
796
  addPackageName(packageNames, explicitVersions, "@biomejs/biome");
@@ -788,7 +825,7 @@ function isEnabledOption(option) {
788
825
  return option != null && option !== false;
789
826
  }
790
827
 
791
- function generateTypescriptConfig(baseTemplateOrParams) {
828
+ function renderTypescriptConfig(baseTemplateOrParams) {
792
829
  const params = typeof baseTemplateOrParams === "string" ? { baseTemplate: baseTemplateOrParams } : baseTemplateOrParams;
793
830
  const {
794
831
  baseTemplate,
@@ -835,10 +872,7 @@ function generateTypescriptConfig(baseTemplateOrParams) {
835
872
  const tsConfig = {
836
873
  $schema: "https://json.schemastore.org/tsconfig",
837
874
  files: [],
838
- references: [
839
- { path: "./.config/tsconfig.app.json" },
840
- { path: "./.config/tsconfig.node.json" }
841
- ]
875
+ references: [{ path: "./.config/tsconfig.app.json" }, { path: "./.config/tsconfig.node.json" }]
842
876
  };
843
877
  files["tsconfig.json"] = {
844
878
  type: "text",
@@ -858,6 +892,7 @@ function generateTypescriptConfig(baseTemplateOrParams) {
858
892
  composite: true,
859
893
  rewriteRelativeImportExtensions: true,
860
894
  erasableSyntaxOnly: true,
895
+ noEmit: true,
861
896
  ...isReact || isR3f ? { jsx: "react-jsx" } : {}
862
897
  },
863
898
  include: ["../src", "../tests"]
@@ -879,7 +914,8 @@ function generateTypescriptConfig(baseTemplateOrParams) {
879
914
  skipLibCheck: true,
880
915
  composite: true,
881
916
  rewriteRelativeImportExtensions: true,
882
- erasableSyntaxOnly: true
917
+ erasableSyntaxOnly: true,
918
+ noEmit: true
883
919
  },
884
920
  include: ["../*.config.ts", "./*.ts"]
885
921
  };
@@ -911,6 +947,7 @@ function generateTypescriptConfig(baseTemplateOrParams) {
911
947
  composite: true,
912
948
  rewriteRelativeImportExtensions: true,
913
949
  erasableSyntaxOnly: true,
950
+ noEmit: true,
914
951
  ...isReact || isR3f ? { jsx: "react-jsx" } : {}
915
952
  },
916
953
  include: ["src", "tests"]
@@ -932,7 +969,8 @@ function generateTypescriptConfig(baseTemplateOrParams) {
932
969
  skipLibCheck: true,
933
970
  composite: true,
934
971
  rewriteRelativeImportExtensions: true,
935
- erasableSyntaxOnly: true
972
+ erasableSyntaxOnly: true,
973
+ noEmit: true
936
974
  },
937
975
  include: ["*.config.ts"]
938
976
  };
@@ -944,8 +982,108 @@ function generateTypescriptConfig(baseTemplateOrParams) {
944
982
  return { files, devDependencies };
945
983
  }
946
984
 
985
+ const packageJsonScripts = {
986
+ appBase: {
987
+ dev: "vite",
988
+ build: "vite build"
989
+ },
990
+ typescript: {
991
+ typecheck: "tsc --build --noEmit",
992
+ "typecheck:watch": "tsc --build --watch"
993
+ },
994
+ test: {
995
+ vitest: {
996
+ test: "vitest"
997
+ }
998
+ },
999
+ build: {
1000
+ unbuild(configPath) {
1001
+ return {
1002
+ build: configPath == null ? "unbuild" : `unbuild --config ${configPath}`
1003
+ };
1004
+ },
1005
+ tsdown: {
1006
+ build: "tsdown"
1007
+ }
1008
+ },
1009
+ lint: {
1010
+ oxlint(configPath) {
1011
+ return {
1012
+ lint: configPath == null ? "oxlint" : `oxlint -c ${configPath}`
1013
+ };
1014
+ },
1015
+ eslint(configPath) {
1016
+ return {
1017
+ lint: configPath == null ? "eslint ." : `eslint --config ${configPath} .`
1018
+ };
1019
+ },
1020
+ biome(configPath) {
1021
+ return {
1022
+ lint: configPath == null ? "biome lint ." : `biome lint --config-path ${configPath} .`
1023
+ };
1024
+ }
1025
+ },
1026
+ format: {
1027
+ prettier(configPath, ignorePath) {
1028
+ const configFlag = configPath == null ? "" : ` --config ${configPath}`;
1029
+ const ignoreFlag = ignorePath == null ? "" : ` --ignore-path ${ignorePath}`;
1030
+ return {
1031
+ format: `prettier${configFlag}${ignoreFlag} --write .`
1032
+ };
1033
+ },
1034
+ oxfmt(configPath) {
1035
+ return {
1036
+ format: `oxfmt -c ${configPath} --write .`
1037
+ };
1038
+ },
1039
+ biome(configPath) {
1040
+ return {
1041
+ format: configPath == null ? "biome format --write ." : `biome format --config-path ${configPath} --write .`
1042
+ };
1043
+ }
1044
+ },
1045
+ release(packageManagerName) {
1046
+ return {
1047
+ release: `${packageManagerName} run build && ${packageManagerName} publish`
1048
+ };
1049
+ },
1050
+ monorepoRoot(linter, formatter) {
1051
+ return mergePackageJsonScripts(
1052
+ {
1053
+ dev: "pnpm --filter './apps/*' run dev",
1054
+ build: "pnpm --filter './packages/*' run build && pnpm --filter './apps/*' run build",
1055
+ test: "pnpm -r run test"
1056
+ },
1057
+ linter === "oxlint" ? {
1058
+ lint: "oxlint ."
1059
+ } : linter === "biome" ? {
1060
+ lint: "biome check ."
1061
+ } : {
1062
+ lint: "eslint ."
1063
+ },
1064
+ formatter === "oxfmt" ? {
1065
+ format: "oxfmt -c .config/oxfmt/base.json ."
1066
+ } : formatter === "biome" ? {
1067
+ format: "biome format . --write"
1068
+ } : {
1069
+ format: "prettier --config .config/prettier/base.json --ignore-path .config/prettier/prettierignore --write ."
1070
+ }
1071
+ );
1072
+ }
1073
+ };
1074
+ function mergePackageJsonScripts(...scriptSets) {
1075
+ return Object.assign({}, ...scriptSets.filter((scriptSet) => scriptSet != null));
1076
+ }
1077
+ function resolveDefaultPackageJsonScripts(params) {
1078
+ return mergePackageJsonScripts(
1079
+ params.isLibrary ? void 0 : packageJsonScripts.appBase,
1080
+ params.language === "typescript" ? packageJsonScripts.typescript : void 0,
1081
+ params.isLibrary ? packageJsonScripts.release(params.packageManagerName) : void 0
1082
+ );
1083
+ }
1084
+
947
1085
  const DEFAULT_LIBRARY_VERSION = "0.1.0";
948
- function generatePackageJson(params) {
1086
+ function renderPackageJson(params) {
949
1087
  const {
950
1088
  name,
951
1089
  language,
@@ -953,13 +1091,20 @@ function generatePackageJson(params) {
953
1091
  dependencies,
954
1092
  devDependencies,
955
1093
  peerDependencies,
956
- scripts,
957
1094
  options,
958
1095
  workspaceDependencies
959
1096
  } = params;
960
1097
  const files = {};
961
1098
  const packageManager = getPackageManagerSpec(options.packageManager);
962
1099
  const isPnpm = packageManager.name === "pnpm";
1100
+ const resolvedScripts = mergePackageJsonScripts(
1101
+ resolveDefaultPackageJsonScripts({
1102
+ language,
1103
+ isLibrary,
1104
+ packageManagerName: packageManager.name
1105
+ }),
1106
+ params.scripts
1107
+ );
963
1108
  const packageJson = {
964
1109
  name,
965
1110
  description: "Built with \u{1F339} create-krispya",
@@ -991,12 +1136,9 @@ function generatePackageJson(params) {
991
1136
  const allDevDependencies = { ...devDependencies };
992
1137
  const engine = getEngineSpec(options.engine);
993
1138
  if (getEngineName(engine) === "node" && engine.version) {
994
- allDevDependencies["@types/node"] ??= formatNodeTypesVersion(
995
- options.versions,
996
- options.engine
997
- );
1139
+ allDevDependencies["@types/node"] ??= formatNodeTypesVersion(options.versions, options.engine);
998
1140
  }
999
- packageJson.scripts = scripts;
1141
+ packageJson.scripts = resolvedScripts;
1000
1142
  packageJson.dependencies = sortKeys(allDependencies);
1001
1143
  if (Object.keys(allDevDependencies).length > 0) {
1002
1144
  packageJson.devDependencies = sortKeys(allDevDependencies);
@@ -1039,7 +1181,7 @@ function generatePackageJson(params) {
1039
1181
  return { files };
1040
1182
  }
1041
1183
 
1042
- function generateReadme(params) {
1184
+ function renderReadme(params) {
1043
1185
  const { name, baseTemplate, isLibrary, libraryBundler, packageManager, codeSnippets } = params;
1044
1186
  const isVanilla = baseTemplate === "vanilla";
1045
1187
  const isReact = baseTemplate === "react";
@@ -1095,13 +1237,14 @@ function generateReadme(params) {
1095
1237
  } else if (isReact) {
1096
1238
  architectureDesc = [
1097
1239
  `- \`src/app.${jsxExt}\` defines the main application component`,
1098
- `- \`src/index.${jsxExt}\` renders the React app into the DOM`,
1240
+ `- \`src/main.${jsxExt}\` renders the React app into the DOM`,
1099
1241
  `- \`tests/\` contains your test files`,
1100
1242
  `- Static assets can be placed in the \`public\` folder`
1101
1243
  ];
1102
1244
  } else {
1103
1245
  architectureDesc = [
1104
- `- \`app.${jsxExt}\` defines the main application component containing your 3D content`,
1246
+ `- \`src/app.${jsxExt}\` defines the main application component containing your 3D content`,
1247
+ `- \`src/main.${jsxExt}\` renders the React app into the DOM`,
1105
1248
  `- Modify the content inside the \`<Canvas>\` component to change what is visible on screen`,
1106
1249
  `- \`tests/\` contains your test files`,
1107
1250
  `- Static assets can be placed in the \`public\` folder`
@@ -1132,7 +1275,67 @@ function generateReadme(params) {
1132
1275
  return { type: "text", content };
1133
1276
  }
1134
1277
 
1135
- function generateSourceFiles(params) {
1278
+ const htmlContent = `<!DOCTYPE html>
1279
+ <html lang="en">
1280
+ <head>
1281
+ <meta charset="UTF-8">
1282
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1283
+ <title>$title</title>
1284
+ </head>
1285
+ <body>
1286
+ <div id="root"></div>
1287
+ <script type="module" src="$indexPath"><\/script>
1288
+ </body>
1289
+ </html>`;
1290
+ const viteHtmlContent = `<!DOCTYPE html>
1291
+ <html lang="en">
1292
+ <head>
1293
+ <meta charset="UTF-8">
1294
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1295
+ <title>$title</title>
1296
+ </head>
1297
+ <body>
1298
+ <div id="app"></div>
1299
+ <script type="module" src="$indexPath"><\/script>
1300
+ </body>
1301
+ </html>`;
1302
+ const indexContent = `import { StrictMode } from 'react'
1303
+ import { createRoot } from 'react-dom/client'
1304
+ import './index.css'
1305
+ import { App } from './app.js'
1306
+
1307
+ createRoot(document.getElementById('root')!).render(
1308
+ <StrictMode>
1309
+ <App />
1310
+ </StrictMode>,
1311
+ )`;
1312
+ const viteIndexContent = `import './index.css'
1313
+
1314
+ document.querySelector('#app')!.innerHTML = \`
1315
+ <h1>Hello Vite!</h1>
1316
+ <p>Edit src/main.ts and save to see HMR in action.</p>
1317
+ \``;
1318
+ const viteStyleContent = `:root {
1319
+ font-family:
1320
+ system-ui,
1321
+ -apple-system,
1322
+ sans-serif;
1323
+ line-height: 1.5;
1324
+ font-weight: 400;
1325
+ }
1326
+
1327
+ *,
1328
+ *::before,
1329
+ *::after {
1330
+ box-sizing: border-box;
1331
+ }
1332
+
1333
+ body {
1334
+ margin: 0;
1335
+ }`;
1336
+ const viteEnvContent = `/// <reference types="vite/client" />`;
1337
+
1338
+ function renderSourceFiles(params) {
1136
1339
  const { name, baseTemplate, language, isLibrary, codeSnippets, replacements } = params;
1137
1340
  const files = {};
1138
1341
  const ext = language === "typescript" ? "ts" : "js";
@@ -1140,6 +1343,9 @@ function generateSourceFiles(params) {
1140
1343
  const isVanilla = baseTemplate === "vanilla";
1141
1344
  const isReact = baseTemplate === "react";
1142
1345
  const isR3f = baseTemplate === "r3f";
1346
+ if (!isLibrary && language === "typescript") {
1347
+ files["src/vite-env.d.ts"] = { type: "text", content: viteEnvContent };
1348
+ }
1143
1349
  if (isLibrary) {
1144
1350
  const libExt = isReact || isR3f ? jsxExt : ext;
1145
1351
  let libContent;
@@ -1172,19 +1378,20 @@ function generateSourceFiles(params) {
1172
1378
  }
1173
1379
  files[`src/index.${libExt}`] = { type: "text", content: libContent };
1174
1380
  } else if (isVanilla) {
1175
- files[`src/main.${ext}`] = { type: "text", content: ViteIndexContent };
1176
- files["src/style.css"] = { type: "text", content: ViteStyleContent };
1177
- const indexHtml = ViteHtmlContent.replace("$indexPath", `./src/main.${ext}`).replace(
1178
- "$title",
1179
- name
1180
- );
1381
+ files[`src/main.${ext}`] = { type: "text", content: viteIndexContent };
1382
+ files["src/index.css"] = { type: "text", content: viteStyleContent };
1383
+ const indexHtml = viteHtmlContent.replace("$indexPath", `./src/main.${ext}`).replace("$title", name);
1181
1384
  files["index.html"] = { type: "text", content: indexHtml };
1182
1385
  } else {
1183
- files[`src/index.tsx`] = { type: "text", content: IndexContent };
1184
- const indexHtml = HtmlContent.replace(
1185
- "$indexPath",
1186
- language === "javascript" ? "./src/index.jsx" : "./src/index.tsx"
1187
- ).replace("$title", name);
1386
+ files[`src/main.${jsxExt}`] = {
1387
+ type: "text",
1388
+ content: language === "typescript" ? indexContent : indexContent.replace(
1389
+ "document.getElementById('root')!",
1390
+ "document.getElementById('root')"
1391
+ )
1392
+ };
1393
+ files["src/index.css"] = { type: "text", content: viteStyleContent };
1394
+ const indexHtml = htmlContent.replace("$indexPath", `./src/main.${jsxExt}`).replace("$title", name);
1188
1395
  files["index.html"] = { type: "text", content: indexHtml };
1189
1396
  codeSnippets["dom-end"]?.reverse();
1190
1397
  codeSnippets["global-end"]?.reverse();
@@ -1226,12 +1433,12 @@ function generateSourceFiles(params) {
1226
1433
  for (const { search, replace } of replacements) {
1227
1434
  appCode = appCode.replace(search, replace);
1228
1435
  }
1229
- files[`src/app.tsx`] = { type: "text", content: appCode };
1436
+ files[`src/app.${jsxExt}`] = { type: "text", content: appCode };
1230
1437
  }
1231
1438
  return files;
1232
1439
  }
1233
1440
 
1234
- function generateTestFiles(params) {
1441
+ function renderTestFiles(params) {
1235
1442
  const { baseTemplate, language, isLibrary } = params;
1236
1443
  const files = {};
1237
1444
  const ext = language === "typescript" ? "ts" : "js";
@@ -1338,21 +1545,184 @@ const COMMON_GITIGNORE_LINES = [
1338
1545
  "*.tsbuildinfo",
1339
1546
  ".env",
1340
1547
  ".env.*",
1341
- "!.env.example"
1548
+ "!.env.example",
1549
+ ".pnpm-store"
1342
1550
  ];
1343
- function generateGitignore(variant) {
1551
+ function renderGitignore(variant) {
1344
1552
  const lines = variant === "workspace-root" ? [...COMMON_GITIGNORE_LINES, ".DS_Store"] : COMMON_GITIGNORE_LINES;
1345
1553
  return {
1346
1554
  type: "text",
1347
1555
  content: lines.join("\n")
1348
1556
  };
1349
1557
  }
1350
-
1351
- function generateVscodeFiles$1(params) {
1352
- const { codeSnippets, vscodeSettings } = params;
1558
+
1559
+ const defaultFormatterMetaConfig = {
1560
+ printWidth: 102,
1561
+ tabWidth: 2,
1562
+ useTabs: false,
1563
+ semi: true,
1564
+ singleQuote: true,
1565
+ trailingComma: "es5",
1566
+ bracketSpacing: true,
1567
+ arrowParens: "always",
1568
+ ignorePatterns: [
1569
+ "package-lock.json",
1570
+ "npm-shrinkwrap.json",
1571
+ "pnpm-lock.yaml",
1572
+ "pnpm-lock.json",
1573
+ "yarn.lock",
1574
+ "bun.lock",
1575
+ "bun.lockb"
1576
+ ]
1577
+ };
1578
+
1579
+ function renderEditorConfig(config = defaultFormatterMetaConfig) {
1580
+ const indentStyle = config.useTabs ? "tab" : "space";
1581
+ const indentSize = config.useTabs ? "tab" : String(config.tabWidth);
1582
+ return {
1583
+ type: "text",
1584
+ content: [
1585
+ "root = true",
1586
+ "",
1587
+ "[*]",
1588
+ "charset = utf-8",
1589
+ "end_of_line = lf",
1590
+ "insert_final_newline = true",
1591
+ `indent_style = ${indentStyle}`,
1592
+ `indent_size = ${indentSize}`,
1593
+ `tab_width = ${config.tabWidth}`,
1594
+ `max_line_length = ${config.printWidth}`
1595
+ ].join("\n")
1596
+ };
1597
+ }
1598
+ function renderVscodeEditorSettings(config = defaultFormatterMetaConfig) {
1599
+ return {
1600
+ "editor.detectIndentation": false,
1601
+ "editor.insertSpaces": !config.useTabs,
1602
+ "editor.tabSize": config.tabWidth,
1603
+ "files.eol": "\n",
1604
+ "files.insertFinalNewline": true
1605
+ };
1606
+ }
1607
+
1608
+ const DEFAULT_VSCODE_SETTINGS = {
1609
+ ...renderVscodeEditorSettings(),
1610
+ "explorer.fileNesting.enabled": true,
1611
+ "explorer.fileNesting.expand": false,
1612
+ "explorer.fileNesting.patterns": {
1613
+ ".gitignore": ".gitattributes",
1614
+ "AGENTS.md": "CLAUDE.md"
1615
+ }
1616
+ };
1617
+ const OXFMT_LANGUAGE_SETTINGS = {
1618
+ "[json]": {
1619
+ "editor.defaultFormatter": "vscode.json-language-features"
1620
+ },
1621
+ "[jsonc]": {
1622
+ "editor.defaultFormatter": "vscode.json-language-features"
1623
+ },
1624
+ "[markdown]": {
1625
+ "editor.defaultFormatter": "vscode.markdown-language-features"
1626
+ },
1627
+ "[yaml]": {
1628
+ "editor.defaultFormatter": "redhat.vscode-yaml"
1629
+ }
1630
+ };
1631
+ function resolvePackageJsonNestedFiles(packageManager) {
1632
+ if (packageManager === "pnpm") {
1633
+ return ["pnpm-lock.yaml", "pnpm-workspace.yaml"];
1634
+ }
1635
+ if (packageManager === "npm") {
1636
+ return ["package-lock.json", "npm-shrinkwrap.json"];
1637
+ }
1638
+ if (packageManager === "yarn") {
1639
+ return ["yarn.lock"];
1640
+ }
1641
+ return [];
1642
+ }
1643
+ function resolveVscodeRecommendations(linter, formatter) {
1644
+ const recommendations = [];
1645
+ if (linter === "oxlint" || formatter === "oxfmt") {
1646
+ recommendations.push("oxc.oxc-vscode");
1647
+ }
1648
+ if (linter === "eslint") {
1649
+ recommendations.push("dbaeumer.vscode-eslint");
1650
+ }
1651
+ if (linter === "biome" || formatter === "biome") {
1652
+ recommendations.push("biomejs.biome");
1653
+ }
1654
+ if (formatter === "prettier") {
1655
+ recommendations.push("esbenp.prettier-vscode");
1656
+ }
1657
+ return recommendations;
1658
+ }
1659
+ function resolveVscodeSettings(params) {
1660
+ const { linter, formatter, configStrategy, isMonorepo, packageManager } = params;
1661
+ const settings = { ...DEFAULT_VSCODE_SETTINGS };
1662
+ const isStealth = !isMonorepo && (configStrategy ?? "stealth") === "stealth";
1663
+ const packageJsonNestedFiles = resolvePackageJsonNestedFiles(packageManager);
1664
+ if (packageJsonNestedFiles.length > 0) {
1665
+ settings["explorer.fileNesting.patterns"] = {
1666
+ ...settings["explorer.fileNesting.patterns"],
1667
+ "package.json": packageJsonNestedFiles.join(", ")
1668
+ };
1669
+ }
1670
+ if (linter === "eslint") {
1671
+ settings["eslint.enable"] = true;
1672
+ settings["oxc.enable"] = false;
1673
+ settings["biome.enabled"] = false;
1674
+ if (isStealth) {
1675
+ settings["eslint.options"] = {
1676
+ overrideConfigFile: ".config/eslint.config.js"
1677
+ };
1678
+ }
1679
+ } else if (linter === "oxlint") {
1680
+ settings["oxc.enable"] = true;
1681
+ settings["eslint.enable"] = false;
1682
+ settings["biome.enabled"] = false;
1683
+ if (isStealth) {
1684
+ settings["oxc.configPath"] = ".config/oxlint.json";
1685
+ }
1686
+ } else if (linter === "biome") {
1687
+ settings["biome.enabled"] = true;
1688
+ settings["eslint.enable"] = false;
1689
+ settings["oxc.enable"] = false;
1690
+ if (isStealth) {
1691
+ settings["biome.linter.configPath"] = ".config/biome.json";
1692
+ }
1693
+ }
1694
+ if (formatter === "prettier") {
1695
+ settings["editor.defaultFormatter"] = "esbenp.prettier-vscode";
1696
+ if (isStealth) {
1697
+ settings["prettier.configPath"] = ".config/prettier.json";
1698
+ settings["prettier.ignorePath"] = ".config/prettierignore";
1699
+ }
1700
+ } else if (formatter === "oxfmt") {
1701
+ settings["editor.defaultFormatter"] = "oxc.oxc-vscode";
1702
+ Object.assign(settings, OXFMT_LANGUAGE_SETTINGS);
1703
+ if (isStealth) {
1704
+ settings["oxc.fmt.configPath"] = ".config/oxfmt.json";
1705
+ }
1706
+ } else if (formatter === "biome") {
1707
+ settings["biome.enabled"] = true;
1708
+ settings["eslint.enable"] = false;
1709
+ settings["oxc.enable"] = false;
1710
+ settings["editor.defaultFormatter"] = "biomejs.biome";
1711
+ if (isStealth) {
1712
+ settings["biome.linter.configPath"] = ".config/biome.json";
1713
+ }
1714
+ }
1715
+ return settings;
1716
+ }
1717
+ function renderVscodeFiles$1(params) {
1718
+ const { codeSnippets = {}, vscodeSettings = {} } = params;
1353
1719
  const files = {};
1354
- if (codeSnippets["vscode-extension-suggestion"]?.length) {
1355
- const uniqueRecommendations = [...new Set(codeSnippets["vscode-extension-suggestion"])];
1720
+ const recommendations = [
1721
+ ...codeSnippets["vscode-extension-suggestion"] ?? [],
1722
+ ...resolveVscodeRecommendations(params.linter, params.formatter)
1723
+ ];
1724
+ if (recommendations.length > 0) {
1725
+ const uniqueRecommendations = [...new Set(recommendations)];
1356
1726
  files[".vscode/extensions.json"] = {
1357
1727
  type: "text",
1358
1728
  content: JSON.stringify(
@@ -1364,9 +1734,13 @@ function generateVscodeFiles$1(params) {
1364
1734
  )
1365
1735
  };
1366
1736
  }
1367
- if (Object.keys(vscodeSettings).length > 0) {
1737
+ const resolvedSettings = {
1738
+ ...resolveVscodeSettings(params),
1739
+ ...vscodeSettings
1740
+ };
1741
+ if (Object.keys(resolvedSettings).length > 0) {
1368
1742
  const sortedSettings = Object.fromEntries(
1369
- Object.entries(vscodeSettings).sort(([a], [b]) => a.localeCompare(b))
1743
+ Object.entries(resolvedSettings).sort(([a], [b]) => a.localeCompare(b))
1370
1744
  );
1371
1745
  files[".vscode/settings.json"] = {
1372
1746
  type: "text",
@@ -1383,7 +1757,7 @@ function formatValue(value, indent) {
1383
1757
  if (value.startsWith("$raw:")) {
1384
1758
  return value.slice(5);
1385
1759
  }
1386
- return JSON.stringify(value);
1760
+ return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
1387
1761
  }
1388
1762
  if (typeof value === "number" || typeof value === "boolean") {
1389
1763
  return String(value);
@@ -1391,8 +1765,17 @@ function formatValue(value, indent) {
1391
1765
  if (value === null) {
1392
1766
  return "null";
1393
1767
  }
1768
+ if (value === void 0) {
1769
+ return "undefined";
1770
+ }
1771
+ if (typeof value === "bigint") {
1772
+ return value.toString();
1773
+ }
1394
1774
  if (Array.isArray(value)) {
1395
1775
  if (value.length === 0) return "[]";
1776
+ if (value.every((item) => item == null || typeof item !== "object")) {
1777
+ return `[${value.map((item) => formatValue(item, indent + 1)).join(", ")}]`;
1778
+ }
1396
1779
  const items = value.map((v) => `${innerSpaces}${formatValue(v, indent + 1)}`);
1397
1780
  return `[
1398
1781
  ${items.join(",\n")}
@@ -1402,28 +1785,114 @@ ${spaces}]`;
1402
1785
  const entries = Object.entries(value);
1403
1786
  if (entries.length === 0) return "{}";
1404
1787
  const props = entries.map(
1405
- ([key, val]) => `${innerSpaces}${key}: ${formatValue(val, indent + 1)}`
1788
+ ([key, val]) => `${innerSpaces}${key}: ${formatValue(val, indent + 1)},`
1406
1789
  );
1407
1790
  return `{
1408
- ${props.join(",\n")}
1791
+ ${props.join("\n")}
1409
1792
  ${spaces}}`;
1410
1793
  }
1411
- return String(value);
1794
+ throw new TypeError(`Unsupported vite config value type: ${typeof value}`);
1412
1795
  }
1413
- function generateViteConfig(params) {
1796
+ function renderViteConfig(params) {
1414
1797
  const { viteConfig, codeSnippets } = params;
1415
1798
  const configBody = formatValue(viteConfig, 0);
1416
1799
  const viteConfigContent = [
1417
- `import { defineConfig } from "vite"`,
1800
+ `import { defineConfig } from 'vite';`,
1418
1801
  ...codeSnippets["vite-config-import"] ?? [],
1419
1802
  ``,
1420
- `export default defineConfig(${configBody})`,
1803
+ `export default defineConfig(${configBody});`,
1421
1804
  ``
1422
1805
  ].join("\n");
1423
1806
  return { type: "text", content: viteConfigContent };
1424
1807
  }
1425
1808
 
1426
- function generateTypescriptConfigPackage(files) {
1809
+ function toPrettierConfig(config = defaultFormatterMetaConfig) {
1810
+ return {
1811
+ $schema: "https://json.schemastore.org/prettierrc",
1812
+ printWidth: config.printWidth,
1813
+ tabWidth: config.tabWidth,
1814
+ useTabs: config.useTabs,
1815
+ semi: config.semi,
1816
+ singleQuote: config.singleQuote,
1817
+ trailingComma: config.trailingComma,
1818
+ bracketSpacing: config.bracketSpacing,
1819
+ arrowParens: config.arrowParens,
1820
+ overrides: [
1821
+ {
1822
+ files: ["*.md", "**/*.md"],
1823
+ options: { semi: false }
1824
+ },
1825
+ {
1826
+ files: ["*.yml", "*.yaml", "**/*.yml", "**/*.yaml"],
1827
+ options: { semi: false }
1828
+ }
1829
+ ]
1830
+ };
1831
+ }
1832
+ function toPrettierIgnoreContent(config = defaultFormatterMetaConfig) {
1833
+ return config.ignorePatterns.join("\n");
1834
+ }
1835
+ function toOxfmtConfig(config = defaultFormatterMetaConfig) {
1836
+ return {
1837
+ printWidth: config.printWidth,
1838
+ tabWidth: config.tabWidth,
1839
+ useTabs: config.useTabs,
1840
+ semi: config.semi,
1841
+ singleQuote: config.singleQuote,
1842
+ trailingComma: config.trailingComma,
1843
+ bracketSpacing: config.bracketSpacing,
1844
+ arrowParens: config.arrowParens,
1845
+ ignorePatterns: config.ignorePatterns
1846
+ };
1847
+ }
1848
+
1849
+ const defaultLinterMetaConfig = {
1850
+ ignorePatterns: ["dist"],
1851
+ rules: {
1852
+ noUnusedVars: {
1853
+ level: "warn",
1854
+ argsIgnorePattern: "^_",
1855
+ varsIgnorePattern: "^_",
1856
+ caughtErrorsIgnorePattern: "^_"
1857
+ },
1858
+ noUnusedExpressions: {
1859
+ level: "warn",
1860
+ allowShortCircuit: true
1861
+ }
1862
+ }
1863
+ };
1864
+
1865
+ function renderOxlintConfig(params) {
1866
+ const config = params.config ?? defaultLinterMetaConfig;
1867
+ const { rules } = config;
1868
+ const plugins = ["unicorn", "typescript", "oxc"];
1869
+ if (params.react === true) {
1870
+ plugins.push("react");
1871
+ }
1872
+ return {
1873
+ $schema: params.schemaPath,
1874
+ plugins,
1875
+ ...params.typescript === true ? { options: { typeAware: true } } : {},
1876
+ rules: {
1877
+ "no-unused-vars": [
1878
+ rules.noUnusedVars.level,
1879
+ {
1880
+ argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1881
+ varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1882
+ caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1883
+ }
1884
+ ],
1885
+ "no-useless-escape": "off",
1886
+ "no-unused-expressions": [
1887
+ rules.noUnusedExpressions.level,
1888
+ { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1889
+ ]
1890
+ },
1891
+ ignorePatterns: config.ignorePatterns
1892
+ };
1893
+ }
1894
+
1895
+ function renderTypescriptConfigPackage(files) {
1427
1896
  const basePath = ".config/typescript";
1428
1897
  files[`${basePath}/package.json`] = {
1429
1898
  type: "text",
@@ -1528,9 +1997,8 @@ In your package's \`tsconfig.json\`:
1528
1997
  )
1529
1998
  };
1530
1999
  }
1531
- function generateOxlintConfigPackage(files) {
2000
+ function renderOxlintConfigPackage(files) {
1532
2001
  const basePath = ".config/oxlint";
1533
- const { rules } = defaultLinterConfig;
1534
2002
  files[`${basePath}/package.json`] = {
1535
2003
  type: "text",
1536
2004
  content: JSON.stringify(
@@ -1567,26 +2035,10 @@ oxlint -c node_modules/@config/oxlint/base.json
1567
2035
  files[`${basePath}/base.json`] = {
1568
2036
  type: "text",
1569
2037
  content: JSON.stringify(
1570
- {
1571
- $schema: "./node_modules/oxlint/configuration_schema.json",
1572
- plugins: ["unicorn", "typescript", "oxc"],
1573
- rules: {
1574
- "no-unused-vars": [
1575
- rules.noUnusedVars.level,
1576
- {
1577
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1578
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1579
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1580
- }
1581
- ],
1582
- "no-useless-escape": "off",
1583
- "no-unused-expressions": [
1584
- rules.noUnusedExpressions.level,
1585
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1586
- ]
1587
- },
1588
- ignorePatterns: defaultLinterConfig.ignorePatterns
1589
- },
2038
+ renderOxlintConfig({
2039
+ schemaPath: "./node_modules/oxlint/configuration_schema.json",
2040
+ typescript: true
2041
+ }),
1590
2042
  null,
1591
2043
  2
1592
2044
  )
@@ -1594,32 +2046,17 @@ oxlint -c node_modules/@config/oxlint/base.json
1594
2046
  files[`${basePath}/react.json`] = {
1595
2047
  type: "text",
1596
2048
  content: JSON.stringify(
1597
- {
1598
- $schema: "./node_modules/oxlint/configuration_schema.json",
1599
- plugins: ["unicorn", "typescript", "oxc", "react"],
1600
- rules: {
1601
- "no-unused-vars": [
1602
- rules.noUnusedVars.level,
1603
- {
1604
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1605
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1606
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1607
- }
1608
- ],
1609
- "no-useless-escape": "off",
1610
- "no-unused-expressions": [
1611
- rules.noUnusedExpressions.level,
1612
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1613
- ]
1614
- },
1615
- ignorePatterns: defaultLinterConfig.ignorePatterns
1616
- },
2049
+ renderOxlintConfig({
2050
+ schemaPath: "./node_modules/oxlint/configuration_schema.json",
2051
+ react: true,
2052
+ typescript: true
2053
+ }),
1617
2054
  null,
1618
2055
  2
1619
2056
  )
1620
2057
  };
1621
2058
  }
1622
- function generateEslintConfigPackage(files) {
2059
+ function renderEslintConfigPackage(files) {
1623
2060
  const basePath = ".config/eslint";
1624
2061
  files[`${basePath}/package.json`] = {
1625
2062
  type: "text",
@@ -1726,7 +2163,7 @@ export default tseslint.config(
1726
2163
  `
1727
2164
  };
1728
2165
  }
1729
- function generatePrettierConfigPackage(files) {
2166
+ function renderPrettierConfigPackage(files) {
1730
2167
  const basePath = ".config/prettier";
1731
2168
  files[`${basePath}/package.json`] = {
1732
2169
  type: "text",
@@ -1739,7 +2176,7 @@ function generatePrettierConfigPackage(files) {
1739
2176
  exports: {
1740
2177
  ".": "./base.json"
1741
2178
  },
1742
- files: ["base.json"]
2179
+ files: ["base.json", "prettierignore"]
1743
2180
  },
1744
2181
  null,
1745
2182
  2
@@ -1774,10 +2211,14 @@ Or in \`.prettierrc.json\`:
1774
2211
  };
1775
2212
  files[`${basePath}/base.json`] = {
1776
2213
  type: "text",
1777
- content: JSON.stringify(defaultPrettierConfig, null, 2)
2214
+ content: JSON.stringify(toPrettierConfig(), null, 2)
2215
+ };
2216
+ files[`${basePath}/prettierignore`] = {
2217
+ type: "text",
2218
+ content: toPrettierIgnoreContent()
1778
2219
  };
1779
2220
  }
1780
- function generateOxfmtConfigPackage(files) {
2221
+ function renderOxfmtConfigPackage(files) {
1781
2222
  const basePath = ".config/oxfmt";
1782
2223
  files[`${basePath}/package.json`] = {
1783
2224
  type: "text",
@@ -1813,11 +2254,11 @@ oxfmt -c node_modules/@config/oxfmt/base.json --write .
1813
2254
  };
1814
2255
  files[`${basePath}/base.json`] = {
1815
2256
  type: "text",
1816
- content: JSON.stringify(defaultOxfmtConfig, null, 2)
2257
+ content: JSON.stringify(toOxfmtConfig(), null, 2)
1817
2258
  };
1818
2259
  }
1819
2260
 
1820
- function generateMonorepo(params) {
2261
+ function renderMonorepo(params) {
1821
2262
  const {
1822
2263
  name,
1823
2264
  linter,
@@ -1826,6 +2267,7 @@ function generateMonorepo(params) {
1826
2267
  pnpmManageVersions,
1827
2268
  engine,
1828
2269
  versions = {},
2270
+ ide = "vscode",
1829
2271
  aiPlatforms
1830
2272
  } = params;
1831
2273
  const files = {};
@@ -1838,6 +2280,7 @@ function generateMonorepo(params) {
1838
2280
  }
1839
2281
  if (linter === "oxlint") {
1840
2282
  assignResolvedPackageVersion(devDependencies, versions, "oxlint");
2283
+ assignResolvedPackageVersion(devDependencies, versions, "oxlint-tsgolint");
1841
2284
  } else if (linter === "eslint") {
1842
2285
  assignResolvedPackageVersion(devDependencies, versions, "eslint");
1843
2286
  } else if (linter === "biome") {
@@ -1853,13 +2296,7 @@ function generateMonorepo(params) {
1853
2296
  version: "0.0.0",
1854
2297
  private: true,
1855
2298
  type: "module",
1856
- scripts: {
1857
- dev: "pnpm --filter './apps/*' run dev",
1858
- build: "pnpm --filter './packages/*' run build && pnpm --filter './apps/*' run build",
1859
- test: "pnpm -r run test",
1860
- lint: linter === "oxlint" ? "oxlint ." : linter === "biome" ? "biome check ." : "eslint .",
1861
- format: formatter === "oxfmt" ? "oxfmt -c .config/oxfmt/base.json ." : formatter === "biome" ? "biome format . --write" : "prettier --config .config/prettier/base.json --write ."
1862
- },
2299
+ scripts: packageJsonScripts.monorepoRoot(linter, formatter),
1863
2300
  devDependencies
1864
2301
  };
1865
2302
  const engines = {};
@@ -1905,9 +2342,9 @@ function generateMonorepo(params) {
1905
2342
  2
1906
2343
  )
1907
2344
  };
1908
- generateTypescriptConfigPackage(files);
2345
+ renderTypescriptConfigPackage(files);
1909
2346
  if (linter === "oxlint") {
1910
- generateOxlintConfigPackage(files);
2347
+ renderOxlintConfigPackage(files);
1911
2348
  files["oxlint.json"] = {
1912
2349
  type: "text",
1913
2350
  content: JSON.stringify(
@@ -1920,7 +2357,7 @@ function generateMonorepo(params) {
1920
2357
  )
1921
2358
  };
1922
2359
  } else if (linter === "eslint") {
1923
- generateEslintConfigPackage(files);
2360
+ renderEslintConfigPackage(files);
1924
2361
  files["eslint.config.js"] = {
1925
2362
  type: "text",
1926
2363
  content: `import base from "@config/eslint/base";
@@ -1953,11 +2390,12 @@ export default [...base];
1953
2390
  };
1954
2391
  }
1955
2392
  if (formatter === "oxfmt") {
1956
- generateOxfmtConfigPackage(files);
2393
+ renderOxfmtConfigPackage(files);
1957
2394
  } else if (formatter === "prettier") {
1958
- generatePrettierConfigPackage(files);
2395
+ renderPrettierConfigPackage(files);
1959
2396
  }
1960
- files[".gitignore"] = generateGitignore("workspace-root");
2397
+ files[".editorconfig"] = renderEditorConfig();
2398
+ files[".gitignore"] = renderGitignore("workspace-root");
1961
2399
  files[".gitattributes"] = {
1962
2400
  type: "text",
1963
2401
  content: `* text=auto eol=lf
@@ -1965,7 +2403,9 @@ export default [...base];
1965
2403
  *.{bat,[bB][aA][tT]} text eol=crlf
1966
2404
  `
1967
2405
  };
1968
- generateVscodeFiles(files, linter, formatter);
2406
+ if (ide === "vscode") {
2407
+ renderVscodeFiles(files, linter, formatter, packageManager.name);
2408
+ }
1969
2409
  files["README.md"] = {
1970
2410
  type: "text",
1971
2411
  content: `# ${name}
@@ -1993,104 +2433,50 @@ To add a new package to this workspace, run create-krispya from this directory a
1993
2433
  `
1994
2434
  };
1995
2435
  if (aiPlatforms && aiPlatforms.length > 0) {
1996
- generateAiFiles(files, {
2436
+ renderAiFiles(files, {
1997
2437
  name,
1998
2438
  packageManager: packageManager.name,
1999
2439
  linter,
2000
2440
  formatter,
2001
2441
  isMonorepo: true,
2442
+ hasTypecheck: false,
2002
2443
  platforms: aiPlatforms
2003
2444
  });
2004
2445
  }
2005
2446
  return { files };
2006
2447
  }
2007
- function generateVscodeFiles(files, linter, formatter) {
2008
- const recommendations = [];
2009
- const settings = {};
2010
- if (linter === "oxlint") {
2011
- recommendations.push("oxc.oxc-vscode");
2012
- settings["oxc.enable"] = true;
2013
- settings["eslint.enable"] = false;
2014
- settings["biome.enabled"] = false;
2015
- } else if (linter === "eslint") {
2016
- recommendations.push("dbaeumer.vscode-eslint");
2017
- settings["eslint.enable"] = true;
2018
- settings["oxc.enable"] = false;
2019
- settings["biome.enabled"] = false;
2020
- } else if (linter === "biome") {
2021
- recommendations.push("biomejs.biome");
2022
- settings["biome.enabled"] = true;
2023
- settings["eslint.enable"] = false;
2024
- settings["oxc.enable"] = false;
2025
- }
2026
- if (formatter === "oxfmt") {
2027
- if (!recommendations.includes("oxc.oxc-vscode")) {
2028
- recommendations.push("oxc.oxc-vscode");
2029
- }
2030
- settings["editor.defaultFormatter"] = "oxc.oxc-vscode";
2031
- settings["[json]"] = {
2032
- "editor.defaultFormatter": "vscode.json-language-features"
2033
- };
2034
- settings["[jsonc]"] = {
2035
- "editor.defaultFormatter": "vscode.json-language-features"
2036
- };
2037
- } else if (formatter === "prettier") {
2038
- recommendations.push("esbenp.prettier-vscode");
2039
- settings["editor.defaultFormatter"] = "esbenp.prettier-vscode";
2040
- } else if (formatter === "biome") {
2041
- if (!recommendations.includes("biomejs.biome")) {
2042
- recommendations.push("biomejs.biome");
2043
- }
2044
- settings["editor.defaultFormatter"] = "biomejs.biome";
2045
- }
2046
- files[".vscode/extensions.json"] = {
2047
- type: "text",
2048
- content: JSON.stringify({ recommendations }, null, 2)
2049
- };
2050
- const codeSnippets = {};
2051
- if (recommendations.length > 0) {
2052
- codeSnippets["vscode-extension-suggestion"] = recommendations;
2053
- }
2448
+ function renderVscodeFiles(files, linter, formatter, packageManager = "pnpm") {
2054
2449
  Object.assign(
2055
2450
  files,
2056
- generateVscodeFiles$1({
2057
- codeSnippets,
2058
- vscodeSettings: settings
2451
+ renderVscodeFiles$1({
2452
+ linter,
2453
+ formatter,
2454
+ isMonorepo: true,
2455
+ packageManager
2059
2456
  })
2060
2457
  );
2061
2458
  }
2062
2459
 
2063
- const monorepo = {
2064
- __proto__: null,
2065
- generateEslintConfigPackage: generateEslintConfigPackage,
2066
- generateMonorepo: generateMonorepo,
2067
- generateOxfmtConfigPackage: generateOxfmtConfigPackage,
2068
- generateOxlintConfigPackage: generateOxlintConfigPackage,
2069
- generatePrettierConfigPackage: generatePrettierConfigPackage,
2070
- generateTypescriptConfigPackage: generateTypescriptConfigPackage,
2071
- generateVscodeFiles: generateVscodeFiles
2072
- };
2073
-
2074
2460
  function toBiomeLevel(level) {
2075
2461
  return level;
2076
2462
  }
2077
- function generateBiome(generator, options) {
2463
+ function planBiome(builder, options) {
2078
2464
  if (options == null || !options.linter && !options.formatter) {
2079
2465
  return;
2080
2466
  }
2081
- const version = generator.getVersion("@biomejs/biome");
2082
- generator.addDevDependency("@biomejs/biome");
2083
- const { rules } = defaultLinterConfig;
2467
+ const version = builder.getVersion("@biomejs/biome");
2468
+ builder.addDevDependency("@biomejs/biome");
2084
2469
  const biomeConfig = {
2085
2470
  $schema: `https://biomejs.dev/schemas/${version}/schema.json`
2086
2471
  };
2087
2472
  if (options.linter) {
2473
+ const linterConfig = options.linter.config;
2088
2474
  biomeConfig.linter = {
2089
2475
  enabled: true,
2090
2476
  rules: {
2091
2477
  recommended: true,
2092
2478
  correctness: {
2093
- noUnusedVariables: toBiomeLevel(rules.noUnusedVars.level)
2479
+ noUnusedVariables: toBiomeLevel(linterConfig.rules.noUnusedVars.level)
2094
2480
  }
2095
2481
  }
2096
2482
  };
@@ -2100,19 +2486,20 @@ function generateBiome(generator, options) {
2100
2486
  };
2101
2487
  }
2102
2488
  if (options.formatter) {
2489
+ const formatterConfig = options.formatter.config;
2103
2490
  biomeConfig.formatter = {
2104
2491
  enabled: true,
2105
- lineWidth: defaultFormatterConfig.printWidth,
2106
- indentWidth: defaultFormatterConfig.tabWidth,
2107
- indentStyle: "space"
2492
+ lineWidth: formatterConfig.printWidth,
2493
+ indentWidth: formatterConfig.tabWidth,
2494
+ indentStyle: formatterConfig.useTabs ? "tab" : "space"
2108
2495
  };
2109
2496
  biomeConfig.javascript = {
2110
2497
  formatter: {
2111
- semicolons: "always" ,
2112
- quoteStyle: "single" ,
2113
- trailingCommas: defaultFormatterConfig.trailingComma,
2114
- bracketSpacing: defaultFormatterConfig.bracketSpacing,
2115
- arrowParentheses: "always"
2498
+ semicolons: formatterConfig.semi ? "always" : "asNeeded",
2499
+ quoteStyle: formatterConfig.singleQuote ? "single" : "double",
2500
+ trailingCommas: formatterConfig.trailingComma,
2501
+ bracketSpacing: formatterConfig.bracketSpacing,
2502
+ arrowParentheses: formatterConfig.arrowParens === "always" ? "always" : "asNeeded"
2116
2503
  }
2117
2504
  };
2118
2505
  biomeConfig.json = {
@@ -2125,53 +2512,48 @@ function generateBiome(generator, options) {
2125
2512
  enabled: false
2126
2513
  };
2127
2514
  }
2128
- const isStealth = generator.isStealthConfig();
2515
+ const isStealth = builder.isStealthConfig();
2129
2516
  if (isStealth) {
2130
- generator.addFile(".config/biome.json", {
2517
+ builder.addFile(".config/biome.json", {
2131
2518
  type: "text",
2132
2519
  content: JSON.stringify(biomeConfig, null, 2)
2133
2520
  });
2134
2521
  if (options.linter) {
2135
- generator.addScript("lint", "biome lint --config-path .config .");
2522
+ builder.addScripts(packageJsonScripts.lint.biome(".config"));
2136
2523
  }
2137
2524
  if (options.formatter) {
2138
- generator.addScript("format", "biome format --config-path .config --write .");
2525
+ builder.addScripts(packageJsonScripts.format.biome(".config"));
2139
2526
  }
2140
- generator.addVscodeSetting("biome.linter.configPath", ".config/biome.json");
2141
2527
  } else {
2142
- generator.addFile("biome.json", {
2528
+ builder.addFile("biome.json", {
2143
2529
  type: "text",
2144
2530
  content: JSON.stringify(biomeConfig, null, 2)
2145
2531
  });
2146
2532
  if (options.linter) {
2147
- generator.addScript("lint", "biome lint .");
2533
+ builder.addScripts(packageJsonScripts.lint.biome());
2148
2534
  }
2149
2535
  if (options.formatter) {
2150
- generator.addScript("format", "biome format --write .");
2536
+ builder.addScripts(packageJsonScripts.format.biome());
2151
2537
  }
2152
2538
  }
2153
2539
  const roles = [];
2154
2540
  if (options.linter) roles.push("linter");
2155
2541
  if (options.formatter) roles.push("formatter");
2156
- generator.inject(
2542
+ builder.inject(
2157
2543
  "readme-tools",
2158
2544
  `[Biome](https://biomejs.dev/) - Fast ${roles.join(" and ")} for JavaScript and TypeScript`
2159
2545
  );
2160
- generator.inject("vscode-extension-suggestion", "biomejs.biome");
2161
- generator.addVscodeSetting("biome.enabled", true);
2162
- if (options.formatter) {
2163
- generator.addVscodeSetting("editor.defaultFormatter", "biomejs.biome");
2164
- }
2546
+ builder.inject("vscode-extension-suggestion", "biomejs.biome");
2165
2547
  }
2166
2548
 
2167
- function generateDrei(generator, options) {
2549
+ function planDrei(builder, options) {
2168
2550
  if (options == null) {
2169
2551
  return;
2170
2552
  }
2171
- generator.addDependency("@react-three/drei");
2172
- generator.inject("import", `import { Environment } from "@react-three/drei"`);
2173
- generator.inject("scene", '<Environment background preset="city" />');
2174
- generator.inject(
2553
+ builder.addDependency("@react-three/drei");
2554
+ builder.inject("import", `import { Environment } from "@react-three/drei"`);
2555
+ builder.inject("scene", '<Environment background preset="city" />');
2556
+ builder.inject(
2175
2557
  "readme-libraries",
2176
2558
  `[@react-three/drei](https://drei.docs.pmnd.rs/) - Useful helpers for @react-three/fiber`
2177
2559
  );
@@ -2180,25 +2562,28 @@ function generateDrei(generator, options) {
2180
2562
  function toEslintLevel(level) {
2181
2563
  return level;
2182
2564
  }
2183
- function generateEslint(generator, options) {
2184
- generator.addDevDependency("eslint");
2185
- const template = generator.options.template ?? "vanilla";
2565
+ function planEslint(builder, options) {
2566
+ if (options == null) {
2567
+ return;
2568
+ }
2569
+ builder.addDevDependency("eslint");
2570
+ const template = builder.options.template ?? "vanilla";
2186
2571
  const baseTemplate = getBaseTemplate(template);
2187
2572
  const isTypescript = getLanguageFromTemplate(template) === "typescript";
2188
2573
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2189
- const { rules } = defaultLinterConfig;
2574
+ const { rules } = options.config;
2190
2575
  const imports = ['import js from "@eslint/js"'];
2191
2576
  const configs = ["js.configs.recommended"];
2192
2577
  if (isTypescript) {
2193
- generator.addDevDependency("typescript-eslint");
2578
+ builder.addDevDependency("typescript-eslint");
2194
2579
  imports.push('import tseslint from "typescript-eslint"');
2195
2580
  configs.push("...tseslint.configs.recommended");
2196
2581
  }
2197
2582
  if (isReact) {
2198
- generator.addDevDependency("eslint-plugin-react-hooks");
2583
+ builder.addDevDependency("eslint-plugin-react-hooks");
2199
2584
  imports.push('import reactHooks from "eslint-plugin-react-hooks"');
2200
2585
  }
2201
- const ignoresArray = JSON.stringify(defaultLinterConfig.ignorePatterns);
2586
+ const ignoresArray = JSON.stringify(options.config.ignorePatterns);
2202
2587
  const unusedVarsRule = isTypescript ? "@typescript-eslint/no-unused-vars" : "no-unused-vars";
2203
2588
  const rulesConfig = {
2204
2589
  [unusedVarsRule]: [
@@ -2232,34 +2617,30 @@ function generateEslint(generator, options) {
2232
2617
  },`,
2233
2618
  "]"
2234
2619
  ].filter(Boolean).join("\n");
2235
- const isStealth = generator.isStealthConfig();
2620
+ const isStealth = builder.isStealthConfig();
2236
2621
  if (isStealth) {
2237
- generator.addFile(".config/eslint.config.js", {
2622
+ builder.addFile(".config/eslint.config.js", {
2238
2623
  type: "text",
2239
2624
  content: configContent
2240
2625
  });
2241
- generator.addScript("lint", "eslint --config .config/eslint.config.js .");
2242
- generator.addVscodeSetting("eslint.options", {
2243
- overrideConfigFile: ".config/eslint.config.js"
2244
- });
2626
+ builder.addScripts(packageJsonScripts.lint.eslint(".config/eslint.config.js"));
2245
2627
  } else {
2246
- generator.addFile("eslint.config.js", {
2628
+ builder.addFile("eslint.config.js", {
2247
2629
  type: "text",
2248
2630
  content: configContent
2249
2631
  });
2250
- generator.addScript("lint", "eslint .");
2632
+ builder.addScripts(packageJsonScripts.lint.eslint());
2251
2633
  }
2252
- generator.inject(
2634
+ builder.inject(
2253
2635
  "readme-tools",
2254
2636
  "[ESLint](https://eslint.org/) - Linter for JavaScript and TypeScript"
2255
2637
  );
2256
- generator.inject("vscode-extension-suggestion", "dbaeumer.vscode-eslint");
2257
- generator.addVscodeSetting("eslint.enable", true);
2638
+ builder.inject("vscode-extension-suggestion", "dbaeumer.vscode-eslint");
2258
2639
  }
2259
2640
 
2260
- function generateFiber(generator, _options) {
2261
- generator.inject("import", `import { Box } from "./box.js"`);
2262
- generator.inject(
2641
+ function planFiber(builder, _options) {
2642
+ builder.inject("import", `import { Box } from "./box.js"`);
2643
+ builder.inject(
2263
2644
  "scene",
2264
2645
  [
2265
2646
  `<ambientLight intensity={Math.PI / 2} />`,
@@ -2269,7 +2650,7 @@ function generateFiber(generator, _options) {
2269
2650
  `<Box position={[1.2, 0, 0]} />`
2270
2651
  ].join("\n")
2271
2652
  );
2272
- generator.addFile("src/box.tsx", {
2653
+ builder.addFile("src/box.tsx", {
2273
2654
  type: "text",
2274
2655
  content: `import type { Mesh } from 'three'
2275
2656
  import { useRef, useState } from 'react'
@@ -2296,11 +2677,11 @@ export function Box(props: ThreeElements['mesh']) {
2296
2677
  });
2297
2678
  }
2298
2679
 
2299
- function generateGithubPages(generator, options) {
2300
- if (options === false || getPackageManagerName(generator.options.packageManager) !== "npm") {
2680
+ function planGithubPages(builder, options) {
2681
+ if (options === false || getPackageManagerName(builder.options.packageManager) !== "npm") {
2301
2682
  return;
2302
2683
  }
2303
- generator.addFile(".github/workflows/gh-pages.yml", {
2684
+ builder.addFile(".github/workflows/gh-pages.yml", {
2304
2685
  type: "text",
2305
2686
  content: `name: Deploy to Github Pages
2306
2687
 
@@ -2346,10 +2727,10 @@ jobs:
2346
2727
  uses: actions/deploy-pages@v4
2347
2728
  `
2348
2729
  });
2349
- generator.inject("readme-start", `A github pages deployment action is configurd.`);
2350
- if (generator.options.githubUserName != null && generator.options.githubRepoName != null) {
2351
- const address = `${generator.options.githubUserName}.github.io/${generator.options.githubRepoName}`;
2352
- generator.inject(
2730
+ builder.inject("readme-start", `A github pages deployment action is configurd.`);
2731
+ if (builder.options.githubUserName != null && builder.options.githubRepoName != null) {
2732
+ const address = `${builder.options.githubUserName}.github.io/${builder.options.githubRepoName}`;
2733
+ builder.inject(
2353
2734
  "readme-start",
2354
2735
  `Your app will be publish at [${address}](https://${address}) once the github action is finished.
2355
2736
  `
@@ -2357,228 +2738,199 @@ jobs:
2357
2738
  }
2358
2739
  }
2359
2740
 
2360
- function generateHandle(generator, options) {
2741
+ function planHandle(builder, options) {
2361
2742
  if (options == null) {
2362
2743
  return;
2363
2744
  }
2364
- generator.addDependency("@react-three/handle");
2365
- generator.inject(
2745
+ builder.addDependency("@react-three/handle");
2746
+ builder.inject(
2366
2747
  "readme-libraries",
2367
2748
  `[@react-three/handle](https://pmndrs.github.io/xr/docs/handles/introduction) - interactive controls and handles for your 3D objects`
2368
2749
  );
2369
2750
  }
2370
2751
 
2371
- function generateKoota(generator, options) {
2752
+ function planKoota(builder, options) {
2372
2753
  if (options == null) {
2373
2754
  return;
2374
2755
  }
2375
- generator.addDependency("koota");
2376
- generator.inject(
2756
+ builder.addDependency("koota");
2757
+ builder.inject(
2377
2758
  "readme-libraries",
2378
2759
  `[koota](https://github.com/pmndrs/koota) - ECS-based state management library optimized for real-time apps, games, and XR experiences`
2379
2760
  );
2380
2761
  }
2381
2762
 
2382
- function generateLeva(generator, options) {
2763
+ function planLeva(builder, options) {
2383
2764
  if (options == null) {
2384
2765
  return;
2385
2766
  }
2386
- generator.addDependency("leva");
2387
- generator.inject(
2767
+ builder.addDependency("leva");
2768
+ builder.inject(
2388
2769
  "readme-libraries",
2389
2770
  `[leva](https://github.com/pmndrs/leva) - HTML GUI panel for React with lightweight, beautiful and extensible controls`
2390
2771
  );
2391
2772
  }
2392
2773
 
2393
- function generateOffscreen(generator, options) {
2774
+ function planOffscreen(builder, options) {
2394
2775
  if (options == null) {
2395
2776
  return;
2396
2777
  }
2397
- if (generator.options.xr != null) {
2778
+ if (builder.options.xr != null) {
2398
2779
  console.info(
2399
2780
  color.blue("Info:"),
2400
2781
  "@react-three/offscreen is disabled because it is not supported with XR"
2401
2782
  );
2402
2783
  return;
2403
2784
  }
2404
- generator.addDependency("@react-three/offscreen");
2405
- generator.inject(
2785
+ builder.addDependency("@react-three/offscreen");
2786
+ builder.inject(
2406
2787
  "readme-libraries",
2407
2788
  `[@react-three/offscreen](https://github.com/pmndrs/offscreen) - Offload your scene to a worker thread for better performance`
2408
2789
  );
2409
2790
  }
2410
2791
 
2411
- function generateOxfmt(generator, options) {
2412
- const isMonorepo = generator.options.workspaceRoot != null;
2792
+ function planOxfmt(builder, options) {
2793
+ if (options == null) {
2794
+ return;
2795
+ }
2796
+ const isMonorepo = builder.options.workspaceRoot != null;
2413
2797
  if (isMonorepo) {
2414
- generator.addDevDependency("@config/oxfmt", { version: "workspace:*" });
2798
+ builder.addDevDependency("@config/oxfmt", { version: "workspace:*" });
2415
2799
  const configPath = "node_modules/@config/oxfmt/base.json";
2416
- generator.addScript("format", `oxfmt -c ${configPath} --write .`);
2417
- generator.addVscodeSetting("oxc.fmt.configPath", configPath);
2800
+ builder.addScripts(packageJsonScripts.format.oxfmt(configPath));
2418
2801
  } else {
2419
- generator.addDevDependency("oxfmt");
2420
- const isStealth = generator.isStealthConfig();
2802
+ builder.addDevDependency("oxfmt");
2803
+ const isStealth = builder.isStealthConfig();
2421
2804
  if (isStealth) {
2422
- generator.addFile(".config/oxfmt.json", {
2805
+ builder.addFile(".config/oxfmt.json", {
2423
2806
  type: "text",
2424
- content: JSON.stringify(defaultOxfmtConfig, null, 2)
2807
+ content: JSON.stringify(toOxfmtConfig(options.config), null, 2)
2425
2808
  });
2426
- generator.addScript("format", "oxfmt -c .config/oxfmt.json --write .");
2427
- generator.addVscodeSetting("oxc.fmt.configPath", ".config/oxfmt.json");
2809
+ builder.addScripts(packageJsonScripts.format.oxfmt(".config/oxfmt.json"));
2428
2810
  } else {
2429
- generator.addFile("oxfmt.json", {
2811
+ builder.addFile("oxfmt.json", {
2430
2812
  type: "text",
2431
- content: JSON.stringify(defaultOxfmtConfig, null, 2)
2813
+ content: JSON.stringify(toOxfmtConfig(options.config), null, 2)
2432
2814
  });
2433
- generator.addScript("format", "oxfmt -c oxfmt.json --write .");
2815
+ builder.addScripts(packageJsonScripts.format.oxfmt("oxfmt.json"));
2434
2816
  }
2435
2817
  }
2436
- generator.inject(
2818
+ builder.inject(
2437
2819
  "readme-tools",
2438
2820
  "[Oxfmt](https://oxc.rs/docs/guide/usage/formatter) - Fast Prettier-compatible code formatter"
2439
2821
  );
2440
- generator.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2441
- generator.addVscodeSetting("editor.defaultFormatter", "oxc.oxc-vscode");
2442
- generator.addVscodeSetting("[json]", {
2443
- "editor.defaultFormatter": "vscode.json-language-features"
2444
- });
2445
- generator.addVscodeSetting("[jsonc]", {
2446
- "editor.defaultFormatter": "vscode.json-language-features"
2447
- });
2448
- generator.addVscodeSetting("[markdown]", {
2449
- "editor.defaultFormatter": "vscode.markdown-language-features"
2450
- });
2451
- generator.addVscodeSetting("[yaml]", {
2452
- "editor.defaultFormatter": "redhat.vscode-yaml"
2453
- });
2822
+ builder.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2454
2823
  }
2455
2824
 
2456
- function toOxlintLevel(level) {
2457
- return level;
2458
- }
2459
- function generateOxlint(generator, options) {
2460
- const template = generator.options.template ?? "vanilla";
2825
+ function planOxlint(builder, options) {
2826
+ if (options == null) {
2827
+ return;
2828
+ }
2829
+ const template = builder.options.template ?? "vanilla";
2461
2830
  const baseTemplate = getBaseTemplate(template);
2831
+ const isTypescript = getLanguageFromTemplate(template) === "typescript";
2462
2832
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2463
- const isMonorepo = generator.options.workspaceRoot != null;
2833
+ const isMonorepo = builder.options.workspaceRoot != null;
2464
2834
  if (isMonorepo) {
2465
- generator.addDevDependency("@config/oxlint", { version: "workspace:*" });
2835
+ builder.addDevDependency("@config/oxlint", { version: "workspace:*" });
2466
2836
  const configPath = isReact ? "node_modules/@config/oxlint/react.json" : "node_modules/@config/oxlint/base.json";
2467
- generator.addScript("lint", `oxlint -c ${configPath}`);
2468
- generator.addVscodeSetting("oxc.configPath", configPath);
2837
+ builder.addScripts(packageJsonScripts.lint.oxlint(configPath));
2469
2838
  } else {
2470
- generator.addDevDependency("oxlint");
2471
- const isStealth = generator.isStealthConfig();
2472
- const { rules } = defaultLinterConfig;
2473
- const plugins = ["unicorn", "typescript", "oxc"];
2474
- if (isReact) {
2475
- plugins.push("react");
2476
- }
2477
- const oxlintConfig = {
2478
- $schema: isStealth ? "../node_modules/oxlint/configuration_schema.json" : "./node_modules/oxlint/configuration_schema.json",
2479
- plugins,
2480
- rules: {
2481
- "no-unused-vars": [
2482
- toOxlintLevel(rules.noUnusedVars.level),
2483
- {
2484
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
2485
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
2486
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
2487
- }
2488
- ],
2489
- "no-useless-escape": "off",
2490
- "no-unused-expressions": [
2491
- toOxlintLevel(rules.noUnusedExpressions.level),
2492
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
2493
- ]
2494
- },
2495
- ignorePatterns: defaultLinterConfig.ignorePatterns
2496
- };
2839
+ builder.addDevDependency("oxlint");
2840
+ if (isTypescript) {
2841
+ builder.addDevDependency("oxlint-tsgolint");
2842
+ }
2843
+ const isStealth = builder.isStealthConfig();
2844
+ const oxlintConfig = renderOxlintConfig({
2845
+ schemaPath: isStealth ? "../node_modules/oxlint/configuration_schema.json" : "./node_modules/oxlint/configuration_schema.json",
2846
+ react: isReact,
2847
+ typescript: isTypescript,
2848
+ config: options.config
2849
+ });
2497
2850
  if (isStealth) {
2498
- generator.addFile(".config/oxlint.json", {
2851
+ builder.addFile(".config/oxlint.json", {
2499
2852
  type: "text",
2500
2853
  content: JSON.stringify(oxlintConfig, null, 2)
2501
2854
  });
2502
- generator.addScript("lint", "oxlint -c .config/oxlint.json");
2503
- generator.addVscodeSetting("oxc.configPath", ".config/oxlint.json");
2855
+ builder.addScripts(packageJsonScripts.lint.oxlint(".config/oxlint.json"));
2504
2856
  } else {
2505
- generator.addFile("oxlint.json", {
2857
+ builder.addFile("oxlint.json", {
2506
2858
  type: "text",
2507
2859
  content: JSON.stringify(oxlintConfig, null, 2)
2508
2860
  });
2509
- generator.addScript("lint", "oxlint");
2861
+ builder.addScripts(packageJsonScripts.lint.oxlint());
2510
2862
  }
2511
2863
  }
2512
- generator.inject(
2864
+ builder.inject(
2513
2865
  "readme-tools",
2514
2866
  "[Oxlint](https://oxc.rs/docs/guide/usage/linter) - A fast linter for JavaScript and TypeScript"
2515
2867
  );
2516
- generator.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2517
- generator.addVscodeSetting("oxc.enable", true);
2868
+ builder.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2518
2869
  }
2519
2870
 
2520
- function generatePostprocessing(generator, options) {
2871
+ function planPostprocessing(builder, options) {
2521
2872
  if (options == null) {
2522
2873
  return;
2523
2874
  }
2524
- if (generator.options.xr != null) {
2875
+ if (builder.options.xr != null) {
2525
2876
  console.info(
2526
2877
  color.blue("Info:"),
2527
2878
  "@react-three/postprocessing is disabled because it is not supported with XR"
2528
2879
  );
2529
2880
  return;
2530
2881
  }
2531
- generator.addDependency("@react-three/postprocessing");
2532
- generator.inject(
2882
+ builder.addDependency("@react-three/postprocessing");
2883
+ builder.inject(
2533
2884
  "readme-libraries",
2534
2885
  `[@react-three/postprocessing](https://react-postprocessing.docs.pmnd.rs/) - Post-processing effects for @react-three/fiber`
2535
2886
  );
2536
2887
  }
2537
2888
 
2538
- function generatePrettier(generator, options) {
2539
- generator.addDevDependency("prettier");
2540
- const isStealth = generator.isStealthConfig();
2889
+ function planPrettier(builder, options) {
2890
+ if (options == null) {
2891
+ return;
2892
+ }
2893
+ builder.addDevDependency("prettier");
2894
+ const isStealth = builder.isStealthConfig();
2541
2895
  if (isStealth) {
2542
- generator.addFile(".config/prettier.json", {
2896
+ builder.addFile(".config/prettier.json", {
2897
+ type: "text",
2898
+ content: JSON.stringify(toPrettierConfig(options.config), null, 2)
2899
+ });
2900
+ builder.addFile(".config/prettierignore", {
2543
2901
  type: "text",
2544
- content: JSON.stringify(defaultPrettierConfig, null, 2)
2902
+ content: toPrettierIgnoreContent(options.config)
2545
2903
  });
2546
- generator.addScript("format", "prettier --config .config/prettier.json --write .");
2547
- generator.addVscodeSetting("prettier.configPath", ".config/prettier.json");
2904
+ builder.addScripts(
2905
+ packageJsonScripts.format.prettier(".config/prettier.json", ".config/prettierignore")
2906
+ );
2548
2907
  } else {
2549
- generator.addFile(".prettierrc", {
2908
+ builder.addFile(".prettierrc", {
2909
+ type: "text",
2910
+ content: JSON.stringify(toPrettierConfig(options.config), null, 2)
2911
+ });
2912
+ builder.addFile(".prettierignore", {
2550
2913
  type: "text",
2551
- content: JSON.stringify(defaultPrettierConfig, null, 2)
2914
+ content: toPrettierIgnoreContent(options.config)
2552
2915
  });
2553
- generator.addScript("format", "prettier --write .");
2916
+ builder.addScripts(packageJsonScripts.format.prettier());
2554
2917
  }
2555
- generator.inject("readme-tools", "[Prettier](https://prettier.io/) - Opinionated code formatter");
2556
- generator.inject("vscode-extension-suggestion", "esbenp.prettier-vscode");
2557
- generator.addVscodeSetting("editor.defaultFormatter", "esbenp.prettier-vscode");
2918
+ builder.inject("readme-tools", "[Prettier](https://prettier.io/) - Opinionated code formatter");
2919
+ builder.inject("vscode-extension-suggestion", "esbenp.prettier-vscode");
2558
2920
  }
2559
2921
 
2560
- function generateRapier(generator, options) {
2922
+ function planRapier(builder, options) {
2561
2923
  if (options == null) {
2562
2924
  return;
2563
2925
  }
2564
- generator.addDependency("@react-three/rapier");
2565
- generator.inject(
2926
+ builder.addDependency("@react-three/rapier");
2927
+ builder.inject(
2566
2928
  "readme-libraries",
2567
2929
  `[@react-three/rapier](https://github.com/pmndrs/react-three-rapier) - Physics based on Rapier for your @react-three/fiber scene`
2568
2930
  );
2569
2931
  }
2570
2932
 
2571
- function unique(...array) {
2572
- const set = /* @__PURE__ */ new Set();
2573
- for (const arr of array) {
2574
- for (const item of arr) {
2575
- set.add(item);
2576
- }
2577
- }
2578
- return Array.from(set);
2579
- }
2580
-
2581
- function generateProvidersModule(generator) {
2933
+ function generateProvidersModule(builder) {
2582
2934
  const canvasProviders = [];
2583
2935
  const globalProviders = [];
2584
2936
  const providerDefs = {
@@ -2644,29 +2996,23 @@ function generateProvidersModule(generator) {
2644
2996
  ]
2645
2997
  }
2646
2998
  };
2647
- if (generator.options.rapier) {
2999
+ if (builder.options.rapier) {
2648
3000
  canvasProviders.push("rapier");
2649
3001
  }
2650
- if (!!generator.options.postprocessing && !generator.options.xr) {
3002
+ if (!!builder.options.postprocessing && !builder.options.xr) {
2651
3003
  canvasProviders.push("postprocessing");
2652
3004
  }
2653
- if (generator.options.uikit) {
3005
+ if (builder.options.uikit) {
2654
3006
  globalProviders.push("uikit");
2655
3007
  }
2656
3008
  function generateProviderFunction(name, { jsdoc, providers }) {
2657
3009
  const resolvedProviders = providers.map((provider) => providerDefs[provider]);
2658
3010
  const providerProps = resolvedProviders.flatMap((provider) => provider.props || []);
2659
3011
  const providerImports = resolvedProviders.flatMap((provider) => provider.import);
2660
- const wrappedComponents = resolvedProviders.filter(
2661
- (provider) => provider.type === "wrapped-jsx"
2662
- );
2663
- const inlineComponents = resolvedProviders.filter(
2664
- (provider) => provider.type === "inline-jsx"
2665
- );
2666
- const layoutEffects = resolvedProviders.filter(
2667
- (provider) => provider.type === "layout-effect"
2668
- );
2669
- const declaredProps = providerProps.map((prop) => `${prop.declaredPropName} = ${prop.declaredPropDefaultValue}`).join(", ");
3012
+ const wrappedComponents = resolvedProviders.filter((provider) => provider.type === "wrapped-jsx");
3013
+ const inlineComponents = resolvedProviders.filter((provider) => provider.type === "inline-jsx");
3014
+ const layoutEffects = resolvedProviders.filter((provider) => provider.type === "layout-effect");
3015
+ const declaredProps = providerProps.map((prop) => `${prop.declaredPropName} = ${String(prop.declaredPropDefaultValue)}`).join(", ");
2670
3016
  const declaredTypes = providerProps.map((prop) => `${prop.declaredPropName}?: ${prop.declaredPropType}`).join("; ");
2671
3017
  const reactImports = ["type ReactNode"];
2672
3018
  if (layoutEffects.length) {
@@ -2683,11 +3029,11 @@ ${jsdoc.split("\n").map((line) => ` * ${line}`).join("\n")}
2683
3029
  ${layoutEffects.length ? `
2684
3030
  useLayoutEffect(() => {
2685
3031
  ${layoutEffects.map((effect) => effect.code).join("\n")}
2686
- }, [${layoutEffects.map((effect) => effect.props?.[0]?.propValue)}]);
3032
+ }, [${layoutEffects.map((effect) => effect.props?.[0]?.propValue).join(", ")}]);
2687
3033
  ` : ""}
2688
3034
  return (
2689
3035
  <>
2690
- ${inlineComponents.map((provider) => provider.code)}
3036
+ ${inlineComponents.map((provider) => provider.code).join("\n")}
2691
3037
  ${wrappedComponents.reduce((acc, provider) => {
2692
3038
  const props = provider.props?.map((prop) => `${prop.propName}={${prop.propValue}}`).join(" ");
2693
3039
  return `<${provider.component} ${props}>${acc}</${provider.component}>`;
@@ -2713,20 +3059,20 @@ ${jsdoc.split("\n").map((line) => ` * ${line}`).join("\n")}
2713
3059
  ${canvas.code}
2714
3060
  `;
2715
3061
  }
2716
- function generateTriplex(generator, options) {
3062
+ function planTriplex(builder, options) {
2717
3063
  if (options == null) {
2718
3064
  return;
2719
3065
  }
2720
- generator.inject("vscode-extension-suggestion", "trytriplex.triplex-vsce");
2721
- generator.inject(
3066
+ builder.inject("vscode-extension-suggestion", "trytriplex.triplex-vsce");
3067
+ builder.inject(
2722
3068
  "readme-tools",
2723
3069
  `[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).`
2724
3070
  );
2725
- generator.addFile(".triplex/providers.tsx", {
2726
- content: generateProvidersModule(generator),
3071
+ builder.addFile(".triplex/providers.tsx", {
3072
+ content: generateProvidersModule(builder),
2727
3073
  type: "text"
2728
3074
  });
2729
- generator.addFile(".triplex/config.json", {
3075
+ builder.addFile(".triplex/config.json", {
2730
3076
  content: JSON.stringify(
2731
3077
  {
2732
3078
  $schema: "https://triplex.dev/config.schema.json",
@@ -2739,9 +3085,9 @@ function generateTriplex(generator, options) {
2739
3085
  });
2740
3086
  }
2741
3087
 
2742
- function generateTsdown(generator) {
2743
- generator.addDevDependency("tsdown");
2744
- const template = generator.options.template ?? "vanilla";
3088
+ function planTsdown(builder) {
3089
+ builder.addDevDependency("tsdown");
3090
+ const template = builder.options.template ?? "vanilla";
2745
3091
  const baseTemplate = getBaseTemplate(template);
2746
3092
  const language = getLanguageFromTemplate(template);
2747
3093
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
@@ -2761,36 +3107,36 @@ function generateTsdown(generator) {
2761
3107
  configLines.push(` },`);
2762
3108
  }
2763
3109
  configLines.push(`})`);
2764
- generator.addFile(`tsdown.config.${ext}`, {
3110
+ builder.addFile(`tsdown.config.${ext}`, {
2765
3111
  type: "text",
2766
3112
  content: configLines.join("\n")
2767
3113
  });
2768
- generator.addScript("build", "tsdown");
2769
- generator.inject(
3114
+ builder.addScripts(packageJsonScripts.build.tsdown);
3115
+ builder.inject(
2770
3116
  "readme-libraries",
2771
3117
  "[tsdown](https://github.com/nicepkg/tsdown) - Fast TypeScript bundler powered by esbuild"
2772
3118
  );
2773
3119
  }
2774
3120
 
2775
- function generateUikit(generator, options) {
3121
+ function planUikit(builder, options) {
2776
3122
  if (options == null) {
2777
3123
  return;
2778
3124
  }
2779
- generator.addDependency("@react-three/uikit");
2780
- generator.inject(
3125
+ builder.addDependency("@react-three/uikit");
3126
+ builder.inject(
2781
3127
  "readme-libraries",
2782
3128
  `[@react-three/uikit](https://pmndrs.github.io/uikit/docs/) - UI primitives for React Three Fiber`
2783
3129
  );
2784
3130
  }
2785
3131
 
2786
- function generateUnbuild(generator) {
2787
- generator.addDevDependency("unbuild");
2788
- const template = generator.options.template ?? "vanilla";
3132
+ function planUnbuild(builder) {
3133
+ builder.addDevDependency("unbuild");
3134
+ const template = builder.options.template ?? "vanilla";
2789
3135
  const baseTemplate = getBaseTemplate(template);
2790
3136
  const language = getLanguageFromTemplate(template);
2791
3137
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2792
3138
  const ext = language === "typescript" ? "ts" : "js";
2793
- const isMonorepo = generator.options.workspaceRoot != null;
3139
+ const isMonorepo = builder.options.workspaceRoot != null;
2794
3140
  const buildConfigLines = [
2795
3141
  `import { defineBuildConfig } from "unbuild"`,
2796
3142
  ``,
@@ -2808,51 +3154,54 @@ function generateUnbuild(generator) {
2808
3154
  }
2809
3155
  buildConfigLines.push(` },`);
2810
3156
  buildConfigLines.push(`})`);
2811
- const isStealth = generator.isStealthConfig() && !isMonorepo;
3157
+ const isStealth = builder.isStealthConfig() && !isMonorepo;
2812
3158
  if (isStealth) {
2813
- generator.addFile(`.config/build.config.${ext}`, {
3159
+ builder.addFile(`.config/build.config.${ext}`, {
2814
3160
  type: "text",
2815
3161
  content: buildConfigLines.join("\n")
2816
3162
  });
2817
- generator.addScript("build", `unbuild --config .config/build.config.${ext}`);
3163
+ builder.addScripts(packageJsonScripts.build.unbuild(`.config/build.config.${ext}`));
2818
3164
  } else {
2819
- generator.addFile(`build.config.${ext}`, {
3165
+ builder.addFile(`build.config.${ext}`, {
2820
3166
  type: "text",
2821
3167
  content: buildConfigLines.join("\n")
2822
3168
  });
2823
- generator.addScript("build", "unbuild");
3169
+ builder.addScripts(packageJsonScripts.build.unbuild());
2824
3170
  }
2825
- generator.inject(
3171
+ builder.inject(
2826
3172
  "readme-libraries",
2827
3173
  "[unbuild](https://github.com/unjs/unbuild) - Unified JavaScript build system"
2828
3174
  );
2829
3175
  }
2830
3176
 
2831
- function generateVitest(generator) {
2832
- generator.addDevDependency("vitest");
2833
- const template = generator.options.template ?? "vanilla";
3177
+ function planVitest(builder, options) {
3178
+ if (options == null) {
3179
+ return;
3180
+ }
3181
+ builder.addDevDependency("vitest");
3182
+ const template = builder.options.template ?? "vanilla";
2834
3183
  const baseTemplate = getBaseTemplate(template);
2835
3184
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2836
3185
  if (isReact) {
2837
- generator.addDevDependency("@testing-library/react");
2838
- generator.addDevDependency("@testing-library/dom");
2839
- generator.addDevDependency("jsdom");
3186
+ builder.addDevDependency("@testing-library/react");
3187
+ builder.addDevDependency("@testing-library/dom");
3188
+ builder.addDevDependency("jsdom");
2840
3189
  }
2841
3190
  if (isReact) {
2842
- generator.configureVite({ test: { environment: "jsdom" } });
3191
+ builder.configureVite({ test: { environment: "jsdom" } });
2843
3192
  }
2844
- generator.addScript("test", "vitest");
2845
- generator.inject(
3193
+ builder.addScripts(packageJsonScripts.test.vitest);
3194
+ builder.inject(
2846
3195
  "readme-tools",
2847
3196
  "[Vitest](https://vitest.dev/) - Fast unit test framework powered by Vite"
2848
3197
  );
2849
3198
  }
2850
3199
 
2851
- function generateViverse(generator, options) {
2852
- if (options == null || getPackageManagerName(generator.options.packageManager) !== "npm") {
3200
+ function planViverse(builder, options) {
3201
+ if (options == null || getPackageManagerName(builder.options.packageManager) !== "npm") {
2853
3202
  return;
2854
3203
  }
2855
- generator.addFile(".github/workflows/viverse.yml", {
3204
+ builder.addFile(".github/workflows/viverse.yml", {
2856
3205
  type: "text",
2857
3206
  content: `name: Deploy to Viverse
2858
3207
 
@@ -2903,12 +3252,12 @@ jobs:
2903
3252
  run: npx viverse-cli auth login -e \${{ secrets.VIVERSE_EMAIL }} -p \${{ secrets.VIVERSE_PASSWORD }}
2904
3253
 
2905
3254
  - name: Deploy to Viverse
2906
- run: npx viverse-cli app publish ./dist --auto-create-app --name ${generator.options.name}
3255
+ run: npx viverse-cli app publish ./dist --auto-create-app --name ${builder.options.name}
2907
3256
 
2908
3257
  `
2909
3258
  });
2910
- generator.addDependency("@viverse/cli");
2911
- generator.inject(
3259
+ builder.addDependency("@viverse/cli");
3260
+ builder.inject(
2912
3261
  "readme-start",
2913
3262
  `A GitHub CI/CD workflow for publishing to Viverse is configured.
2914
3263
 
@@ -2921,36 +3270,36 @@ You can also upload your project manually using the Viverse CLI:
2921
3270
  \`\`\`bash
2922
3271
  viverse-cli auth login -e <email> -p <password>
2923
3272
  npm run build
2924
- viverse-cli app publish ./dist --auto-create-app --name ${generator.options.name}
3273
+ viverse-cli app publish ./dist --auto-create-app --name ${builder.options.name}
2925
3274
  \`\`\`
2926
3275
  `
2927
3276
  );
2928
3277
  }
2929
3278
 
2930
- function generateXr(generator, options) {
3279
+ function planXr(builder, options) {
2931
3280
  if (options == null || options === false) {
2932
3281
  return;
2933
3282
  }
2934
3283
  if (options === true) {
2935
3284
  options = {};
2936
3285
  }
2937
- generator.addDependency("@react-three/xr");
2938
- generator.addDependency("@vitejs/plugin-basic-ssl");
2939
- generator.inject("import", "import { XR, createXRStore } from '@react-three/xr'");
2940
- generator.inject(
3286
+ builder.addDependency("@react-three/xr");
3287
+ builder.addDependency("@vitejs/plugin-basic-ssl");
3288
+ builder.inject("import", "import { XR, createXRStore } from '@react-three/xr'");
3289
+ builder.inject(
2941
3290
  `global-start`,
2942
3291
  `const store = createXRStore(${JSON.stringify(options.storeOptions ?? {})})`
2943
3292
  );
2944
- generator.inject("scene-start", "<XR store={store}>");
2945
- generator.inject("scene-end", "</XR>");
2946
- generator.inject("vite-config-import", "import basicSsl from '@vitejs/plugin-basic-ssl'");
2947
- generator.configureVite({
3293
+ builder.inject("scene-start", "<XR store={store}>");
3294
+ builder.inject("scene-end", "</XR>");
3295
+ builder.inject("vite-config-import", "import basicSsl from '@vitejs/plugin-basic-ssl';");
3296
+ builder.configureVite({
2948
3297
  server: {
2949
3298
  host: true
2950
3299
  },
2951
3300
  plugins: ["$raw:basicSsl()"]
2952
3301
  });
2953
- generator.inject(
3302
+ builder.inject(
2954
3303
  "dom-start",
2955
3304
  `<div style={{
2956
3305
  display: "flex",
@@ -2982,55 +3331,226 @@ function generateXr(generator, options) {
2982
3331
  Enter VR
2983
3332
  </button></div>`
2984
3333
  );
2985
- generator.inject(
3334
+ builder.inject(
2986
3335
  "readme-libraries",
2987
3336
  `[@react-three/xr](https://pmndrs.github.io/xr/docs/) - VR/AR support for @react-three/fiber`
2988
3337
  );
2989
3338
  }
2990
3339
 
2991
- function generateZustand(generator, options) {
3340
+ function planZustand(builder, options) {
2992
3341
  if (options == null) {
2993
3342
  return;
2994
3343
  }
2995
- generator.addDependency("zustand");
2996
- generator.inject(
3344
+ builder.addDependency("zustand");
3345
+ builder.inject(
2997
3346
  "readme-libraries",
2998
3347
  `[zustand](https://zustand.docs.pmnd.rs/) - small, fast and scalable state-management solution`
2999
3348
  );
3000
3349
  }
3001
3350
 
3002
- function merge(target, modification) {
3003
- if (modification == null) {
3004
- throw new Error(`Cannot merge "${modification}" modification into target "${target}"`);
3005
- }
3006
- if (target == null) {
3007
- return modification;
3008
- }
3009
- if (Array.isArray(target)) {
3010
- if (!Array.isArray(modification)) {
3011
- throw new Error(
3012
- `Cannot merge non-array modification "${modification}" into array target "${target}"`
3013
- );
3014
- }
3015
- return [...target, ...modification];
3016
- }
3017
- if (typeof target === "object") {
3018
- if (typeof modification != "object") {
3019
- throw new Error(
3020
- `Cannot merge non-object modification "${modification}" into object target "${target}"`
3021
- );
3351
+ function resolveProjectPlanInput(options) {
3352
+ const packageManager = options.packageManager ?? { name: "pnpm" };
3353
+ return {
3354
+ project: {
3355
+ githubUserName: options.githubUserName,
3356
+ githubRepoName: options.githubRepoName,
3357
+ name: options.name,
3358
+ projectType: options.projectType,
3359
+ template: options.template
3360
+ },
3361
+ aiAgents: {
3362
+ tool: "ai-agents",
3363
+ config: {
3364
+ platforms: options.aiPlatforms ?? []
3365
+ }
3366
+ },
3367
+ formatter: {
3368
+ tool: options.formatter ?? "prettier",
3369
+ config: structuredClone(defaultFormatterMetaConfig)
3370
+ },
3371
+ linter: {
3372
+ tool: options.linter ?? "oxlint",
3373
+ config: structuredClone(defaultLinterMetaConfig)
3374
+ },
3375
+ testing: {
3376
+ tool: options.testing ?? (options.projectType === "library" ? "vitest" : "none"),
3377
+ config: {}
3378
+ },
3379
+ typescript: {
3380
+ tool: "typescript",
3381
+ config: {
3382
+ configStrategy: options.configStrategy
3383
+ }
3384
+ },
3385
+ ide: {
3386
+ tool: options.ide ?? "vscode",
3387
+ config: {}
3388
+ },
3389
+ packageManager: {
3390
+ tool: packageManager.name,
3391
+ config: {
3392
+ version: packageManager.version,
3393
+ pnpmManageVersions: options.pnpmManageVersions
3394
+ }
3395
+ },
3396
+ libraryBundler: {
3397
+ tool: options.libraryBundler ?? "unbuild",
3398
+ config: {}
3399
+ },
3400
+ features: {
3401
+ fiber: options.fiber,
3402
+ handle: options.handle,
3403
+ drei: options.drei,
3404
+ koota: options.koota,
3405
+ leva: options.leva,
3406
+ offscreen: options.offscreen,
3407
+ postprocessing: options.postprocessing,
3408
+ rapier: options.rapier,
3409
+ triplex: options.triplex,
3410
+ viverse: options.viverse,
3411
+ uikit: options.uikit,
3412
+ xr: options.xr,
3413
+ zustand: options.zustand,
3414
+ githubPages: options.githubPages
3415
+ },
3416
+ context: {
3417
+ dependencies: options.dependencies,
3418
+ engine: options.engine,
3419
+ files: options.files,
3420
+ injections: options.injections,
3421
+ replacements: options.replacements,
3422
+ versions: options.versions,
3423
+ workspaceRoot: options.workspaceRoot,
3424
+ workspaceDependencies: options.workspaceDependencies
3022
3425
  }
3023
- const result = { ...target };
3024
- for (const modificationKey in modification) {
3025
- result[modificationKey] = merge(target[modificationKey], modification[modificationKey]);
3426
+ };
3427
+ }
3428
+ function projectPlanInputToOptions(input) {
3429
+ return {
3430
+ ...input.project,
3431
+ aiPlatforms: input.aiAgents.config.platforms.length > 0 ? input.aiAgents.config.platforms : void 0,
3432
+ formatter: input.formatter.tool,
3433
+ linter: input.linter.tool,
3434
+ testing: input.testing.tool,
3435
+ configStrategy: input.typescript.config.configStrategy,
3436
+ ide: input.ide.tool,
3437
+ packageManager: packageManagerSpecFromInput(input.packageManager),
3438
+ pnpmManageVersions: input.packageManager.config.pnpmManageVersions,
3439
+ libraryBundler: input.libraryBundler.tool,
3440
+ ...input.features,
3441
+ dependencies: input.context.dependencies,
3442
+ engine: input.context.engine,
3443
+ files: input.context.files,
3444
+ injections: input.context.injections,
3445
+ replacements: input.context.replacements,
3446
+ versions: input.context.versions,
3447
+ workspaceRoot: input.context.workspaceRoot,
3448
+ workspaceDependencies: input.context.workspaceDependencies
3449
+ };
3450
+ }
3451
+ function resolveWorkspacePlanInput(params) {
3452
+ return {
3453
+ project: {
3454
+ name: params.name
3455
+ },
3456
+ aiAgents: {
3457
+ tool: "ai-agents",
3458
+ config: {
3459
+ platforms: params.aiPlatforms ?? []
3460
+ }
3461
+ },
3462
+ formatter: {
3463
+ tool: params.formatter,
3464
+ config: structuredClone(defaultFormatterMetaConfig)
3465
+ },
3466
+ linter: {
3467
+ tool: params.linter,
3468
+ config: structuredClone(defaultLinterMetaConfig)
3469
+ },
3470
+ ide: {
3471
+ tool: params.ide ?? "vscode",
3472
+ config: {}
3473
+ },
3474
+ packageManager: {
3475
+ tool: params.packageManager.name,
3476
+ config: {
3477
+ version: params.packageManager.version,
3478
+ pnpmManageVersions: params.pnpmManageVersions
3479
+ }
3480
+ },
3481
+ context: {
3482
+ engine: params.engine,
3483
+ pnpmManageVersions: params.pnpmManageVersions,
3484
+ versions: params.versions
3026
3485
  }
3027
- return result;
3028
- }
3029
- console.warn(`target "${target}" is overwritten with modification "${modification}"`);
3030
- return modification;
3486
+ };
3487
+ }
3488
+ function workspacePlanInputToMonorepoParams(input) {
3489
+ return {
3490
+ name: input.project.name,
3491
+ linter: input.linter.tool,
3492
+ formatter: input.formatter.tool,
3493
+ packageManager: packageManagerSpecFromInput(input.packageManager),
3494
+ pnpmManageVersions: input.context.pnpmManageVersions,
3495
+ engine: input.context.engine,
3496
+ versions: input.context.versions,
3497
+ aiPlatforms: input.aiAgents.config.platforms.length > 0 ? input.aiAgents.config.platforms : void 0,
3498
+ ide: input.ide.tool
3499
+ };
3500
+ }
3501
+ function isProjectPlanInput(input) {
3502
+ return "project" in input && "formatter" in input && typeof input.formatter === "object";
3503
+ }
3504
+ function isWorkspacePlanInput(input) {
3505
+ return "project" in input && "formatter" in input && typeof input.formatter === "object";
3506
+ }
3507
+ function packageManagerSpecFromInput(packageManager) {
3508
+ return {
3509
+ name: packageManager.tool,
3510
+ version: packageManager.config.version
3511
+ };
3512
+ }
3513
+
3514
+ async function resolveProjectFacts(input) {
3515
+ const options = projectPlanInputToOptions(input);
3516
+ options.packageManager = await resolvePackageManager(options);
3517
+ options.engine = await resolveEngine(options);
3518
+ options.versions = await resolveProjectPackageVersions(options);
3519
+ return resolveProjectPlanInput(options);
3520
+ }
3521
+ async function resolveWorkspaceFacts(input) {
3522
+ const params = workspacePlanInputToMonorepoParams(input);
3523
+ const options = {
3524
+ name: params.name,
3525
+ linter: params.linter,
3526
+ formatter: params.formatter,
3527
+ packageManager: params.packageManager,
3528
+ engine: params.engine,
3529
+ pnpmManageVersions: params.pnpmManageVersions,
3530
+ versions: params.versions
3531
+ };
3532
+ const packageManager = await resolvePackageManager(options);
3533
+ const engine = await resolveEngine(options);
3534
+ const versions = await resolveMonorepoRootPackageVersions({
3535
+ linter: params.linter,
3536
+ formatter: params.formatter,
3537
+ engine,
3538
+ versions: params.versions
3539
+ });
3540
+ return resolveWorkspacePlanInput({
3541
+ ...params,
3542
+ packageManager,
3543
+ engine,
3544
+ versions
3545
+ });
3031
3546
  }
3032
3547
 
3033
- function generate(options) {
3548
+ async function planProject(input) {
3549
+ const planInput = isProjectPlanInput(input) ? input : resolveProjectPlanInput(input);
3550
+ return createProjectPlan(await resolveProjectFacts(planInput));
3551
+ }
3552
+ function createProjectPlan(planInput) {
3553
+ const options = projectPlanInputToOptions(planInput);
3034
3554
  const clonedOptions = structuredClone(options);
3035
3555
  const template = clonedOptions.template ?? "vanilla";
3036
3556
  const baseTemplate = getBaseTemplate(template);
@@ -3039,7 +3559,8 @@ function generate(options) {
3039
3559
  const isReact = baseTemplate === "react";
3040
3560
  const isR3f = baseTemplate === "r3f";
3041
3561
  const isLibrary = clonedOptions.projectType === "library";
3042
- const libraryBundler = clonedOptions.libraryBundler ?? "unbuild";
3562
+ const libraryBundler = planInput.libraryBundler.tool;
3563
+ const ide = planInput.ide.tool;
3043
3564
  const files = {
3044
3565
  ...clonedOptions.files
3045
3566
  };
@@ -3073,7 +3594,7 @@ function generate(options) {
3073
3594
  }
3074
3595
  }
3075
3596
  if (language === "typescript") {
3076
- const tsResult = generateTypescriptConfig({
3597
+ const tsResult = renderTypescriptConfig({
3077
3598
  baseTemplate,
3078
3599
  useConfigPackage: clonedOptions.workspaceRoot != null,
3079
3600
  configStrategy: clonedOptions.configStrategy,
@@ -3085,12 +3606,9 @@ function generate(options) {
3085
3606
  }
3086
3607
  const codeSnippets = {};
3087
3608
  const vscodeSettings = {};
3088
- const scripts = isLibrary ? {} : {
3089
- dev: "vite",
3090
- build: "vite build"
3091
- };
3609
+ const scripts = {};
3092
3610
  if (!isLibrary && (isReact || isR3f)) {
3093
- codeSnippets["vite-config-import"] = ["import react from '@vitejs/plugin-react'"];
3611
+ codeSnippets["vite-config-import"] = ["import react from '@vitejs/plugin-react';"];
3094
3612
  }
3095
3613
  if (!isLibrary && isR3f) {
3096
3614
  codeSnippets["import"] = [`import { Canvas } from "@react-three/fiber"`];
@@ -3107,7 +3625,7 @@ function generate(options) {
3107
3625
  viteConfig.resolve = { dedupe: ["three"] };
3108
3626
  }
3109
3627
  const isMonorepoPackage = clonedOptions.workspaceRoot != null;
3110
- const generator = {
3628
+ const builder = {
3111
3629
  options: clonedOptions,
3112
3630
  versions,
3113
3631
  getVersion(name2) {
@@ -3137,8 +3655,11 @@ function generate(options) {
3137
3655
  addFile(path, content) {
3138
3656
  files[path] = content;
3139
3657
  },
3658
+ addScripts(nextScripts) {
3659
+ Object.assign(scripts, nextScripts);
3660
+ },
3140
3661
  addScript(name2, command) {
3141
- scripts[name2] = command;
3662
+ this.addScripts({ [name2]: command });
3142
3663
  },
3143
3664
  inject(location, code) {
3144
3665
  let entries = codeSnippets[location];
@@ -3158,71 +3679,61 @@ function generate(options) {
3158
3679
  }
3159
3680
  };
3160
3681
  if (isR3f) {
3161
- generateDrei(generator, clonedOptions.drei);
3162
- generateHandle(generator, clonedOptions.handle);
3163
- generateKoota(generator, clonedOptions.koota);
3164
- generateLeva(generator, clonedOptions.leva);
3165
- generateOffscreen(generator, clonedOptions.offscreen);
3166
- generatePostprocessing(generator, clonedOptions.postprocessing);
3167
- generateRapier(generator, clonedOptions.rapier);
3168
- generateUikit(generator, clonedOptions.uikit);
3169
- generateXr(generator, clonedOptions.xr);
3170
- generateZustand(generator, clonedOptions.zustand);
3171
- generateFiber(generator, clonedOptions.fiber);
3172
- generateTriplex(generator, clonedOptions.triplex);
3173
- generateViverse(generator, clonedOptions.viverse);
3682
+ planDrei(builder, planInput.features.drei);
3683
+ planHandle(builder, planInput.features.handle);
3684
+ planKoota(builder, planInput.features.koota);
3685
+ planLeva(builder, planInput.features.leva);
3686
+ planOffscreen(builder, planInput.features.offscreen);
3687
+ planPostprocessing(builder, planInput.features.postprocessing);
3688
+ planRapier(builder, planInput.features.rapier);
3689
+ planUikit(builder, planInput.features.uikit);
3690
+ planXr(builder, planInput.features.xr);
3691
+ planZustand(builder, planInput.features.zustand);
3692
+ planFiber(builder, planInput.features.fiber);
3693
+ planTriplex(builder, planInput.features.triplex);
3694
+ planViverse(builder, planInput.features.viverse);
3174
3695
  }
3175
3696
  if (!isLibrary) {
3176
- generateGithubPages(generator, clonedOptions.githubPages);
3697
+ planGithubPages(builder, planInput.features.githubPages);
3177
3698
  }
3178
3699
  if (isLibrary) {
3179
3700
  if (libraryBundler === "unbuild") {
3180
- generateUnbuild(generator);
3701
+ planUnbuild(builder);
3181
3702
  } else if (libraryBundler === "tsdown") {
3182
- generateTsdown(generator);
3703
+ planTsdown(builder);
3183
3704
  }
3184
- const packageManager2 = getPackageManagerName(clonedOptions.packageManager);
3185
- generator.addScript("release", `${packageManager2} run build && ${packageManager2} publish`);
3186
3705
  }
3187
- const testing = clonedOptions.testing ?? (isLibrary ? "vitest" : "none");
3706
+ const testing = planInput.testing.tool;
3188
3707
  if (testing === "vitest") {
3189
- generateVitest(generator);
3190
- }
3191
- const linter = clonedOptions.linter;
3192
- const formatter = clonedOptions.formatter;
3193
- if (linter === "eslint") {
3194
- generateEslint(generator);
3195
- generator.addVscodeSetting("biome.enabled", false);
3196
- generator.addVscodeSetting("oxc.enable", false);
3197
- } else if (linter === "oxlint") {
3198
- generateOxlint(generator);
3199
- generator.addVscodeSetting("eslint.enable", false);
3200
- generator.addVscodeSetting("biome.enabled", false);
3201
- } else if (linter === "biome") {
3202
- generateBiome(generator, {
3203
- linter: true,
3204
- formatter: formatter === "biome"
3708
+ planVitest(builder, planInput.testing);
3709
+ }
3710
+ const linter = planInput.linter.tool;
3711
+ const formatter = planInput.formatter.tool;
3712
+ if (planInput.linter.tool === "eslint") {
3713
+ planEslint(builder, planInput.linter);
3714
+ } else if (planInput.linter.tool === "oxlint") {
3715
+ planOxlint(builder, planInput.linter);
3716
+ } else if (planInput.linter.tool === "biome") {
3717
+ planBiome(builder, {
3718
+ linter: planInput.linter,
3719
+ formatter: planInput.formatter.tool === "biome" ? planInput.formatter : void 0
3205
3720
  });
3206
- generator.addVscodeSetting("eslint.enable", false);
3207
- generator.addVscodeSetting("oxc.enable", false);
3208
3721
  }
3209
- if (formatter === "prettier") {
3210
- generatePrettier(generator);
3211
- } else if (formatter === "oxfmt") {
3212
- generateOxfmt(generator);
3213
- } else if (formatter === "biome" && linter !== "biome") {
3214
- generateBiome(generator, { linter: false, formatter: true });
3215
- generator.addVscodeSetting("eslint.enable", false);
3216
- generator.addVscodeSetting("oxc.enable", false);
3722
+ if (planInput.formatter.tool === "prettier") {
3723
+ planPrettier(builder, planInput.formatter);
3724
+ } else if (planInput.formatter.tool === "oxfmt") {
3725
+ planOxfmt(builder, planInput.formatter);
3726
+ } else if (planInput.formatter.tool === "biome" && planInput.linter.tool !== "biome") {
3727
+ planBiome(builder, { formatter: planInput.formatter });
3217
3728
  }
3218
3729
  for (const { code, location } of clonedOptions.injections ?? []) {
3219
- generator.inject(location, code);
3730
+ builder.inject(location, code);
3220
3731
  }
3221
3732
  if (!isLibrary) {
3222
- files["vite.config.ts"] = generateViteConfig({ viteConfig, codeSnippets });
3733
+ files["vite.config.ts"] = renderViteConfig({ viteConfig, codeSnippets });
3223
3734
  }
3224
3735
  const packageManager = getPackageManagerName(options.packageManager);
3225
- files["README.md"] = generateReadme({
3736
+ files["README.md"] = renderReadme({
3226
3737
  name,
3227
3738
  baseTemplate,
3228
3739
  isLibrary,
@@ -3232,7 +3743,7 @@ function generate(options) {
3232
3743
  });
3233
3744
  Object.assign(
3234
3745
  files,
3235
- generateSourceFiles({
3746
+ renderSourceFiles({
3236
3747
  name,
3237
3748
  baseTemplate,
3238
3749
  language,
@@ -3244,7 +3755,7 @@ function generate(options) {
3244
3755
  if (testing === "vitest") {
3245
3756
  Object.assign(
3246
3757
  files,
3247
- generateTestFiles({
3758
+ renderTestFiles({
3248
3759
  baseTemplate,
3249
3760
  language,
3250
3761
  isLibrary
@@ -3253,7 +3764,7 @@ function generate(options) {
3253
3764
  }
3254
3765
  Object.assign(
3255
3766
  files,
3256
- generatePackageJson({
3767
+ renderPackageJson({
3257
3768
  name,
3258
3769
  language,
3259
3770
  isLibrary,
@@ -3265,25 +3776,54 @@ function generate(options) {
3265
3776
  workspaceDependencies: clonedOptions.workspaceDependencies
3266
3777
  }).files
3267
3778
  );
3268
- if (!isMonorepoPackage) {
3269
- Object.assign(files, generateVscodeFiles$1({ codeSnippets, vscodeSettings }));
3779
+ if (!isMonorepoPackage && ide === "vscode") {
3780
+ Object.assign(
3781
+ files,
3782
+ renderVscodeFiles$1({
3783
+ codeSnippets,
3784
+ vscodeSettings,
3785
+ linter,
3786
+ formatter,
3787
+ configStrategy: clonedOptions.configStrategy,
3788
+ isMonorepo: false,
3789
+ packageManager
3790
+ })
3791
+ );
3270
3792
  }
3271
3793
  if (!isMonorepoPackage) {
3272
- files[".gitignore"] = generateGitignore("standalone");
3273
- files[".gitattributes"] = { type: "text", content: GitAttributes };
3794
+ files[".editorconfig"] = renderEditorConfig();
3795
+ files[".gitignore"] = renderGitignore("standalone");
3796
+ files[".gitattributes"] = { type: "text", content: gitAttributesContent };
3274
3797
  }
3275
- if (!isMonorepoPackage && clonedOptions.aiPlatforms?.length) {
3276
- generateAiFiles(files, {
3798
+ if (!isMonorepoPackage && planInput.aiAgents.config.platforms.length > 0) {
3799
+ renderAiFiles(files, {
3277
3800
  name,
3278
3801
  packageManager: getPackageManagerName(clonedOptions.packageManager),
3279
3802
  linter: clonedOptions.linter ?? "oxlint",
3280
3803
  formatter: clonedOptions.formatter ?? "prettier",
3281
3804
  isMonorepo: false,
3282
3805
  configStrategy: clonedOptions.configStrategy,
3283
- platforms: clonedOptions.aiPlatforms
3806
+ hasTypecheck: language === "typescript",
3807
+ platforms: planInput.aiAgents.config.platforms
3284
3808
  });
3285
3809
  }
3286
- return files;
3810
+ return {
3811
+ files,
3812
+ dependencies,
3813
+ devDependencies,
3814
+ peerDependencies,
3815
+ scripts,
3816
+ vscodeSettings,
3817
+ vscodeExtensions: [...new Set(codeSnippets["vscode-extension-suggestion"] ?? [])],
3818
+ injections: Object.entries(codeSnippets).flatMap(
3819
+ ([location, entries]) => (entries ?? []).map((code) => ({
3820
+ location,
3821
+ code
3822
+ }))
3823
+ ),
3824
+ replacements,
3825
+ warnings: []
3826
+ };
3287
3827
  }
3288
3828
  function resolveDependencySemver(name, versions, options = {}) {
3289
3829
  if (options.version != null) {
@@ -3292,4 +3832,22 @@ function resolveDependencySemver(name, versions, options = {}) {
3292
3832
  return formatResolvedPackageVersion(versions, name, options.prefix);
3293
3833
  }
3294
3834
 
3295
- export { ALL_AI_PLATFORMS as A, validatePackageName as B, generateMonorepo as C, getLatestNodeVersion as D, getLatestNpmCliVersion as E, getLatestNpmMajorVersion as F, getLatestNpmVersion as G, getLatestPnpmVersion as H, getLatestYarnVersion as I, monorepo as J, 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 };
3835
+ async function planWorkspace(input) {
3836
+ const planInput = isWorkspacePlanInput(input) ? input : resolveWorkspacePlanInput(input);
3837
+ const resolvedInput = await resolveWorkspaceFacts(planInput);
3838
+ const { files } = renderMonorepo(workspacePlanInputToMonorepoParams(resolvedInput));
3839
+ return {
3840
+ files,
3841
+ dependencies: {},
3842
+ devDependencies: {},
3843
+ peerDependencies: {},
3844
+ scripts: {},
3845
+ vscodeSettings: {},
3846
+ vscodeExtensions: [],
3847
+ injections: [],
3848
+ replacements: [],
3849
+ warnings: []
3850
+ };
3851
+ }
3852
+
3853
+ 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, renderViteConfig as P, packageJsonScripts as Q, resolveDefaultPackageJsonScripts as R, formatResolvedPackageVersion as S, renderOxlintConfig as T, 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 };