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.
@@ -9,62 +9,7 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
9
9
 
10
10
  const color__default = /*#__PURE__*/_interopDefaultCompat(color);
11
11
 
12
- const HtmlContent = `<!DOCTYPE html>
13
- <html lang="en">
14
- <head>
15
- <meta charset="UTF-8">
16
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
17
- <title>$title</title>
18
- </head>
19
- <body style="margin: 0; overscroll-behavior: none; user-select: none; touch-action: none;">
20
- <script type="module" src="$indexPath"><\/script>
21
- <div style="width: 100dvw; height: 100dvh; overflow: hidden;" id="root"></div>
22
- </body>
23
- </html>`;
24
- const ViteHtmlContent = `<!DOCTYPE html>
25
- <html lang="en">
26
- <head>
27
- <meta charset="UTF-8">
28
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
29
- <title>$title</title>
30
- </head>
31
- <body>
32
- <div id="app"></div>
33
- <script type="module" src="$indexPath"><\/script>
34
- </body>
35
- </html>`;
36
- const IndexContent = `import { StrictMode } from 'react'
37
- import { createRoot } from 'react-dom/client'
38
- import { App } from './app.js'
39
-
40
- createRoot(document.getElementById('root')!).render(
41
- <StrictMode>
42
- <App />
43
- </StrictMode>,
44
- )`;
45
- const ViteIndexContent = `import './style.css'
46
-
47
- document.querySelector('#app')!.innerHTML = \`
48
- <h1>Hello Vite!</h1>
49
- <p>Edit src/main.ts and save to see HMR in action.</p>
50
- \``;
51
- const ViteStyleContent = `body {
52
- font-family: system-ui, -apple-system, sans-serif;
53
- margin: 0;
54
- padding: 2rem;
55
- min-height: 100vh;
56
- background: #1a1a1a;
57
- color: #fff;
58
- }
59
-
60
- h1 {
61
- color: #646cff;
62
- }
63
-
64
- a {
65
- color: #646cff;
66
- }`;
67
- const GitAttributes = [
12
+ const gitAttributesContent = [
68
13
  "* text eol=lf",
69
14
  "*.png binary",
70
15
  "*.jpg binary",
@@ -89,55 +34,6 @@ const GitAttributes = [
89
34
  "*.glb binary",
90
35
  "*.gltf binary"
91
36
  ].join("\n");
92
- const defaultFormatterConfig = {
93
- printWidth: 102,
94
- tabWidth: 2,
95
- useTabs: false,
96
- semi: true,
97
- singleQuote: true,
98
- trailingComma: "es5",
99
- bracketSpacing: true,
100
- arrowParens: "always"
101
- };
102
- const defaultPrettierConfig = {
103
- $schema: "https://json.schemastore.org/prettierrc",
104
- ...defaultFormatterConfig,
105
- overrides: [
106
- {
107
- files: ["*.md", "**/*.md"],
108
- options: { semi: false }
109
- },
110
- {
111
- files: ["*.yml", "*.yaml", "**/*.yml", "**/*.yaml"],
112
- options: { semi: false }
113
- }
114
- ]
115
- };
116
- const defaultOxfmtConfig = {
117
- printWidth: defaultFormatterConfig.printWidth,
118
- tabWidth: defaultFormatterConfig.tabWidth,
119
- useTabs: defaultFormatterConfig.useTabs,
120
- semi: defaultFormatterConfig.semi,
121
- singleQuote: defaultFormatterConfig.singleQuote,
122
- trailingComma: defaultFormatterConfig.trailingComma,
123
- bracketSpacing: defaultFormatterConfig.bracketSpacing,
124
- arrowParens: defaultFormatterConfig.arrowParens
125
- };
126
- const defaultLinterConfig = {
127
- ignorePatterns: ["dist"],
128
- rules: {
129
- noUnusedVars: {
130
- level: "warn",
131
- argsIgnorePattern: "^_",
132
- varsIgnorePattern: "^_",
133
- caughtErrorsIgnorePattern: "^_"
134
- },
135
- noUnusedExpressions: {
136
- level: "warn",
137
- allowShortCircuit: true
138
- }
139
- }
140
- };
141
37
 
142
38
  const ALL_AI_PLATFORMS = ["agents", "claude"];
143
39
  const AI_PLATFORM_LABELS = {
@@ -148,12 +44,15 @@ const AI_PLATFORM_HINTS = {
148
44
  agents: "OpenAI, Cursor, Windsurf, etc.",
149
45
  claude: "Claude Code"
150
46
  };
151
- function generateAiFiles(files, params) {
152
- const { platforms, isMonorepo, configStrategy, ...rest } = params;
47
+ function renderAiFiles(files, params) {
48
+ const { platforms, isMonorepo, configStrategy, hasTypecheck, ...rest } = params;
153
49
  if (platforms.length === 0) return;
154
50
  const content = generateWorkspace({
155
51
  ...rest,
156
- isMonorepo: !!isMonorepo});
52
+ isMonorepo: !!isMonorepo,
53
+ configStrategy: configStrategy ?? "stealth",
54
+ hasTypecheck: hasTypecheck ?? false
55
+ });
157
56
  const pointer = "See [`AGENTS.md`](./Agents.md) for agent context.\n";
158
57
  const hasAgents = platforms.includes("agents");
159
58
  const hasClaude = platforms.includes("claude");
@@ -168,31 +67,115 @@ function generateAiFiles(files, params) {
168
67
  }
169
68
  }
170
69
  function generateWorkspace(ctx) {
171
- const { name, packageManager, linter, formatter, isMonorepo} = ctx;
70
+ const { packageManager, linter, formatter, hasTypecheck } = ctx;
71
+ const exampleFiles = "src/App.tsx src/core/systems/move-entity.ts";
72
+ const commands = getAfterEditingCommands(ctx, exampleFiles);
172
73
  const sections = [
173
- `# ${name}`,
74
+ "# Workspace Tools",
174
75
  "",
175
- `- **Type:** ${isMonorepo ? "pnpm monorepo" : "standalone project"}`,
176
76
  `- **Package Manager:** ${packageManager}`,
177
77
  `- **Linter:** ${linter}`,
178
78
  `- **Formatter:** ${formatter}`,
179
79
  "",
180
- "## Commands",
181
- "",
182
- `- \`${packageManager} test\` \u2014 run tests`,
183
- `- \`${packageManager} build\` \u2014 build`,
184
- `- \`${packageManager} lint\` and \`${packageManager} format\` \u2014 run before committing`
80
+ "## After Editing",
81
+ ""
185
82
  ];
186
- if (isMonorepo) {
83
+ if (hasTypecheck) {
187
84
  sections.push(
188
- "",
189
- "- Use `workspace:*` for internal dependencies",
190
- `- New packages: \`${packageManager} create krispya <name> --workspace\``
85
+ "\u2705 After editing files, check the types for errors and then format and lint only the files changed for the current task."
86
+ );
87
+ } else {
88
+ sections.push(
89
+ "\u2705 After editing files, format and lint only the files changed for the current task."
191
90
  );
192
91
  }
193
- sections.push("");
92
+ sections.push("", "```sh", "# Example");
93
+ if (hasTypecheck) {
94
+ sections.push(runScript(packageManager, "typecheck"));
95
+ }
96
+ sections.push(
97
+ "# Run format and lint for only files modified",
98
+ commands.format,
99
+ commands.lint,
100
+ "```",
101
+ "",
102
+ "\u274C Avoid unless explicitly approved:",
103
+ "",
104
+ "```sh",
105
+ runScript(packageManager, "format"),
106
+ runScript(packageManager, "lint"),
107
+ "```",
108
+ ""
109
+ );
194
110
  return sections.join("\n");
195
111
  }
112
+ function getAfterEditingCommands(ctx, files) {
113
+ return {
114
+ format: getFormatChangedFilesCommand(ctx, files),
115
+ lint: getLintChangedFilesCommand(ctx, files)
116
+ };
117
+ }
118
+ function getFormatChangedFilesCommand(ctx, files) {
119
+ const exec = getExecCommand(ctx.packageManager);
120
+ if (ctx.formatter === "prettier") {
121
+ const configPath = getPrettierConfigPath(ctx);
122
+ const ignorePath = getPrettierIgnorePath(ctx);
123
+ const configFlag2 = configPath == null ? "" : ` --config ${configPath}`;
124
+ const ignoreFlag = ignorePath == null ? "" : ` --ignore-path ${ignorePath}`;
125
+ return `${exec} prettier${configFlag2}${ignoreFlag} --write ${files}`;
126
+ }
127
+ if (ctx.formatter === "oxfmt") {
128
+ const configPath = getOxfmtConfigPath(ctx);
129
+ return `${exec} oxfmt -c ${configPath} --write ${files}`;
130
+ }
131
+ const configFlag = ctx.isMonorepo || ctx.configStrategy === "root" ? "" : " --config-path .config";
132
+ return `${exec} biome format${configFlag} --write ${files}`;
133
+ }
134
+ function getLintChangedFilesCommand(ctx, files) {
135
+ const exec = getExecCommand(ctx.packageManager);
136
+ if (ctx.linter === "oxlint") {
137
+ if (!ctx.isMonorepo) {
138
+ return runScript(ctx.packageManager, "lint", files);
139
+ }
140
+ return `${exec} oxlint ${files}`;
141
+ }
142
+ if (ctx.linter === "eslint") {
143
+ const configFlag2 = ctx.configStrategy === "stealth" ? " --config .config/eslint.config.js" : "";
144
+ return `${exec} eslint${configFlag2} ${files}`;
145
+ }
146
+ const configFlag = ctx.isMonorepo || ctx.configStrategy === "root" ? "" : " --config-path .config";
147
+ return `${exec} biome lint${configFlag} ${files}`;
148
+ }
149
+ function getPrettierConfigPath(ctx) {
150
+ if (ctx.isMonorepo) return ".config/prettier/base.json";
151
+ if (ctx.configStrategy === "stealth") return ".config/prettier.json";
152
+ return void 0;
153
+ }
154
+ function getPrettierIgnorePath(ctx) {
155
+ if (ctx.isMonorepo) return ".config/prettier/prettierignore";
156
+ if (ctx.configStrategy === "stealth") return ".config/prettierignore";
157
+ return void 0;
158
+ }
159
+ function getOxfmtConfigPath(ctx) {
160
+ if (ctx.isMonorepo) return ".config/oxfmt/base.json";
161
+ if (ctx.configStrategy === "stealth") return ".config/oxfmt.json";
162
+ return "oxfmt.json";
163
+ }
164
+ function runScript(packageManager, script, args) {
165
+ const suffix = args == null ? "" : ` ${args}`;
166
+ if (packageManager === "npm") {
167
+ return `npm run ${script}${args == null ? "" : ` --${suffix}`}`;
168
+ }
169
+ if (packageManager === "yarn") {
170
+ return `yarn ${script}${suffix}`;
171
+ }
172
+ return `${packageManager} ${script}${args == null ? "" : ` --${suffix}`}`;
173
+ }
174
+ function getExecCommand(packageManager) {
175
+ if (packageManager === "npm") return "npm exec --";
176
+ if (packageManager === "yarn") return "yarn exec";
177
+ return `${packageManager} exec`;
178
+ }
196
179
 
197
180
  function getLanguageFromTemplate(template) {
198
181
  return template.endsWith("-js") ? "javascript" : "typescript";
@@ -201,59 +184,49 @@ function getBaseTemplate(template) {
201
184
  return template.replace("-js", "");
202
185
  }
203
186
 
204
- async function getLatestNpmVersion(packageName, fallback) {
205
- try {
206
- const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
207
- const data = await response.json();
208
- return data.version;
209
- } catch {
210
- return fallback;
211
- }
212
- }
213
- function compareNumericSemver(a, b) {
214
- const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
215
- const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
216
- const maxLength = Math.max(aParts.length, bParts.length);
217
- for (let index = 0; index < maxLength; index += 1) {
218
- const difference = (aParts[index] ?? 0) - (bParts[index] ?? 0);
219
- if (difference !== 0) {
220
- return difference;
187
+ function unique(...array) {
188
+ const set = /* @__PURE__ */ new Set();
189
+ for (const arr of array) {
190
+ for (const item of arr) {
191
+ set.add(item);
221
192
  }
222
193
  }
223
- return 0;
194
+ return Array.from(set);
224
195
  }
225
- async function getLatestNpmMajorVersion(packageName, majorVersion, fallback) {
226
- try {
227
- const response = await fetch(`https://registry.npmjs.org/${packageName}`);
228
- const data = await response.json();
229
- const latestMatchingVersion = Object.keys(data.versions ?? {}).filter((version) => version.split(".")[0] === majorVersion).sort((a, b) => compareNumericSemver(b, a))[0];
230
- return latestMatchingVersion ?? fallback;
231
- } catch {
232
- return fallback;
196
+
197
+ function merge(target, modification) {
198
+ const targetLabel = JSON.stringify(target);
199
+ const modificationLabel = JSON.stringify(modification);
200
+ if (modification == null) {
201
+ throw new Error(`Cannot merge "${modificationLabel}" modification into target "${targetLabel}"`);
233
202
  }
234
- }
235
- async function getLatestPnpmVersion() {
236
- return getLatestNpmVersion("pnpm", "10.11.0");
237
- }
238
- async function getLatestYarnVersion() {
239
- return getLatestNpmVersion("yarn", "4.6.0");
240
- }
241
- async function getLatestNpmCliVersion() {
242
- return getLatestNpmVersion("npm", "11.0.0");
243
- }
244
- async function getLatestNodeVersion() {
245
- try {
246
- const response = await fetch("https://nodejs.org/dist/index.json");
247
- const data = await response.json();
248
- const latestVersion = data[0];
249
- if (latestVersion) {
250
- return latestVersion.version.replace(/^v/, "");
203
+ if (target == null) {
204
+ return modification;
205
+ }
206
+ if (Array.isArray(target)) {
207
+ if (!Array.isArray(modification)) {
208
+ throw new Error(
209
+ `Cannot merge non-array modification "${modificationLabel}" into array target "${targetLabel}"`
210
+ );
251
211
  }
252
- return "25.0.0";
253
- } catch {
254
- return "25.0.0";
212
+ return [...target, ...modification];
213
+ }
214
+ if (typeof target === "object") {
215
+ if (typeof modification !== "object") {
216
+ throw new Error(
217
+ `Cannot merge non-object modification "${modificationLabel}" into object target "${targetLabel}"`
218
+ );
219
+ }
220
+ const result = { ...target };
221
+ for (const modificationKey in modification) {
222
+ result[modificationKey] = merge(target[modificationKey], modification[modificationKey]);
223
+ }
224
+ return result;
255
225
  }
226
+ console.warn(`target "${targetLabel}" is overwritten with modification "${modificationLabel}"`);
227
+ return modification;
256
228
  }
229
+
257
230
  function validateNameSegment(segment, label) {
258
231
  if (!segment.length) {
259
232
  return `${label} is required`;
@@ -297,27 +270,83 @@ function validatePackageName(name) {
297
270
  }
298
271
  return validateNameSegment(name, "Package name");
299
272
  }
300
- function parseWorkspaceYamlContent(content) {
301
- const directories = [];
302
- let inPackagesSection = false;
303
- for (const line of content.split("\n")) {
304
- const trimmed = line.trim();
305
- if (trimmed === "packages:") {
306
- inPackagesSection = true;
307
- continue;
308
- }
309
- if (inPackagesSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ") && !trimmed.startsWith("-")) {
310
- break;
311
- }
312
- if (inPackagesSection && trimmed.startsWith("-")) {
313
- const entry = trimmed.slice(1).trim().replace(/^["']|["']$/g, "").replace(/^\.\//, "").replace(/\/\*.*$/, "");
314
- if (entry && !entry.startsWith(".")) {
315
- directories.push(entry);
316
- }
317
- }
318
- }
319
- return directories;
273
+
274
+ function generateRandomName() {
275
+ const adjectives = [
276
+ "red",
277
+ "blue",
278
+ "green",
279
+ "yellow",
280
+ "purple",
281
+ "orange",
282
+ "pink",
283
+ "black",
284
+ "white",
285
+ "tiny",
286
+ "big",
287
+ "small",
288
+ "large",
289
+ "huge",
290
+ "giant",
291
+ "mini",
292
+ "mega",
293
+ "super",
294
+ "happy",
295
+ "sad",
296
+ "angry",
297
+ "calm",
298
+ "quiet",
299
+ "loud",
300
+ "silent",
301
+ "noisy",
302
+ "shiny",
303
+ "dull",
304
+ "bright",
305
+ "dark",
306
+ "fuzzy",
307
+ "smooth",
308
+ "rough",
309
+ "soft"
310
+ ];
311
+ const nouns = [
312
+ "apple",
313
+ "banana",
314
+ "cherry",
315
+ "date",
316
+ "elderberry",
317
+ "fig",
318
+ "grape",
319
+ "honeydew",
320
+ "cat",
321
+ "dog",
322
+ "elephant",
323
+ "fox",
324
+ "giraffe",
325
+ "horse",
326
+ "iguana",
327
+ "jaguar",
328
+ "mountain",
329
+ "river",
330
+ "ocean",
331
+ "desert",
332
+ "forest",
333
+ "jungle",
334
+ "meadow",
335
+ "valley",
336
+ "star",
337
+ "moon",
338
+ "sun",
339
+ "planet",
340
+ "comet",
341
+ "asteroid",
342
+ "galaxy",
343
+ "universe"
344
+ ];
345
+ const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
346
+ const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
347
+ return `${randomAdjective}-${randomNoun}`;
320
348
  }
349
+
321
350
  async function pathExists(path) {
322
351
  try {
323
352
  await promises.access(path, fs.constants.F_OK);
@@ -393,81 +422,82 @@ async function detectTooling(root) {
393
422
  } catch {
394
423
  return { linter: void 0, formatter: void 0 };
395
424
  }
396
- }
397
- function generateRandomName() {
398
- const adjectives = [
399
- "red",
400
- "blue",
401
- "green",
402
- "yellow",
403
- "purple",
404
- "orange",
405
- "pink",
406
- "black",
407
- "white",
408
- "tiny",
409
- "big",
410
- "small",
411
- "large",
412
- "huge",
413
- "giant",
414
- "mini",
415
- "mega",
416
- "super",
417
- "happy",
418
- "sad",
419
- "angry",
420
- "calm",
421
- "quiet",
422
- "loud",
423
- "silent",
424
- "noisy",
425
- "shiny",
426
- "dull",
427
- "bright",
428
- "dark",
429
- "fuzzy",
430
- "smooth",
431
- "rough",
432
- "soft"
433
- ];
434
- const nouns = [
435
- "apple",
436
- "banana",
437
- "cherry",
438
- "date",
439
- "elderberry",
440
- "fig",
441
- "grape",
442
- "honeydew",
443
- "cat",
444
- "dog",
445
- "elephant",
446
- "fox",
447
- "giraffe",
448
- "horse",
449
- "iguana",
450
- "jaguar",
451
- "mountain",
452
- "river",
453
- "ocean",
454
- "desert",
455
- "forest",
456
- "jungle",
457
- "meadow",
458
- "valley",
459
- "star",
460
- "moon",
461
- "sun",
462
- "planet",
463
- "comet",
464
- "asteroid",
465
- "galaxy",
466
- "universe"
467
- ];
468
- const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
469
- const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
470
- return `${randomAdjective}-${randomNoun}`;
425
+ }
426
+
427
+ async function getLatestNpmVersion(packageName, fallback) {
428
+ try {
429
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
430
+ const data = await response.json();
431
+ return data.version;
432
+ } catch {
433
+ return fallback;
434
+ }
435
+ }
436
+ function compareNumericSemver(a, b) {
437
+ const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
438
+ const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
439
+ const maxLength = Math.max(aParts.length, bParts.length);
440
+ for (let index = 0; index < maxLength; index += 1) {
441
+ const difference = (aParts[index] ?? 0) - (bParts[index] ?? 0);
442
+ if (difference !== 0) {
443
+ return difference;
444
+ }
445
+ }
446
+ return 0;
447
+ }
448
+ async function getLatestNpmMajorVersion(packageName, majorVersion, fallback) {
449
+ try {
450
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`);
451
+ const data = await response.json();
452
+ const latestMatchingVersion = Object.keys(data.versions ?? {}).filter((version) => version.split(".")[0] === majorVersion).sort((a, b) => compareNumericSemver(b, a))[0];
453
+ return latestMatchingVersion ?? fallback;
454
+ } catch {
455
+ return fallback;
456
+ }
457
+ }
458
+ async function getLatestPnpmVersion() {
459
+ return getLatestNpmVersion("pnpm", "10.11.0");
460
+ }
461
+ async function getLatestYarnVersion() {
462
+ return getLatestNpmVersion("yarn", "4.6.0");
463
+ }
464
+ async function getLatestNpmCliVersion() {
465
+ return getLatestNpmVersion("npm", "11.0.0");
466
+ }
467
+ async function getLatestNodeVersion() {
468
+ try {
469
+ const response = await fetch("https://nodejs.org/dist/index.json");
470
+ const data = await response.json();
471
+ const latestVersion = data[0];
472
+ if (latestVersion) {
473
+ return latestVersion.version.replace(/^v/, "");
474
+ }
475
+ return "25.0.0";
476
+ } catch {
477
+ return "25.0.0";
478
+ }
479
+ }
480
+
481
+ function parseWorkspaceYamlContent(content) {
482
+ const directories = [];
483
+ let inPackagesSection = false;
484
+ for (const line of content.split("\n")) {
485
+ const trimmed = line.trim();
486
+ if (trimmed === "packages:") {
487
+ inPackagesSection = true;
488
+ continue;
489
+ }
490
+ if (inPackagesSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ") && !trimmed.startsWith("-")) {
491
+ break;
492
+ }
493
+ if (inPackagesSection && trimmed.startsWith("-")) {
494
+ const entry = trimmed.slice(1).trim().replace(/^["']|["']$/g, "").replace(/^\.\//, "").replace(/\/\*.*$/, "");
495
+ if (entry && !entry.startsWith(".")) {
496
+ directories.push(entry);
497
+ }
498
+ }
499
+ }
500
+ return directories;
471
501
  }
472
502
 
473
503
  const PACKAGE_VERSION_DEFINITIONS = {
@@ -495,6 +525,7 @@ const PACKAGE_VERSION_DEFINITIONS = {
495
525
  leva: { fallbackVersion: "0.10.0" },
496
526
  oxfmt: { fallbackVersion: "0.21.0" },
497
527
  oxlint: { fallbackVersion: "1.36.0" },
528
+ "oxlint-tsgolint": { fallbackVersion: "0.22.1" },
498
529
  prettier: { fallbackVersion: "3.4.2" },
499
530
  react: { fallbackVersion: "19.0.0" },
500
531
  "react-dom": { fallbackVersion: "19.0.0" },
@@ -651,6 +682,9 @@ async function resolveMonorepoRootPackageVersions(params) {
651
682
  const packageNames = /* @__PURE__ */ new Set();
652
683
  const explicitVersions = new Set(Object.keys(params.versions ?? {}));
653
684
  addPackageName(packageNames, explicitVersions, getLinterPackage(params.linter));
685
+ if (params.linter === "oxlint") {
686
+ addPackageName(packageNames, explicitVersions, "oxlint-tsgolint");
687
+ }
654
688
  if (params.formatter !== "biome" || params.linter !== "biome") {
655
689
  addPackageName(packageNames, explicitVersions, getFormatterPackage(params.formatter));
656
690
  }
@@ -760,6 +794,9 @@ function collectProjectPackageNames(options) {
760
794
  } else if (linter === "oxlint") {
761
795
  if (!inWorkspace) {
762
796
  addPackageName(packageNames, explicitVersions, "oxlint");
797
+ if (isTypescript) {
798
+ addPackageName(packageNames, explicitVersions, "oxlint-tsgolint");
799
+ }
763
800
  }
764
801
  } else if (linter === "biome") {
765
802
  addPackageName(packageNames, explicitVersions, "@biomejs/biome");
@@ -794,7 +831,7 @@ function isEnabledOption(option) {
794
831
  return option != null && option !== false;
795
832
  }
796
833
 
797
- function generateTypescriptConfig(baseTemplateOrParams) {
834
+ function renderTypescriptConfig(baseTemplateOrParams) {
798
835
  const params = typeof baseTemplateOrParams === "string" ? { baseTemplate: baseTemplateOrParams } : baseTemplateOrParams;
799
836
  const {
800
837
  baseTemplate,
@@ -841,10 +878,7 @@ function generateTypescriptConfig(baseTemplateOrParams) {
841
878
  const tsConfig = {
842
879
  $schema: "https://json.schemastore.org/tsconfig",
843
880
  files: [],
844
- references: [
845
- { path: "./.config/tsconfig.app.json" },
846
- { path: "./.config/tsconfig.node.json" }
847
- ]
881
+ references: [{ path: "./.config/tsconfig.app.json" }, { path: "./.config/tsconfig.node.json" }]
848
882
  };
849
883
  files["tsconfig.json"] = {
850
884
  type: "text",
@@ -864,6 +898,7 @@ function generateTypescriptConfig(baseTemplateOrParams) {
864
898
  composite: true,
865
899
  rewriteRelativeImportExtensions: true,
866
900
  erasableSyntaxOnly: true,
901
+ noEmit: true,
867
902
  ...isReact || isR3f ? { jsx: "react-jsx" } : {}
868
903
  },
869
904
  include: ["../src", "../tests"]
@@ -885,7 +920,8 @@ function generateTypescriptConfig(baseTemplateOrParams) {
885
920
  skipLibCheck: true,
886
921
  composite: true,
887
922
  rewriteRelativeImportExtensions: true,
888
- erasableSyntaxOnly: true
923
+ erasableSyntaxOnly: true,
924
+ noEmit: true
889
925
  },
890
926
  include: ["../*.config.ts", "./*.ts"]
891
927
  };
@@ -917,6 +953,7 @@ function generateTypescriptConfig(baseTemplateOrParams) {
917
953
  composite: true,
918
954
  rewriteRelativeImportExtensions: true,
919
955
  erasableSyntaxOnly: true,
956
+ noEmit: true,
920
957
  ...isReact || isR3f ? { jsx: "react-jsx" } : {}
921
958
  },
922
959
  include: ["src", "tests"]
@@ -938,7 +975,8 @@ function generateTypescriptConfig(baseTemplateOrParams) {
938
975
  skipLibCheck: true,
939
976
  composite: true,
940
977
  rewriteRelativeImportExtensions: true,
941
- erasableSyntaxOnly: true
978
+ erasableSyntaxOnly: true,
979
+ noEmit: true
942
980
  },
943
981
  include: ["*.config.ts"]
944
982
  };
@@ -950,8 +988,108 @@ function generateTypescriptConfig(baseTemplateOrParams) {
950
988
  return { files, devDependencies };
951
989
  }
952
990
 
991
+ const packageJsonScripts = {
992
+ appBase: {
993
+ dev: "vite",
994
+ build: "vite build"
995
+ },
996
+ typescript: {
997
+ typecheck: "tsc --build --noEmit",
998
+ "typecheck:watch": "tsc --build --watch"
999
+ },
1000
+ test: {
1001
+ vitest: {
1002
+ test: "vitest"
1003
+ }
1004
+ },
1005
+ build: {
1006
+ unbuild(configPath) {
1007
+ return {
1008
+ build: configPath == null ? "unbuild" : `unbuild --config ${configPath}`
1009
+ };
1010
+ },
1011
+ tsdown: {
1012
+ build: "tsdown"
1013
+ }
1014
+ },
1015
+ lint: {
1016
+ oxlint(configPath) {
1017
+ return {
1018
+ lint: configPath == null ? "oxlint" : `oxlint -c ${configPath}`
1019
+ };
1020
+ },
1021
+ eslint(configPath) {
1022
+ return {
1023
+ lint: configPath == null ? "eslint ." : `eslint --config ${configPath} .`
1024
+ };
1025
+ },
1026
+ biome(configPath) {
1027
+ return {
1028
+ lint: configPath == null ? "biome lint ." : `biome lint --config-path ${configPath} .`
1029
+ };
1030
+ }
1031
+ },
1032
+ format: {
1033
+ prettier(configPath, ignorePath) {
1034
+ const configFlag = configPath == null ? "" : ` --config ${configPath}`;
1035
+ const ignoreFlag = ignorePath == null ? "" : ` --ignore-path ${ignorePath}`;
1036
+ return {
1037
+ format: `prettier${configFlag}${ignoreFlag} --write .`
1038
+ };
1039
+ },
1040
+ oxfmt(configPath) {
1041
+ return {
1042
+ format: `oxfmt -c ${configPath} --write .`
1043
+ };
1044
+ },
1045
+ biome(configPath) {
1046
+ return {
1047
+ format: configPath == null ? "biome format --write ." : `biome format --config-path ${configPath} --write .`
1048
+ };
1049
+ }
1050
+ },
1051
+ release(packageManagerName) {
1052
+ return {
1053
+ release: `${packageManagerName} run build && ${packageManagerName} publish`
1054
+ };
1055
+ },
1056
+ monorepoRoot(linter, formatter) {
1057
+ return mergePackageJsonScripts(
1058
+ {
1059
+ dev: "pnpm --filter './apps/*' run dev",
1060
+ build: "pnpm --filter './packages/*' run build && pnpm --filter './apps/*' run build",
1061
+ test: "pnpm -r run test"
1062
+ },
1063
+ linter === "oxlint" ? {
1064
+ lint: "oxlint ."
1065
+ } : linter === "biome" ? {
1066
+ lint: "biome check ."
1067
+ } : {
1068
+ lint: "eslint ."
1069
+ },
1070
+ formatter === "oxfmt" ? {
1071
+ format: "oxfmt -c .config/oxfmt/base.json ."
1072
+ } : formatter === "biome" ? {
1073
+ format: "biome format . --write"
1074
+ } : {
1075
+ format: "prettier --config .config/prettier/base.json --ignore-path .config/prettier/prettierignore --write ."
1076
+ }
1077
+ );
1078
+ }
1079
+ };
1080
+ function mergePackageJsonScripts(...scriptSets) {
1081
+ return Object.assign({}, ...scriptSets.filter((scriptSet) => scriptSet != null));
1082
+ }
1083
+ function resolveDefaultPackageJsonScripts(params) {
1084
+ return mergePackageJsonScripts(
1085
+ params.isLibrary ? void 0 : packageJsonScripts.appBase,
1086
+ params.language === "typescript" ? packageJsonScripts.typescript : void 0,
1087
+ params.isLibrary ? packageJsonScripts.release(params.packageManagerName) : void 0
1088
+ );
1089
+ }
1090
+
953
1091
  const DEFAULT_LIBRARY_VERSION = "0.1.0";
954
- function generatePackageJson(params) {
1092
+ function renderPackageJson(params) {
955
1093
  const {
956
1094
  name,
957
1095
  language,
@@ -959,13 +1097,20 @@ function generatePackageJson(params) {
959
1097
  dependencies,
960
1098
  devDependencies,
961
1099
  peerDependencies,
962
- scripts,
963
1100
  options,
964
1101
  workspaceDependencies
965
1102
  } = params;
966
1103
  const files = {};
967
1104
  const packageManager = getPackageManagerSpec(options.packageManager);
968
1105
  const isPnpm = packageManager.name === "pnpm";
1106
+ const resolvedScripts = mergePackageJsonScripts(
1107
+ resolveDefaultPackageJsonScripts({
1108
+ language,
1109
+ isLibrary,
1110
+ packageManagerName: packageManager.name
1111
+ }),
1112
+ params.scripts
1113
+ );
969
1114
  const packageJson = {
970
1115
  name,
971
1116
  description: "Built with \u{1F339} create-krispya",
@@ -997,12 +1142,9 @@ function generatePackageJson(params) {
997
1142
  const allDevDependencies = { ...devDependencies };
998
1143
  const engine = getEngineSpec(options.engine);
999
1144
  if (getEngineName(engine) === "node" && engine.version) {
1000
- allDevDependencies["@types/node"] ??= formatNodeTypesVersion(
1001
- options.versions,
1002
- options.engine
1003
- );
1145
+ allDevDependencies["@types/node"] ??= formatNodeTypesVersion(options.versions, options.engine);
1004
1146
  }
1005
- packageJson.scripts = scripts;
1147
+ packageJson.scripts = resolvedScripts;
1006
1148
  packageJson.dependencies = sortKeys(allDependencies);
1007
1149
  if (Object.keys(allDevDependencies).length > 0) {
1008
1150
  packageJson.devDependencies = sortKeys(allDevDependencies);
@@ -1045,7 +1187,7 @@ function generatePackageJson(params) {
1045
1187
  return { files };
1046
1188
  }
1047
1189
 
1048
- function generateReadme(params) {
1190
+ function renderReadme(params) {
1049
1191
  const { name, baseTemplate, isLibrary, libraryBundler, packageManager, codeSnippets } = params;
1050
1192
  const isVanilla = baseTemplate === "vanilla";
1051
1193
  const isReact = baseTemplate === "react";
@@ -1101,13 +1243,14 @@ function generateReadme(params) {
1101
1243
  } else if (isReact) {
1102
1244
  architectureDesc = [
1103
1245
  `- \`src/app.${jsxExt}\` defines the main application component`,
1104
- `- \`src/index.${jsxExt}\` renders the React app into the DOM`,
1246
+ `- \`src/main.${jsxExt}\` renders the React app into the DOM`,
1105
1247
  `- \`tests/\` contains your test files`,
1106
1248
  `- Static assets can be placed in the \`public\` folder`
1107
1249
  ];
1108
1250
  } else {
1109
1251
  architectureDesc = [
1110
- `- \`app.${jsxExt}\` defines the main application component containing your 3D content`,
1252
+ `- \`src/app.${jsxExt}\` defines the main application component containing your 3D content`,
1253
+ `- \`src/main.${jsxExt}\` renders the React app into the DOM`,
1111
1254
  `- Modify the content inside the \`<Canvas>\` component to change what is visible on screen`,
1112
1255
  `- \`tests/\` contains your test files`,
1113
1256
  `- Static assets can be placed in the \`public\` folder`
@@ -1138,7 +1281,67 @@ function generateReadme(params) {
1138
1281
  return { type: "text", content };
1139
1282
  }
1140
1283
 
1141
- function generateSourceFiles(params) {
1284
+ const htmlContent = `<!DOCTYPE html>
1285
+ <html lang="en">
1286
+ <head>
1287
+ <meta charset="UTF-8">
1288
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1289
+ <title>$title</title>
1290
+ </head>
1291
+ <body>
1292
+ <div id="root"></div>
1293
+ <script type="module" src="$indexPath"><\/script>
1294
+ </body>
1295
+ </html>`;
1296
+ const viteHtmlContent = `<!DOCTYPE html>
1297
+ <html lang="en">
1298
+ <head>
1299
+ <meta charset="UTF-8">
1300
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1301
+ <title>$title</title>
1302
+ </head>
1303
+ <body>
1304
+ <div id="app"></div>
1305
+ <script type="module" src="$indexPath"><\/script>
1306
+ </body>
1307
+ </html>`;
1308
+ const indexContent = `import { StrictMode } from 'react'
1309
+ import { createRoot } from 'react-dom/client'
1310
+ import './index.css'
1311
+ import { App } from './app.js'
1312
+
1313
+ createRoot(document.getElementById('root')!).render(
1314
+ <StrictMode>
1315
+ <App />
1316
+ </StrictMode>,
1317
+ )`;
1318
+ const viteIndexContent = `import './index.css'
1319
+
1320
+ document.querySelector('#app')!.innerHTML = \`
1321
+ <h1>Hello Vite!</h1>
1322
+ <p>Edit src/main.ts and save to see HMR in action.</p>
1323
+ \``;
1324
+ const viteStyleContent = `:root {
1325
+ font-family:
1326
+ system-ui,
1327
+ -apple-system,
1328
+ sans-serif;
1329
+ line-height: 1.5;
1330
+ font-weight: 400;
1331
+ }
1332
+
1333
+ *,
1334
+ *::before,
1335
+ *::after {
1336
+ box-sizing: border-box;
1337
+ }
1338
+
1339
+ body {
1340
+ margin: 0;
1341
+ }`;
1342
+ const viteEnvContent = `/// <reference types="vite/client" />`;
1343
+
1344
+ function renderSourceFiles(params) {
1142
1345
  const { name, baseTemplate, language, isLibrary, codeSnippets, replacements } = params;
1143
1346
  const files = {};
1144
1347
  const ext = language === "typescript" ? "ts" : "js";
@@ -1146,6 +1349,9 @@ function generateSourceFiles(params) {
1146
1349
  const isVanilla = baseTemplate === "vanilla";
1147
1350
  const isReact = baseTemplate === "react";
1148
1351
  const isR3f = baseTemplate === "r3f";
1352
+ if (!isLibrary && language === "typescript") {
1353
+ files["src/vite-env.d.ts"] = { type: "text", content: viteEnvContent };
1354
+ }
1149
1355
  if (isLibrary) {
1150
1356
  const libExt = isReact || isR3f ? jsxExt : ext;
1151
1357
  let libContent;
@@ -1178,19 +1384,20 @@ function generateSourceFiles(params) {
1178
1384
  }
1179
1385
  files[`src/index.${libExt}`] = { type: "text", content: libContent };
1180
1386
  } else if (isVanilla) {
1181
- files[`src/main.${ext}`] = { type: "text", content: ViteIndexContent };
1182
- files["src/style.css"] = { type: "text", content: ViteStyleContent };
1183
- const indexHtml = ViteHtmlContent.replace("$indexPath", `./src/main.${ext}`).replace(
1184
- "$title",
1185
- name
1186
- );
1387
+ files[`src/main.${ext}`] = { type: "text", content: viteIndexContent };
1388
+ files["src/index.css"] = { type: "text", content: viteStyleContent };
1389
+ const indexHtml = viteHtmlContent.replace("$indexPath", `./src/main.${ext}`).replace("$title", name);
1187
1390
  files["index.html"] = { type: "text", content: indexHtml };
1188
1391
  } else {
1189
- files[`src/index.tsx`] = { type: "text", content: IndexContent };
1190
- const indexHtml = HtmlContent.replace(
1191
- "$indexPath",
1192
- language === "javascript" ? "./src/index.jsx" : "./src/index.tsx"
1193
- ).replace("$title", name);
1392
+ files[`src/main.${jsxExt}`] = {
1393
+ type: "text",
1394
+ content: language === "typescript" ? indexContent : indexContent.replace(
1395
+ "document.getElementById('root')!",
1396
+ "document.getElementById('root')"
1397
+ )
1398
+ };
1399
+ files["src/index.css"] = { type: "text", content: viteStyleContent };
1400
+ const indexHtml = htmlContent.replace("$indexPath", `./src/main.${jsxExt}`).replace("$title", name);
1194
1401
  files["index.html"] = { type: "text", content: indexHtml };
1195
1402
  codeSnippets["dom-end"]?.reverse();
1196
1403
  codeSnippets["global-end"]?.reverse();
@@ -1232,12 +1439,12 @@ function generateSourceFiles(params) {
1232
1439
  for (const { search, replace } of replacements) {
1233
1440
  appCode = appCode.replace(search, replace);
1234
1441
  }
1235
- files[`src/app.tsx`] = { type: "text", content: appCode };
1442
+ files[`src/app.${jsxExt}`] = { type: "text", content: appCode };
1236
1443
  }
1237
1444
  return files;
1238
1445
  }
1239
1446
 
1240
- function generateTestFiles(params) {
1447
+ function renderTestFiles(params) {
1241
1448
  const { baseTemplate, language, isLibrary } = params;
1242
1449
  const files = {};
1243
1450
  const ext = language === "typescript" ? "ts" : "js";
@@ -1344,21 +1551,184 @@ const COMMON_GITIGNORE_LINES = [
1344
1551
  "*.tsbuildinfo",
1345
1552
  ".env",
1346
1553
  ".env.*",
1347
- "!.env.example"
1554
+ "!.env.example",
1555
+ ".pnpm-store"
1348
1556
  ];
1349
- function generateGitignore(variant) {
1557
+ function renderGitignore(variant) {
1350
1558
  const lines = variant === "workspace-root" ? [...COMMON_GITIGNORE_LINES, ".DS_Store"] : COMMON_GITIGNORE_LINES;
1351
1559
  return {
1352
1560
  type: "text",
1353
1561
  content: lines.join("\n")
1354
1562
  };
1355
1563
  }
1356
-
1357
- function generateVscodeFiles$1(params) {
1358
- const { codeSnippets, vscodeSettings } = params;
1564
+
1565
+ const defaultFormatterMetaConfig = {
1566
+ printWidth: 102,
1567
+ tabWidth: 2,
1568
+ useTabs: false,
1569
+ semi: true,
1570
+ singleQuote: true,
1571
+ trailingComma: "es5",
1572
+ bracketSpacing: true,
1573
+ arrowParens: "always",
1574
+ ignorePatterns: [
1575
+ "package-lock.json",
1576
+ "npm-shrinkwrap.json",
1577
+ "pnpm-lock.yaml",
1578
+ "pnpm-lock.json",
1579
+ "yarn.lock",
1580
+ "bun.lock",
1581
+ "bun.lockb"
1582
+ ]
1583
+ };
1584
+
1585
+ function renderEditorConfig(config = defaultFormatterMetaConfig) {
1586
+ const indentStyle = config.useTabs ? "tab" : "space";
1587
+ const indentSize = config.useTabs ? "tab" : String(config.tabWidth);
1588
+ return {
1589
+ type: "text",
1590
+ content: [
1591
+ "root = true",
1592
+ "",
1593
+ "[*]",
1594
+ "charset = utf-8",
1595
+ "end_of_line = lf",
1596
+ "insert_final_newline = true",
1597
+ `indent_style = ${indentStyle}`,
1598
+ `indent_size = ${indentSize}`,
1599
+ `tab_width = ${config.tabWidth}`,
1600
+ `max_line_length = ${config.printWidth}`
1601
+ ].join("\n")
1602
+ };
1603
+ }
1604
+ function renderVscodeEditorSettings(config = defaultFormatterMetaConfig) {
1605
+ return {
1606
+ "editor.detectIndentation": false,
1607
+ "editor.insertSpaces": !config.useTabs,
1608
+ "editor.tabSize": config.tabWidth,
1609
+ "files.eol": "\n",
1610
+ "files.insertFinalNewline": true
1611
+ };
1612
+ }
1613
+
1614
+ const DEFAULT_VSCODE_SETTINGS = {
1615
+ ...renderVscodeEditorSettings(),
1616
+ "explorer.fileNesting.enabled": true,
1617
+ "explorer.fileNesting.expand": false,
1618
+ "explorer.fileNesting.patterns": {
1619
+ ".gitignore": ".gitattributes",
1620
+ "AGENTS.md": "CLAUDE.md"
1621
+ }
1622
+ };
1623
+ const OXFMT_LANGUAGE_SETTINGS = {
1624
+ "[json]": {
1625
+ "editor.defaultFormatter": "vscode.json-language-features"
1626
+ },
1627
+ "[jsonc]": {
1628
+ "editor.defaultFormatter": "vscode.json-language-features"
1629
+ },
1630
+ "[markdown]": {
1631
+ "editor.defaultFormatter": "vscode.markdown-language-features"
1632
+ },
1633
+ "[yaml]": {
1634
+ "editor.defaultFormatter": "redhat.vscode-yaml"
1635
+ }
1636
+ };
1637
+ function resolvePackageJsonNestedFiles(packageManager) {
1638
+ if (packageManager === "pnpm") {
1639
+ return ["pnpm-lock.yaml", "pnpm-workspace.yaml"];
1640
+ }
1641
+ if (packageManager === "npm") {
1642
+ return ["package-lock.json", "npm-shrinkwrap.json"];
1643
+ }
1644
+ if (packageManager === "yarn") {
1645
+ return ["yarn.lock"];
1646
+ }
1647
+ return [];
1648
+ }
1649
+ function resolveVscodeRecommendations(linter, formatter) {
1650
+ const recommendations = [];
1651
+ if (linter === "oxlint" || formatter === "oxfmt") {
1652
+ recommendations.push("oxc.oxc-vscode");
1653
+ }
1654
+ if (linter === "eslint") {
1655
+ recommendations.push("dbaeumer.vscode-eslint");
1656
+ }
1657
+ if (linter === "biome" || formatter === "biome") {
1658
+ recommendations.push("biomejs.biome");
1659
+ }
1660
+ if (formatter === "prettier") {
1661
+ recommendations.push("esbenp.prettier-vscode");
1662
+ }
1663
+ return recommendations;
1664
+ }
1665
+ function resolveVscodeSettings(params) {
1666
+ const { linter, formatter, configStrategy, isMonorepo, packageManager } = params;
1667
+ const settings = { ...DEFAULT_VSCODE_SETTINGS };
1668
+ const isStealth = !isMonorepo && (configStrategy ?? "stealth") === "stealth";
1669
+ const packageJsonNestedFiles = resolvePackageJsonNestedFiles(packageManager);
1670
+ if (packageJsonNestedFiles.length > 0) {
1671
+ settings["explorer.fileNesting.patterns"] = {
1672
+ ...settings["explorer.fileNesting.patterns"],
1673
+ "package.json": packageJsonNestedFiles.join(", ")
1674
+ };
1675
+ }
1676
+ if (linter === "eslint") {
1677
+ settings["eslint.enable"] = true;
1678
+ settings["oxc.enable"] = false;
1679
+ settings["biome.enabled"] = false;
1680
+ if (isStealth) {
1681
+ settings["eslint.options"] = {
1682
+ overrideConfigFile: ".config/eslint.config.js"
1683
+ };
1684
+ }
1685
+ } else if (linter === "oxlint") {
1686
+ settings["oxc.enable"] = true;
1687
+ settings["eslint.enable"] = false;
1688
+ settings["biome.enabled"] = false;
1689
+ if (isStealth) {
1690
+ settings["oxc.configPath"] = ".config/oxlint.json";
1691
+ }
1692
+ } else if (linter === "biome") {
1693
+ settings["biome.enabled"] = true;
1694
+ settings["eslint.enable"] = false;
1695
+ settings["oxc.enable"] = false;
1696
+ if (isStealth) {
1697
+ settings["biome.linter.configPath"] = ".config/biome.json";
1698
+ }
1699
+ }
1700
+ if (formatter === "prettier") {
1701
+ settings["editor.defaultFormatter"] = "esbenp.prettier-vscode";
1702
+ if (isStealth) {
1703
+ settings["prettier.configPath"] = ".config/prettier.json";
1704
+ settings["prettier.ignorePath"] = ".config/prettierignore";
1705
+ }
1706
+ } else if (formatter === "oxfmt") {
1707
+ settings["editor.defaultFormatter"] = "oxc.oxc-vscode";
1708
+ Object.assign(settings, OXFMT_LANGUAGE_SETTINGS);
1709
+ if (isStealth) {
1710
+ settings["oxc.fmt.configPath"] = ".config/oxfmt.json";
1711
+ }
1712
+ } else if (formatter === "biome") {
1713
+ settings["biome.enabled"] = true;
1714
+ settings["eslint.enable"] = false;
1715
+ settings["oxc.enable"] = false;
1716
+ settings["editor.defaultFormatter"] = "biomejs.biome";
1717
+ if (isStealth) {
1718
+ settings["biome.linter.configPath"] = ".config/biome.json";
1719
+ }
1720
+ }
1721
+ return settings;
1722
+ }
1723
+ function renderVscodeFiles$1(params) {
1724
+ const { codeSnippets = {}, vscodeSettings = {} } = params;
1359
1725
  const files = {};
1360
- if (codeSnippets["vscode-extension-suggestion"]?.length) {
1361
- const uniqueRecommendations = [...new Set(codeSnippets["vscode-extension-suggestion"])];
1726
+ const recommendations = [
1727
+ ...codeSnippets["vscode-extension-suggestion"] ?? [],
1728
+ ...resolveVscodeRecommendations(params.linter, params.formatter)
1729
+ ];
1730
+ if (recommendations.length > 0) {
1731
+ const uniqueRecommendations = [...new Set(recommendations)];
1362
1732
  files[".vscode/extensions.json"] = {
1363
1733
  type: "text",
1364
1734
  content: JSON.stringify(
@@ -1370,9 +1740,13 @@ function generateVscodeFiles$1(params) {
1370
1740
  )
1371
1741
  };
1372
1742
  }
1373
- if (Object.keys(vscodeSettings).length > 0) {
1743
+ const resolvedSettings = {
1744
+ ...resolveVscodeSettings(params),
1745
+ ...vscodeSettings
1746
+ };
1747
+ if (Object.keys(resolvedSettings).length > 0) {
1374
1748
  const sortedSettings = Object.fromEntries(
1375
- Object.entries(vscodeSettings).sort(([a], [b]) => a.localeCompare(b))
1749
+ Object.entries(resolvedSettings).sort(([a], [b]) => a.localeCompare(b))
1376
1750
  );
1377
1751
  files[".vscode/settings.json"] = {
1378
1752
  type: "text",
@@ -1389,7 +1763,7 @@ function formatValue(value, indent) {
1389
1763
  if (value.startsWith("$raw:")) {
1390
1764
  return value.slice(5);
1391
1765
  }
1392
- return JSON.stringify(value);
1766
+ return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
1393
1767
  }
1394
1768
  if (typeof value === "number" || typeof value === "boolean") {
1395
1769
  return String(value);
@@ -1397,8 +1771,17 @@ function formatValue(value, indent) {
1397
1771
  if (value === null) {
1398
1772
  return "null";
1399
1773
  }
1774
+ if (value === void 0) {
1775
+ return "undefined";
1776
+ }
1777
+ if (typeof value === "bigint") {
1778
+ return value.toString();
1779
+ }
1400
1780
  if (Array.isArray(value)) {
1401
1781
  if (value.length === 0) return "[]";
1782
+ if (value.every((item) => item == null || typeof item !== "object")) {
1783
+ return `[${value.map((item) => formatValue(item, indent + 1)).join(", ")}]`;
1784
+ }
1402
1785
  const items = value.map((v) => `${innerSpaces}${formatValue(v, indent + 1)}`);
1403
1786
  return `[
1404
1787
  ${items.join(",\n")}
@@ -1408,28 +1791,114 @@ ${spaces}]`;
1408
1791
  const entries = Object.entries(value);
1409
1792
  if (entries.length === 0) return "{}";
1410
1793
  const props = entries.map(
1411
- ([key, val]) => `${innerSpaces}${key}: ${formatValue(val, indent + 1)}`
1794
+ ([key, val]) => `${innerSpaces}${key}: ${formatValue(val, indent + 1)},`
1412
1795
  );
1413
1796
  return `{
1414
- ${props.join(",\n")}
1797
+ ${props.join("\n")}
1415
1798
  ${spaces}}`;
1416
1799
  }
1417
- return String(value);
1800
+ throw new TypeError(`Unsupported vite config value type: ${typeof value}`);
1418
1801
  }
1419
- function generateViteConfig(params) {
1802
+ function renderViteConfig(params) {
1420
1803
  const { viteConfig, codeSnippets } = params;
1421
1804
  const configBody = formatValue(viteConfig, 0);
1422
1805
  const viteConfigContent = [
1423
- `import { defineConfig } from "vite"`,
1806
+ `import { defineConfig } from 'vite';`,
1424
1807
  ...codeSnippets["vite-config-import"] ?? [],
1425
1808
  ``,
1426
- `export default defineConfig(${configBody})`,
1809
+ `export default defineConfig(${configBody});`,
1427
1810
  ``
1428
1811
  ].join("\n");
1429
1812
  return { type: "text", content: viteConfigContent };
1430
1813
  }
1431
1814
 
1432
- function generateTypescriptConfigPackage(files) {
1815
+ function toPrettierConfig(config = defaultFormatterMetaConfig) {
1816
+ return {
1817
+ $schema: "https://json.schemastore.org/prettierrc",
1818
+ printWidth: config.printWidth,
1819
+ tabWidth: config.tabWidth,
1820
+ useTabs: config.useTabs,
1821
+ semi: config.semi,
1822
+ singleQuote: config.singleQuote,
1823
+ trailingComma: config.trailingComma,
1824
+ bracketSpacing: config.bracketSpacing,
1825
+ arrowParens: config.arrowParens,
1826
+ overrides: [
1827
+ {
1828
+ files: ["*.md", "**/*.md"],
1829
+ options: { semi: false }
1830
+ },
1831
+ {
1832
+ files: ["*.yml", "*.yaml", "**/*.yml", "**/*.yaml"],
1833
+ options: { semi: false }
1834
+ }
1835
+ ]
1836
+ };
1837
+ }
1838
+ function toPrettierIgnoreContent(config = defaultFormatterMetaConfig) {
1839
+ return config.ignorePatterns.join("\n");
1840
+ }
1841
+ function toOxfmtConfig(config = defaultFormatterMetaConfig) {
1842
+ return {
1843
+ printWidth: config.printWidth,
1844
+ tabWidth: config.tabWidth,
1845
+ useTabs: config.useTabs,
1846
+ semi: config.semi,
1847
+ singleQuote: config.singleQuote,
1848
+ trailingComma: config.trailingComma,
1849
+ bracketSpacing: config.bracketSpacing,
1850
+ arrowParens: config.arrowParens,
1851
+ ignorePatterns: config.ignorePatterns
1852
+ };
1853
+ }
1854
+
1855
+ const defaultLinterMetaConfig = {
1856
+ ignorePatterns: ["dist"],
1857
+ rules: {
1858
+ noUnusedVars: {
1859
+ level: "warn",
1860
+ argsIgnorePattern: "^_",
1861
+ varsIgnorePattern: "^_",
1862
+ caughtErrorsIgnorePattern: "^_"
1863
+ },
1864
+ noUnusedExpressions: {
1865
+ level: "warn",
1866
+ allowShortCircuit: true
1867
+ }
1868
+ }
1869
+ };
1870
+
1871
+ function renderOxlintConfig(params) {
1872
+ const config = params.config ?? defaultLinterMetaConfig;
1873
+ const { rules } = config;
1874
+ const plugins = ["unicorn", "typescript", "oxc"];
1875
+ if (params.react === true) {
1876
+ plugins.push("react");
1877
+ }
1878
+ return {
1879
+ $schema: params.schemaPath,
1880
+ plugins,
1881
+ ...params.typescript === true ? { options: { typeAware: true } } : {},
1882
+ rules: {
1883
+ "no-unused-vars": [
1884
+ rules.noUnusedVars.level,
1885
+ {
1886
+ argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1887
+ varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1888
+ caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1889
+ }
1890
+ ],
1891
+ "no-useless-escape": "off",
1892
+ "no-unused-expressions": [
1893
+ rules.noUnusedExpressions.level,
1894
+ { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1895
+ ]
1896
+ },
1897
+ ignorePatterns: config.ignorePatterns
1898
+ };
1899
+ }
1900
+
1901
+ function renderTypescriptConfigPackage(files) {
1433
1902
  const basePath = ".config/typescript";
1434
1903
  files[`${basePath}/package.json`] = {
1435
1904
  type: "text",
@@ -1534,9 +2003,8 @@ In your package's \`tsconfig.json\`:
1534
2003
  )
1535
2004
  };
1536
2005
  }
1537
- function generateOxlintConfigPackage(files) {
2006
+ function renderOxlintConfigPackage(files) {
1538
2007
  const basePath = ".config/oxlint";
1539
- const { rules } = defaultLinterConfig;
1540
2008
  files[`${basePath}/package.json`] = {
1541
2009
  type: "text",
1542
2010
  content: JSON.stringify(
@@ -1573,26 +2041,10 @@ oxlint -c node_modules/@config/oxlint/base.json
1573
2041
  files[`${basePath}/base.json`] = {
1574
2042
  type: "text",
1575
2043
  content: JSON.stringify(
1576
- {
1577
- $schema: "./node_modules/oxlint/configuration_schema.json",
1578
- plugins: ["unicorn", "typescript", "oxc"],
1579
- rules: {
1580
- "no-unused-vars": [
1581
- rules.noUnusedVars.level,
1582
- {
1583
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1584
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1585
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1586
- }
1587
- ],
1588
- "no-useless-escape": "off",
1589
- "no-unused-expressions": [
1590
- rules.noUnusedExpressions.level,
1591
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1592
- ]
1593
- },
1594
- ignorePatterns: defaultLinterConfig.ignorePatterns
1595
- },
2044
+ renderOxlintConfig({
2045
+ schemaPath: "./node_modules/oxlint/configuration_schema.json",
2046
+ typescript: true
2047
+ }),
1596
2048
  null,
1597
2049
  2
1598
2050
  )
@@ -1600,32 +2052,17 @@ oxlint -c node_modules/@config/oxlint/base.json
1600
2052
  files[`${basePath}/react.json`] = {
1601
2053
  type: "text",
1602
2054
  content: JSON.stringify(
1603
- {
1604
- $schema: "./node_modules/oxlint/configuration_schema.json",
1605
- plugins: ["unicorn", "typescript", "oxc", "react"],
1606
- rules: {
1607
- "no-unused-vars": [
1608
- rules.noUnusedVars.level,
1609
- {
1610
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1611
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1612
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1613
- }
1614
- ],
1615
- "no-useless-escape": "off",
1616
- "no-unused-expressions": [
1617
- rules.noUnusedExpressions.level,
1618
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1619
- ]
1620
- },
1621
- ignorePatterns: defaultLinterConfig.ignorePatterns
1622
- },
2055
+ renderOxlintConfig({
2056
+ schemaPath: "./node_modules/oxlint/configuration_schema.json",
2057
+ react: true,
2058
+ typescript: true
2059
+ }),
1623
2060
  null,
1624
2061
  2
1625
2062
  )
1626
2063
  };
1627
2064
  }
1628
- function generateEslintConfigPackage(files) {
2065
+ function renderEslintConfigPackage(files) {
1629
2066
  const basePath = ".config/eslint";
1630
2067
  files[`${basePath}/package.json`] = {
1631
2068
  type: "text",
@@ -1732,7 +2169,7 @@ export default tseslint.config(
1732
2169
  `
1733
2170
  };
1734
2171
  }
1735
- function generatePrettierConfigPackage(files) {
2172
+ function renderPrettierConfigPackage(files) {
1736
2173
  const basePath = ".config/prettier";
1737
2174
  files[`${basePath}/package.json`] = {
1738
2175
  type: "text",
@@ -1745,7 +2182,7 @@ function generatePrettierConfigPackage(files) {
1745
2182
  exports: {
1746
2183
  ".": "./base.json"
1747
2184
  },
1748
- files: ["base.json"]
2185
+ files: ["base.json", "prettierignore"]
1749
2186
  },
1750
2187
  null,
1751
2188
  2
@@ -1780,10 +2217,14 @@ Or in \`.prettierrc.json\`:
1780
2217
  };
1781
2218
  files[`${basePath}/base.json`] = {
1782
2219
  type: "text",
1783
- content: JSON.stringify(defaultPrettierConfig, null, 2)
2220
+ content: JSON.stringify(toPrettierConfig(), null, 2)
2221
+ };
2222
+ files[`${basePath}/prettierignore`] = {
2223
+ type: "text",
2224
+ content: toPrettierIgnoreContent()
1784
2225
  };
1785
2226
  }
1786
- function generateOxfmtConfigPackage(files) {
2227
+ function renderOxfmtConfigPackage(files) {
1787
2228
  const basePath = ".config/oxfmt";
1788
2229
  files[`${basePath}/package.json`] = {
1789
2230
  type: "text",
@@ -1819,11 +2260,11 @@ oxfmt -c node_modules/@config/oxfmt/base.json --write .
1819
2260
  };
1820
2261
  files[`${basePath}/base.json`] = {
1821
2262
  type: "text",
1822
- content: JSON.stringify(defaultOxfmtConfig, null, 2)
2263
+ content: JSON.stringify(toOxfmtConfig(), null, 2)
1823
2264
  };
1824
2265
  }
1825
2266
 
1826
- function generateMonorepo(params) {
2267
+ function renderMonorepo(params) {
1827
2268
  const {
1828
2269
  name,
1829
2270
  linter,
@@ -1832,6 +2273,7 @@ function generateMonorepo(params) {
1832
2273
  pnpmManageVersions,
1833
2274
  engine,
1834
2275
  versions = {},
2276
+ ide = "vscode",
1835
2277
  aiPlatforms
1836
2278
  } = params;
1837
2279
  const files = {};
@@ -1844,6 +2286,7 @@ function generateMonorepo(params) {
1844
2286
  }
1845
2287
  if (linter === "oxlint") {
1846
2288
  assignResolvedPackageVersion(devDependencies, versions, "oxlint");
2289
+ assignResolvedPackageVersion(devDependencies, versions, "oxlint-tsgolint");
1847
2290
  } else if (linter === "eslint") {
1848
2291
  assignResolvedPackageVersion(devDependencies, versions, "eslint");
1849
2292
  } else if (linter === "biome") {
@@ -1859,13 +2302,7 @@ function generateMonorepo(params) {
1859
2302
  version: "0.0.0",
1860
2303
  private: true,
1861
2304
  type: "module",
1862
- scripts: {
1863
- dev: "pnpm --filter './apps/*' run dev",
1864
- build: "pnpm --filter './packages/*' run build && pnpm --filter './apps/*' run build",
1865
- test: "pnpm -r run test",
1866
- lint: linter === "oxlint" ? "oxlint ." : linter === "biome" ? "biome check ." : "eslint .",
1867
- format: formatter === "oxfmt" ? "oxfmt -c .config/oxfmt/base.json ." : formatter === "biome" ? "biome format . --write" : "prettier --config .config/prettier/base.json --write ."
1868
- },
2305
+ scripts: packageJsonScripts.monorepoRoot(linter, formatter),
1869
2306
  devDependencies
1870
2307
  };
1871
2308
  const engines = {};
@@ -1911,9 +2348,9 @@ function generateMonorepo(params) {
1911
2348
  2
1912
2349
  )
1913
2350
  };
1914
- generateTypescriptConfigPackage(files);
2351
+ renderTypescriptConfigPackage(files);
1915
2352
  if (linter === "oxlint") {
1916
- generateOxlintConfigPackage(files);
2353
+ renderOxlintConfigPackage(files);
1917
2354
  files["oxlint.json"] = {
1918
2355
  type: "text",
1919
2356
  content: JSON.stringify(
@@ -1926,7 +2363,7 @@ function generateMonorepo(params) {
1926
2363
  )
1927
2364
  };
1928
2365
  } else if (linter === "eslint") {
1929
- generateEslintConfigPackage(files);
2366
+ renderEslintConfigPackage(files);
1930
2367
  files["eslint.config.js"] = {
1931
2368
  type: "text",
1932
2369
  content: `import base from "@config/eslint/base";
@@ -1959,11 +2396,12 @@ export default [...base];
1959
2396
  };
1960
2397
  }
1961
2398
  if (formatter === "oxfmt") {
1962
- generateOxfmtConfigPackage(files);
2399
+ renderOxfmtConfigPackage(files);
1963
2400
  } else if (formatter === "prettier") {
1964
- generatePrettierConfigPackage(files);
2401
+ renderPrettierConfigPackage(files);
1965
2402
  }
1966
- files[".gitignore"] = generateGitignore("workspace-root");
2403
+ files[".editorconfig"] = renderEditorConfig();
2404
+ files[".gitignore"] = renderGitignore("workspace-root");
1967
2405
  files[".gitattributes"] = {
1968
2406
  type: "text",
1969
2407
  content: `* text=auto eol=lf
@@ -1971,7 +2409,9 @@ export default [...base];
1971
2409
  *.{bat,[bB][aA][tT]} text eol=crlf
1972
2410
  `
1973
2411
  };
1974
- generateVscodeFiles(files, linter, formatter);
2412
+ if (ide === "vscode") {
2413
+ renderVscodeFiles(files, linter, formatter, packageManager.name);
2414
+ }
1975
2415
  files["README.md"] = {
1976
2416
  type: "text",
1977
2417
  content: `# ${name}
@@ -1999,104 +2439,50 @@ To add a new package to this workspace, run create-krispya from this directory a
1999
2439
  `
2000
2440
  };
2001
2441
  if (aiPlatforms && aiPlatforms.length > 0) {
2002
- generateAiFiles(files, {
2442
+ renderAiFiles(files, {
2003
2443
  name,
2004
2444
  packageManager: packageManager.name,
2005
2445
  linter,
2006
2446
  formatter,
2007
2447
  isMonorepo: true,
2448
+ hasTypecheck: false,
2008
2449
  platforms: aiPlatforms
2009
2450
  });
2010
2451
  }
2011
2452
  return { files };
2012
2453
  }
2013
- function generateVscodeFiles(files, linter, formatter) {
2014
- const recommendations = [];
2015
- const settings = {};
2016
- if (linter === "oxlint") {
2017
- recommendations.push("oxc.oxc-vscode");
2018
- settings["oxc.enable"] = true;
2019
- settings["eslint.enable"] = false;
2020
- settings["biome.enabled"] = false;
2021
- } else if (linter === "eslint") {
2022
- recommendations.push("dbaeumer.vscode-eslint");
2023
- settings["eslint.enable"] = true;
2024
- settings["oxc.enable"] = false;
2025
- settings["biome.enabled"] = false;
2026
- } else if (linter === "biome") {
2027
- recommendations.push("biomejs.biome");
2028
- settings["biome.enabled"] = true;
2029
- settings["eslint.enable"] = false;
2030
- settings["oxc.enable"] = false;
2031
- }
2032
- if (formatter === "oxfmt") {
2033
- if (!recommendations.includes("oxc.oxc-vscode")) {
2034
- recommendations.push("oxc.oxc-vscode");
2035
- }
2036
- settings["editor.defaultFormatter"] = "oxc.oxc-vscode";
2037
- settings["[json]"] = {
2038
- "editor.defaultFormatter": "vscode.json-language-features"
2039
- };
2040
- settings["[jsonc]"] = {
2041
- "editor.defaultFormatter": "vscode.json-language-features"
2042
- };
2043
- } else if (formatter === "prettier") {
2044
- recommendations.push("esbenp.prettier-vscode");
2045
- settings["editor.defaultFormatter"] = "esbenp.prettier-vscode";
2046
- } else if (formatter === "biome") {
2047
- if (!recommendations.includes("biomejs.biome")) {
2048
- recommendations.push("biomejs.biome");
2049
- }
2050
- settings["editor.defaultFormatter"] = "biomejs.biome";
2051
- }
2052
- files[".vscode/extensions.json"] = {
2053
- type: "text",
2054
- content: JSON.stringify({ recommendations }, null, 2)
2055
- };
2056
- const codeSnippets = {};
2057
- if (recommendations.length > 0) {
2058
- codeSnippets["vscode-extension-suggestion"] = recommendations;
2059
- }
2454
+ function renderVscodeFiles(files, linter, formatter, packageManager = "pnpm") {
2060
2455
  Object.assign(
2061
2456
  files,
2062
- generateVscodeFiles$1({
2063
- codeSnippets,
2064
- vscodeSettings: settings
2457
+ renderVscodeFiles$1({
2458
+ linter,
2459
+ formatter,
2460
+ isMonorepo: true,
2461
+ packageManager
2065
2462
  })
2066
2463
  );
2067
2464
  }
2068
2465
 
2069
- const monorepo = {
2070
- __proto__: null,
2071
- generateEslintConfigPackage: generateEslintConfigPackage,
2072
- generateMonorepo: generateMonorepo,
2073
- generateOxfmtConfigPackage: generateOxfmtConfigPackage,
2074
- generateOxlintConfigPackage: generateOxlintConfigPackage,
2075
- generatePrettierConfigPackage: generatePrettierConfigPackage,
2076
- generateTypescriptConfigPackage: generateTypescriptConfigPackage,
2077
- generateVscodeFiles: generateVscodeFiles
2078
- };
2079
-
2080
2466
  function toBiomeLevel(level) {
2081
2467
  return level;
2082
2468
  }
2083
- function generateBiome(generator, options) {
2469
+ function planBiome(builder, options) {
2084
2470
  if (options == null || !options.linter && !options.formatter) {
2085
2471
  return;
2086
2472
  }
2087
- const version = generator.getVersion("@biomejs/biome");
2088
- generator.addDevDependency("@biomejs/biome");
2089
- const { rules } = defaultLinterConfig;
2473
+ const version = builder.getVersion("@biomejs/biome");
2474
+ builder.addDevDependency("@biomejs/biome");
2090
2475
  const biomeConfig = {
2091
2476
  $schema: `https://biomejs.dev/schemas/${version}/schema.json`
2092
2477
  };
2093
2478
  if (options.linter) {
2479
+ const linterConfig = options.linter.config;
2094
2480
  biomeConfig.linter = {
2095
2481
  enabled: true,
2096
2482
  rules: {
2097
2483
  recommended: true,
2098
2484
  correctness: {
2099
- noUnusedVariables: toBiomeLevel(rules.noUnusedVars.level)
2485
+ noUnusedVariables: toBiomeLevel(linterConfig.rules.noUnusedVars.level)
2100
2486
  }
2101
2487
  }
2102
2488
  };
@@ -2106,19 +2492,20 @@ function generateBiome(generator, options) {
2106
2492
  };
2107
2493
  }
2108
2494
  if (options.formatter) {
2495
+ const formatterConfig = options.formatter.config;
2109
2496
  biomeConfig.formatter = {
2110
2497
  enabled: true,
2111
- lineWidth: defaultFormatterConfig.printWidth,
2112
- indentWidth: defaultFormatterConfig.tabWidth,
2113
- indentStyle: "space"
2498
+ lineWidth: formatterConfig.printWidth,
2499
+ indentWidth: formatterConfig.tabWidth,
2500
+ indentStyle: formatterConfig.useTabs ? "tab" : "space"
2114
2501
  };
2115
2502
  biomeConfig.javascript = {
2116
2503
  formatter: {
2117
- semicolons: "always" ,
2118
- quoteStyle: "single" ,
2119
- trailingCommas: defaultFormatterConfig.trailingComma,
2120
- bracketSpacing: defaultFormatterConfig.bracketSpacing,
2121
- arrowParentheses: "always"
2504
+ semicolons: formatterConfig.semi ? "always" : "asNeeded",
2505
+ quoteStyle: formatterConfig.singleQuote ? "single" : "double",
2506
+ trailingCommas: formatterConfig.trailingComma,
2507
+ bracketSpacing: formatterConfig.bracketSpacing,
2508
+ arrowParentheses: formatterConfig.arrowParens === "always" ? "always" : "asNeeded"
2122
2509
  }
2123
2510
  };
2124
2511
  biomeConfig.json = {
@@ -2131,53 +2518,48 @@ function generateBiome(generator, options) {
2131
2518
  enabled: false
2132
2519
  };
2133
2520
  }
2134
- const isStealth = generator.isStealthConfig();
2521
+ const isStealth = builder.isStealthConfig();
2135
2522
  if (isStealth) {
2136
- generator.addFile(".config/biome.json", {
2523
+ builder.addFile(".config/biome.json", {
2137
2524
  type: "text",
2138
2525
  content: JSON.stringify(biomeConfig, null, 2)
2139
2526
  });
2140
2527
  if (options.linter) {
2141
- generator.addScript("lint", "biome lint --config-path .config .");
2528
+ builder.addScripts(packageJsonScripts.lint.biome(".config"));
2142
2529
  }
2143
2530
  if (options.formatter) {
2144
- generator.addScript("format", "biome format --config-path .config --write .");
2531
+ builder.addScripts(packageJsonScripts.format.biome(".config"));
2145
2532
  }
2146
- generator.addVscodeSetting("biome.linter.configPath", ".config/biome.json");
2147
2533
  } else {
2148
- generator.addFile("biome.json", {
2534
+ builder.addFile("biome.json", {
2149
2535
  type: "text",
2150
2536
  content: JSON.stringify(biomeConfig, null, 2)
2151
2537
  });
2152
2538
  if (options.linter) {
2153
- generator.addScript("lint", "biome lint .");
2539
+ builder.addScripts(packageJsonScripts.lint.biome());
2154
2540
  }
2155
2541
  if (options.formatter) {
2156
- generator.addScript("format", "biome format --write .");
2542
+ builder.addScripts(packageJsonScripts.format.biome());
2157
2543
  }
2158
2544
  }
2159
2545
  const roles = [];
2160
2546
  if (options.linter) roles.push("linter");
2161
2547
  if (options.formatter) roles.push("formatter");
2162
- generator.inject(
2548
+ builder.inject(
2163
2549
  "readme-tools",
2164
2550
  `[Biome](https://biomejs.dev/) - Fast ${roles.join(" and ")} for JavaScript and TypeScript`
2165
2551
  );
2166
- generator.inject("vscode-extension-suggestion", "biomejs.biome");
2167
- generator.addVscodeSetting("biome.enabled", true);
2168
- if (options.formatter) {
2169
- generator.addVscodeSetting("editor.defaultFormatter", "biomejs.biome");
2170
- }
2552
+ builder.inject("vscode-extension-suggestion", "biomejs.biome");
2171
2553
  }
2172
2554
 
2173
- function generateDrei(generator, options) {
2555
+ function planDrei(builder, options) {
2174
2556
  if (options == null) {
2175
2557
  return;
2176
2558
  }
2177
- generator.addDependency("@react-three/drei");
2178
- generator.inject("import", `import { Environment } from "@react-three/drei"`);
2179
- generator.inject("scene", '<Environment background preset="city" />');
2180
- generator.inject(
2559
+ builder.addDependency("@react-three/drei");
2560
+ builder.inject("import", `import { Environment } from "@react-three/drei"`);
2561
+ builder.inject("scene", '<Environment background preset="city" />');
2562
+ builder.inject(
2181
2563
  "readme-libraries",
2182
2564
  `[@react-three/drei](https://drei.docs.pmnd.rs/) - Useful helpers for @react-three/fiber`
2183
2565
  );
@@ -2186,25 +2568,28 @@ function generateDrei(generator, options) {
2186
2568
  function toEslintLevel(level) {
2187
2569
  return level;
2188
2570
  }
2189
- function generateEslint(generator, options) {
2190
- generator.addDevDependency("eslint");
2191
- const template = generator.options.template ?? "vanilla";
2571
+ function planEslint(builder, options) {
2572
+ if (options == null) {
2573
+ return;
2574
+ }
2575
+ builder.addDevDependency("eslint");
2576
+ const template = builder.options.template ?? "vanilla";
2192
2577
  const baseTemplate = getBaseTemplate(template);
2193
2578
  const isTypescript = getLanguageFromTemplate(template) === "typescript";
2194
2579
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2195
- const { rules } = defaultLinterConfig;
2580
+ const { rules } = options.config;
2196
2581
  const imports = ['import js from "@eslint/js"'];
2197
2582
  const configs = ["js.configs.recommended"];
2198
2583
  if (isTypescript) {
2199
- generator.addDevDependency("typescript-eslint");
2584
+ builder.addDevDependency("typescript-eslint");
2200
2585
  imports.push('import tseslint from "typescript-eslint"');
2201
2586
  configs.push("...tseslint.configs.recommended");
2202
2587
  }
2203
2588
  if (isReact) {
2204
- generator.addDevDependency("eslint-plugin-react-hooks");
2589
+ builder.addDevDependency("eslint-plugin-react-hooks");
2205
2590
  imports.push('import reactHooks from "eslint-plugin-react-hooks"');
2206
2591
  }
2207
- const ignoresArray = JSON.stringify(defaultLinterConfig.ignorePatterns);
2592
+ const ignoresArray = JSON.stringify(options.config.ignorePatterns);
2208
2593
  const unusedVarsRule = isTypescript ? "@typescript-eslint/no-unused-vars" : "no-unused-vars";
2209
2594
  const rulesConfig = {
2210
2595
  [unusedVarsRule]: [
@@ -2238,34 +2623,30 @@ function generateEslint(generator, options) {
2238
2623
  },`,
2239
2624
  "]"
2240
2625
  ].filter(Boolean).join("\n");
2241
- const isStealth = generator.isStealthConfig();
2626
+ const isStealth = builder.isStealthConfig();
2242
2627
  if (isStealth) {
2243
- generator.addFile(".config/eslint.config.js", {
2628
+ builder.addFile(".config/eslint.config.js", {
2244
2629
  type: "text",
2245
2630
  content: configContent
2246
2631
  });
2247
- generator.addScript("lint", "eslint --config .config/eslint.config.js .");
2248
- generator.addVscodeSetting("eslint.options", {
2249
- overrideConfigFile: ".config/eslint.config.js"
2250
- });
2632
+ builder.addScripts(packageJsonScripts.lint.eslint(".config/eslint.config.js"));
2251
2633
  } else {
2252
- generator.addFile("eslint.config.js", {
2634
+ builder.addFile("eslint.config.js", {
2253
2635
  type: "text",
2254
2636
  content: configContent
2255
2637
  });
2256
- generator.addScript("lint", "eslint .");
2638
+ builder.addScripts(packageJsonScripts.lint.eslint());
2257
2639
  }
2258
- generator.inject(
2640
+ builder.inject(
2259
2641
  "readme-tools",
2260
2642
  "[ESLint](https://eslint.org/) - Linter for JavaScript and TypeScript"
2261
2643
  );
2262
- generator.inject("vscode-extension-suggestion", "dbaeumer.vscode-eslint");
2263
- generator.addVscodeSetting("eslint.enable", true);
2644
+ builder.inject("vscode-extension-suggestion", "dbaeumer.vscode-eslint");
2264
2645
  }
2265
2646
 
2266
- function generateFiber(generator, _options) {
2267
- generator.inject("import", `import { Box } from "./box.js"`);
2268
- generator.inject(
2647
+ function planFiber(builder, _options) {
2648
+ builder.inject("import", `import { Box } from "./box.js"`);
2649
+ builder.inject(
2269
2650
  "scene",
2270
2651
  [
2271
2652
  `<ambientLight intensity={Math.PI / 2} />`,
@@ -2275,7 +2656,7 @@ function generateFiber(generator, _options) {
2275
2656
  `<Box position={[1.2, 0, 0]} />`
2276
2657
  ].join("\n")
2277
2658
  );
2278
- generator.addFile("src/box.tsx", {
2659
+ builder.addFile("src/box.tsx", {
2279
2660
  type: "text",
2280
2661
  content: `import type { Mesh } from 'three'
2281
2662
  import { useRef, useState } from 'react'
@@ -2302,11 +2683,11 @@ export function Box(props: ThreeElements['mesh']) {
2302
2683
  });
2303
2684
  }
2304
2685
 
2305
- function generateGithubPages(generator, options) {
2306
- if (options === false || getPackageManagerName(generator.options.packageManager) !== "npm") {
2686
+ function planGithubPages(builder, options) {
2687
+ if (options === false || getPackageManagerName(builder.options.packageManager) !== "npm") {
2307
2688
  return;
2308
2689
  }
2309
- generator.addFile(".github/workflows/gh-pages.yml", {
2690
+ builder.addFile(".github/workflows/gh-pages.yml", {
2310
2691
  type: "text",
2311
2692
  content: `name: Deploy to Github Pages
2312
2693
 
@@ -2352,10 +2733,10 @@ jobs:
2352
2733
  uses: actions/deploy-pages@v4
2353
2734
  `
2354
2735
  });
2355
- generator.inject("readme-start", `A github pages deployment action is configurd.`);
2356
- if (generator.options.githubUserName != null && generator.options.githubRepoName != null) {
2357
- const address = `${generator.options.githubUserName}.github.io/${generator.options.githubRepoName}`;
2358
- generator.inject(
2736
+ builder.inject("readme-start", `A github pages deployment action is configurd.`);
2737
+ if (builder.options.githubUserName != null && builder.options.githubRepoName != null) {
2738
+ const address = `${builder.options.githubUserName}.github.io/${builder.options.githubRepoName}`;
2739
+ builder.inject(
2359
2740
  "readme-start",
2360
2741
  `Your app will be publish at [${address}](https://${address}) once the github action is finished.
2361
2742
  `
@@ -2363,228 +2744,199 @@ jobs:
2363
2744
  }
2364
2745
  }
2365
2746
 
2366
- function generateHandle(generator, options) {
2747
+ function planHandle(builder, options) {
2367
2748
  if (options == null) {
2368
2749
  return;
2369
2750
  }
2370
- generator.addDependency("@react-three/handle");
2371
- generator.inject(
2751
+ builder.addDependency("@react-three/handle");
2752
+ builder.inject(
2372
2753
  "readme-libraries",
2373
2754
  `[@react-three/handle](https://pmndrs.github.io/xr/docs/handles/introduction) - interactive controls and handles for your 3D objects`
2374
2755
  );
2375
2756
  }
2376
2757
 
2377
- function generateKoota(generator, options) {
2758
+ function planKoota(builder, options) {
2378
2759
  if (options == null) {
2379
2760
  return;
2380
2761
  }
2381
- generator.addDependency("koota");
2382
- generator.inject(
2762
+ builder.addDependency("koota");
2763
+ builder.inject(
2383
2764
  "readme-libraries",
2384
2765
  `[koota](https://github.com/pmndrs/koota) - ECS-based state management library optimized for real-time apps, games, and XR experiences`
2385
2766
  );
2386
2767
  }
2387
2768
 
2388
- function generateLeva(generator, options) {
2769
+ function planLeva(builder, options) {
2389
2770
  if (options == null) {
2390
2771
  return;
2391
2772
  }
2392
- generator.addDependency("leva");
2393
- generator.inject(
2773
+ builder.addDependency("leva");
2774
+ builder.inject(
2394
2775
  "readme-libraries",
2395
2776
  `[leva](https://github.com/pmndrs/leva) - HTML GUI panel for React with lightweight, beautiful and extensible controls`
2396
2777
  );
2397
2778
  }
2398
2779
 
2399
- function generateOffscreen(generator, options) {
2780
+ function planOffscreen(builder, options) {
2400
2781
  if (options == null) {
2401
2782
  return;
2402
2783
  }
2403
- if (generator.options.xr != null) {
2784
+ if (builder.options.xr != null) {
2404
2785
  console.info(
2405
2786
  color__default.blue("Info:"),
2406
2787
  "@react-three/offscreen is disabled because it is not supported with XR"
2407
2788
  );
2408
2789
  return;
2409
2790
  }
2410
- generator.addDependency("@react-three/offscreen");
2411
- generator.inject(
2791
+ builder.addDependency("@react-three/offscreen");
2792
+ builder.inject(
2412
2793
  "readme-libraries",
2413
2794
  `[@react-three/offscreen](https://github.com/pmndrs/offscreen) - Offload your scene to a worker thread for better performance`
2414
2795
  );
2415
2796
  }
2416
2797
 
2417
- function generateOxfmt(generator, options) {
2418
- const isMonorepo = generator.options.workspaceRoot != null;
2798
+ function planOxfmt(builder, options) {
2799
+ if (options == null) {
2800
+ return;
2801
+ }
2802
+ const isMonorepo = builder.options.workspaceRoot != null;
2419
2803
  if (isMonorepo) {
2420
- generator.addDevDependency("@config/oxfmt", { version: "workspace:*" });
2804
+ builder.addDevDependency("@config/oxfmt", { version: "workspace:*" });
2421
2805
  const configPath = "node_modules/@config/oxfmt/base.json";
2422
- generator.addScript("format", `oxfmt -c ${configPath} --write .`);
2423
- generator.addVscodeSetting("oxc.fmt.configPath", configPath);
2806
+ builder.addScripts(packageJsonScripts.format.oxfmt(configPath));
2424
2807
  } else {
2425
- generator.addDevDependency("oxfmt");
2426
- const isStealth = generator.isStealthConfig();
2808
+ builder.addDevDependency("oxfmt");
2809
+ const isStealth = builder.isStealthConfig();
2427
2810
  if (isStealth) {
2428
- generator.addFile(".config/oxfmt.json", {
2811
+ builder.addFile(".config/oxfmt.json", {
2429
2812
  type: "text",
2430
- content: JSON.stringify(defaultOxfmtConfig, null, 2)
2813
+ content: JSON.stringify(toOxfmtConfig(options.config), null, 2)
2431
2814
  });
2432
- generator.addScript("format", "oxfmt -c .config/oxfmt.json --write .");
2433
- generator.addVscodeSetting("oxc.fmt.configPath", ".config/oxfmt.json");
2815
+ builder.addScripts(packageJsonScripts.format.oxfmt(".config/oxfmt.json"));
2434
2816
  } else {
2435
- generator.addFile("oxfmt.json", {
2817
+ builder.addFile("oxfmt.json", {
2436
2818
  type: "text",
2437
- content: JSON.stringify(defaultOxfmtConfig, null, 2)
2819
+ content: JSON.stringify(toOxfmtConfig(options.config), null, 2)
2438
2820
  });
2439
- generator.addScript("format", "oxfmt -c oxfmt.json --write .");
2821
+ builder.addScripts(packageJsonScripts.format.oxfmt("oxfmt.json"));
2440
2822
  }
2441
2823
  }
2442
- generator.inject(
2824
+ builder.inject(
2443
2825
  "readme-tools",
2444
2826
  "[Oxfmt](https://oxc.rs/docs/guide/usage/formatter) - Fast Prettier-compatible code formatter"
2445
2827
  );
2446
- generator.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2447
- generator.addVscodeSetting("editor.defaultFormatter", "oxc.oxc-vscode");
2448
- generator.addVscodeSetting("[json]", {
2449
- "editor.defaultFormatter": "vscode.json-language-features"
2450
- });
2451
- generator.addVscodeSetting("[jsonc]", {
2452
- "editor.defaultFormatter": "vscode.json-language-features"
2453
- });
2454
- generator.addVscodeSetting("[markdown]", {
2455
- "editor.defaultFormatter": "vscode.markdown-language-features"
2456
- });
2457
- generator.addVscodeSetting("[yaml]", {
2458
- "editor.defaultFormatter": "redhat.vscode-yaml"
2459
- });
2828
+ builder.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2460
2829
  }
2461
2830
 
2462
- function toOxlintLevel(level) {
2463
- return level;
2464
- }
2465
- function generateOxlint(generator, options) {
2466
- const template = generator.options.template ?? "vanilla";
2831
+ function planOxlint(builder, options) {
2832
+ if (options == null) {
2833
+ return;
2834
+ }
2835
+ const template = builder.options.template ?? "vanilla";
2467
2836
  const baseTemplate = getBaseTemplate(template);
2837
+ const isTypescript = getLanguageFromTemplate(template) === "typescript";
2468
2838
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2469
- const isMonorepo = generator.options.workspaceRoot != null;
2839
+ const isMonorepo = builder.options.workspaceRoot != null;
2470
2840
  if (isMonorepo) {
2471
- generator.addDevDependency("@config/oxlint", { version: "workspace:*" });
2841
+ builder.addDevDependency("@config/oxlint", { version: "workspace:*" });
2472
2842
  const configPath = isReact ? "node_modules/@config/oxlint/react.json" : "node_modules/@config/oxlint/base.json";
2473
- generator.addScript("lint", `oxlint -c ${configPath}`);
2474
- generator.addVscodeSetting("oxc.configPath", configPath);
2843
+ builder.addScripts(packageJsonScripts.lint.oxlint(configPath));
2475
2844
  } else {
2476
- generator.addDevDependency("oxlint");
2477
- const isStealth = generator.isStealthConfig();
2478
- const { rules } = defaultLinterConfig;
2479
- const plugins = ["unicorn", "typescript", "oxc"];
2480
- if (isReact) {
2481
- plugins.push("react");
2482
- }
2483
- const oxlintConfig = {
2484
- $schema: isStealth ? "../node_modules/oxlint/configuration_schema.json" : "./node_modules/oxlint/configuration_schema.json",
2485
- plugins,
2486
- rules: {
2487
- "no-unused-vars": [
2488
- toOxlintLevel(rules.noUnusedVars.level),
2489
- {
2490
- argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
2491
- varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
2492
- caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
2493
- }
2494
- ],
2495
- "no-useless-escape": "off",
2496
- "no-unused-expressions": [
2497
- toOxlintLevel(rules.noUnusedExpressions.level),
2498
- { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
2499
- ]
2500
- },
2501
- ignorePatterns: defaultLinterConfig.ignorePatterns
2502
- };
2845
+ builder.addDevDependency("oxlint");
2846
+ if (isTypescript) {
2847
+ builder.addDevDependency("oxlint-tsgolint");
2848
+ }
2849
+ const isStealth = builder.isStealthConfig();
2850
+ const oxlintConfig = renderOxlintConfig({
2851
+ schemaPath: isStealth ? "../node_modules/oxlint/configuration_schema.json" : "./node_modules/oxlint/configuration_schema.json",
2852
+ react: isReact,
2853
+ typescript: isTypescript,
2854
+ config: options.config
2855
+ });
2503
2856
  if (isStealth) {
2504
- generator.addFile(".config/oxlint.json", {
2857
+ builder.addFile(".config/oxlint.json", {
2505
2858
  type: "text",
2506
2859
  content: JSON.stringify(oxlintConfig, null, 2)
2507
2860
  });
2508
- generator.addScript("lint", "oxlint -c .config/oxlint.json");
2509
- generator.addVscodeSetting("oxc.configPath", ".config/oxlint.json");
2861
+ builder.addScripts(packageJsonScripts.lint.oxlint(".config/oxlint.json"));
2510
2862
  } else {
2511
- generator.addFile("oxlint.json", {
2863
+ builder.addFile("oxlint.json", {
2512
2864
  type: "text",
2513
2865
  content: JSON.stringify(oxlintConfig, null, 2)
2514
2866
  });
2515
- generator.addScript("lint", "oxlint");
2867
+ builder.addScripts(packageJsonScripts.lint.oxlint());
2516
2868
  }
2517
2869
  }
2518
- generator.inject(
2870
+ builder.inject(
2519
2871
  "readme-tools",
2520
2872
  "[Oxlint](https://oxc.rs/docs/guide/usage/linter) - A fast linter for JavaScript and TypeScript"
2521
2873
  );
2522
- generator.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2523
- generator.addVscodeSetting("oxc.enable", true);
2874
+ builder.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2524
2875
  }
2525
2876
 
2526
- function generatePostprocessing(generator, options) {
2877
+ function planPostprocessing(builder, options) {
2527
2878
  if (options == null) {
2528
2879
  return;
2529
2880
  }
2530
- if (generator.options.xr != null) {
2881
+ if (builder.options.xr != null) {
2531
2882
  console.info(
2532
2883
  color__default.blue("Info:"),
2533
2884
  "@react-three/postprocessing is disabled because it is not supported with XR"
2534
2885
  );
2535
2886
  return;
2536
2887
  }
2537
- generator.addDependency("@react-three/postprocessing");
2538
- generator.inject(
2888
+ builder.addDependency("@react-three/postprocessing");
2889
+ builder.inject(
2539
2890
  "readme-libraries",
2540
2891
  `[@react-three/postprocessing](https://react-postprocessing.docs.pmnd.rs/) - Post-processing effects for @react-three/fiber`
2541
2892
  );
2542
2893
  }
2543
2894
 
2544
- function generatePrettier(generator, options) {
2545
- generator.addDevDependency("prettier");
2546
- const isStealth = generator.isStealthConfig();
2895
+ function planPrettier(builder, options) {
2896
+ if (options == null) {
2897
+ return;
2898
+ }
2899
+ builder.addDevDependency("prettier");
2900
+ const isStealth = builder.isStealthConfig();
2547
2901
  if (isStealth) {
2548
- generator.addFile(".config/prettier.json", {
2902
+ builder.addFile(".config/prettier.json", {
2903
+ type: "text",
2904
+ content: JSON.stringify(toPrettierConfig(options.config), null, 2)
2905
+ });
2906
+ builder.addFile(".config/prettierignore", {
2549
2907
  type: "text",
2550
- content: JSON.stringify(defaultPrettierConfig, null, 2)
2908
+ content: toPrettierIgnoreContent(options.config)
2551
2909
  });
2552
- generator.addScript("format", "prettier --config .config/prettier.json --write .");
2553
- generator.addVscodeSetting("prettier.configPath", ".config/prettier.json");
2910
+ builder.addScripts(
2911
+ packageJsonScripts.format.prettier(".config/prettier.json", ".config/prettierignore")
2912
+ );
2554
2913
  } else {
2555
- generator.addFile(".prettierrc", {
2914
+ builder.addFile(".prettierrc", {
2915
+ type: "text",
2916
+ content: JSON.stringify(toPrettierConfig(options.config), null, 2)
2917
+ });
2918
+ builder.addFile(".prettierignore", {
2556
2919
  type: "text",
2557
- content: JSON.stringify(defaultPrettierConfig, null, 2)
2920
+ content: toPrettierIgnoreContent(options.config)
2558
2921
  });
2559
- generator.addScript("format", "prettier --write .");
2922
+ builder.addScripts(packageJsonScripts.format.prettier());
2560
2923
  }
2561
- generator.inject("readme-tools", "[Prettier](https://prettier.io/) - Opinionated code formatter");
2562
- generator.inject("vscode-extension-suggestion", "esbenp.prettier-vscode");
2563
- generator.addVscodeSetting("editor.defaultFormatter", "esbenp.prettier-vscode");
2924
+ builder.inject("readme-tools", "[Prettier](https://prettier.io/) - Opinionated code formatter");
2925
+ builder.inject("vscode-extension-suggestion", "esbenp.prettier-vscode");
2564
2926
  }
2565
2927
 
2566
- function generateRapier(generator, options) {
2928
+ function planRapier(builder, options) {
2567
2929
  if (options == null) {
2568
2930
  return;
2569
2931
  }
2570
- generator.addDependency("@react-three/rapier");
2571
- generator.inject(
2932
+ builder.addDependency("@react-three/rapier");
2933
+ builder.inject(
2572
2934
  "readme-libraries",
2573
2935
  `[@react-three/rapier](https://github.com/pmndrs/react-three-rapier) - Physics based on Rapier for your @react-three/fiber scene`
2574
2936
  );
2575
2937
  }
2576
2938
 
2577
- function unique(...array) {
2578
- const set = /* @__PURE__ */ new Set();
2579
- for (const arr of array) {
2580
- for (const item of arr) {
2581
- set.add(item);
2582
- }
2583
- }
2584
- return Array.from(set);
2585
- }
2586
-
2587
- function generateProvidersModule(generator) {
2939
+ function generateProvidersModule(builder) {
2588
2940
  const canvasProviders = [];
2589
2941
  const globalProviders = [];
2590
2942
  const providerDefs = {
@@ -2650,29 +3002,23 @@ function generateProvidersModule(generator) {
2650
3002
  ]
2651
3003
  }
2652
3004
  };
2653
- if (generator.options.rapier) {
3005
+ if (builder.options.rapier) {
2654
3006
  canvasProviders.push("rapier");
2655
3007
  }
2656
- if (!!generator.options.postprocessing && !generator.options.xr) {
3008
+ if (!!builder.options.postprocessing && !builder.options.xr) {
2657
3009
  canvasProviders.push("postprocessing");
2658
3010
  }
2659
- if (generator.options.uikit) {
3011
+ if (builder.options.uikit) {
2660
3012
  globalProviders.push("uikit");
2661
3013
  }
2662
3014
  function generateProviderFunction(name, { jsdoc, providers }) {
2663
3015
  const resolvedProviders = providers.map((provider) => providerDefs[provider]);
2664
3016
  const providerProps = resolvedProviders.flatMap((provider) => provider.props || []);
2665
3017
  const providerImports = resolvedProviders.flatMap((provider) => provider.import);
2666
- const wrappedComponents = resolvedProviders.filter(
2667
- (provider) => provider.type === "wrapped-jsx"
2668
- );
2669
- const inlineComponents = resolvedProviders.filter(
2670
- (provider) => provider.type === "inline-jsx"
2671
- );
2672
- const layoutEffects = resolvedProviders.filter(
2673
- (provider) => provider.type === "layout-effect"
2674
- );
2675
- const declaredProps = providerProps.map((prop) => `${prop.declaredPropName} = ${prop.declaredPropDefaultValue}`).join(", ");
3018
+ const wrappedComponents = resolvedProviders.filter((provider) => provider.type === "wrapped-jsx");
3019
+ const inlineComponents = resolvedProviders.filter((provider) => provider.type === "inline-jsx");
3020
+ const layoutEffects = resolvedProviders.filter((provider) => provider.type === "layout-effect");
3021
+ const declaredProps = providerProps.map((prop) => `${prop.declaredPropName} = ${String(prop.declaredPropDefaultValue)}`).join(", ");
2676
3022
  const declaredTypes = providerProps.map((prop) => `${prop.declaredPropName}?: ${prop.declaredPropType}`).join("; ");
2677
3023
  const reactImports = ["type ReactNode"];
2678
3024
  if (layoutEffects.length) {
@@ -2689,11 +3035,11 @@ ${jsdoc.split("\n").map((line) => ` * ${line}`).join("\n")}
2689
3035
  ${layoutEffects.length ? `
2690
3036
  useLayoutEffect(() => {
2691
3037
  ${layoutEffects.map((effect) => effect.code).join("\n")}
2692
- }, [${layoutEffects.map((effect) => effect.props?.[0]?.propValue)}]);
3038
+ }, [${layoutEffects.map((effect) => effect.props?.[0]?.propValue).join(", ")}]);
2693
3039
  ` : ""}
2694
3040
  return (
2695
3041
  <>
2696
- ${inlineComponents.map((provider) => provider.code)}
3042
+ ${inlineComponents.map((provider) => provider.code).join("\n")}
2697
3043
  ${wrappedComponents.reduce((acc, provider) => {
2698
3044
  const props = provider.props?.map((prop) => `${prop.propName}={${prop.propValue}}`).join(" ");
2699
3045
  return `<${provider.component} ${props}>${acc}</${provider.component}>`;
@@ -2719,20 +3065,20 @@ ${jsdoc.split("\n").map((line) => ` * ${line}`).join("\n")}
2719
3065
  ${canvas.code}
2720
3066
  `;
2721
3067
  }
2722
- function generateTriplex(generator, options) {
3068
+ function planTriplex(builder, options) {
2723
3069
  if (options == null) {
2724
3070
  return;
2725
3071
  }
2726
- generator.inject("vscode-extension-suggestion", "trytriplex.triplex-vsce");
2727
- generator.inject(
3072
+ builder.inject("vscode-extension-suggestion", "trytriplex.triplex-vsce");
3073
+ builder.inject(
2728
3074
  "readme-tools",
2729
3075
  `[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).`
2730
3076
  );
2731
- generator.addFile(".triplex/providers.tsx", {
2732
- content: generateProvidersModule(generator),
3077
+ builder.addFile(".triplex/providers.tsx", {
3078
+ content: generateProvidersModule(builder),
2733
3079
  type: "text"
2734
3080
  });
2735
- generator.addFile(".triplex/config.json", {
3081
+ builder.addFile(".triplex/config.json", {
2736
3082
  content: JSON.stringify(
2737
3083
  {
2738
3084
  $schema: "https://triplex.dev/config.schema.json",
@@ -2745,9 +3091,9 @@ function generateTriplex(generator, options) {
2745
3091
  });
2746
3092
  }
2747
3093
 
2748
- function generateTsdown(generator) {
2749
- generator.addDevDependency("tsdown");
2750
- const template = generator.options.template ?? "vanilla";
3094
+ function planTsdown(builder) {
3095
+ builder.addDevDependency("tsdown");
3096
+ const template = builder.options.template ?? "vanilla";
2751
3097
  const baseTemplate = getBaseTemplate(template);
2752
3098
  const language = getLanguageFromTemplate(template);
2753
3099
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
@@ -2767,36 +3113,36 @@ function generateTsdown(generator) {
2767
3113
  configLines.push(` },`);
2768
3114
  }
2769
3115
  configLines.push(`})`);
2770
- generator.addFile(`tsdown.config.${ext}`, {
3116
+ builder.addFile(`tsdown.config.${ext}`, {
2771
3117
  type: "text",
2772
3118
  content: configLines.join("\n")
2773
3119
  });
2774
- generator.addScript("build", "tsdown");
2775
- generator.inject(
3120
+ builder.addScripts(packageJsonScripts.build.tsdown);
3121
+ builder.inject(
2776
3122
  "readme-libraries",
2777
3123
  "[tsdown](https://github.com/nicepkg/tsdown) - Fast TypeScript bundler powered by esbuild"
2778
3124
  );
2779
3125
  }
2780
3126
 
2781
- function generateUikit(generator, options) {
3127
+ function planUikit(builder, options) {
2782
3128
  if (options == null) {
2783
3129
  return;
2784
3130
  }
2785
- generator.addDependency("@react-three/uikit");
2786
- generator.inject(
3131
+ builder.addDependency("@react-three/uikit");
3132
+ builder.inject(
2787
3133
  "readme-libraries",
2788
3134
  `[@react-three/uikit](https://pmndrs.github.io/uikit/docs/) - UI primitives for React Three Fiber`
2789
3135
  );
2790
3136
  }
2791
3137
 
2792
- function generateUnbuild(generator) {
2793
- generator.addDevDependency("unbuild");
2794
- const template = generator.options.template ?? "vanilla";
3138
+ function planUnbuild(builder) {
3139
+ builder.addDevDependency("unbuild");
3140
+ const template = builder.options.template ?? "vanilla";
2795
3141
  const baseTemplate = getBaseTemplate(template);
2796
3142
  const language = getLanguageFromTemplate(template);
2797
3143
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2798
3144
  const ext = language === "typescript" ? "ts" : "js";
2799
- const isMonorepo = generator.options.workspaceRoot != null;
3145
+ const isMonorepo = builder.options.workspaceRoot != null;
2800
3146
  const buildConfigLines = [
2801
3147
  `import { defineBuildConfig } from "unbuild"`,
2802
3148
  ``,
@@ -2814,51 +3160,54 @@ function generateUnbuild(generator) {
2814
3160
  }
2815
3161
  buildConfigLines.push(` },`);
2816
3162
  buildConfigLines.push(`})`);
2817
- const isStealth = generator.isStealthConfig() && !isMonorepo;
3163
+ const isStealth = builder.isStealthConfig() && !isMonorepo;
2818
3164
  if (isStealth) {
2819
- generator.addFile(`.config/build.config.${ext}`, {
3165
+ builder.addFile(`.config/build.config.${ext}`, {
2820
3166
  type: "text",
2821
3167
  content: buildConfigLines.join("\n")
2822
3168
  });
2823
- generator.addScript("build", `unbuild --config .config/build.config.${ext}`);
3169
+ builder.addScripts(packageJsonScripts.build.unbuild(`.config/build.config.${ext}`));
2824
3170
  } else {
2825
- generator.addFile(`build.config.${ext}`, {
3171
+ builder.addFile(`build.config.${ext}`, {
2826
3172
  type: "text",
2827
3173
  content: buildConfigLines.join("\n")
2828
3174
  });
2829
- generator.addScript("build", "unbuild");
3175
+ builder.addScripts(packageJsonScripts.build.unbuild());
2830
3176
  }
2831
- generator.inject(
3177
+ builder.inject(
2832
3178
  "readme-libraries",
2833
3179
  "[unbuild](https://github.com/unjs/unbuild) - Unified JavaScript build system"
2834
3180
  );
2835
3181
  }
2836
3182
 
2837
- function generateVitest(generator) {
2838
- generator.addDevDependency("vitest");
2839
- const template = generator.options.template ?? "vanilla";
3183
+ function planVitest(builder, options) {
3184
+ if (options == null) {
3185
+ return;
3186
+ }
3187
+ builder.addDevDependency("vitest");
3188
+ const template = builder.options.template ?? "vanilla";
2840
3189
  const baseTemplate = getBaseTemplate(template);
2841
3190
  const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2842
3191
  if (isReact) {
2843
- generator.addDevDependency("@testing-library/react");
2844
- generator.addDevDependency("@testing-library/dom");
2845
- generator.addDevDependency("jsdom");
3192
+ builder.addDevDependency("@testing-library/react");
3193
+ builder.addDevDependency("@testing-library/dom");
3194
+ builder.addDevDependency("jsdom");
2846
3195
  }
2847
3196
  if (isReact) {
2848
- generator.configureVite({ test: { environment: "jsdom" } });
3197
+ builder.configureVite({ test: { environment: "jsdom" } });
2849
3198
  }
2850
- generator.addScript("test", "vitest");
2851
- generator.inject(
3199
+ builder.addScripts(packageJsonScripts.test.vitest);
3200
+ builder.inject(
2852
3201
  "readme-tools",
2853
3202
  "[Vitest](https://vitest.dev/) - Fast unit test framework powered by Vite"
2854
3203
  );
2855
3204
  }
2856
3205
 
2857
- function generateViverse(generator, options) {
2858
- if (options == null || getPackageManagerName(generator.options.packageManager) !== "npm") {
3206
+ function planViverse(builder, options) {
3207
+ if (options == null || getPackageManagerName(builder.options.packageManager) !== "npm") {
2859
3208
  return;
2860
3209
  }
2861
- generator.addFile(".github/workflows/viverse.yml", {
3210
+ builder.addFile(".github/workflows/viverse.yml", {
2862
3211
  type: "text",
2863
3212
  content: `name: Deploy to Viverse
2864
3213
 
@@ -2909,12 +3258,12 @@ jobs:
2909
3258
  run: npx viverse-cli auth login -e \${{ secrets.VIVERSE_EMAIL }} -p \${{ secrets.VIVERSE_PASSWORD }}
2910
3259
 
2911
3260
  - name: Deploy to Viverse
2912
- run: npx viverse-cli app publish ./dist --auto-create-app --name ${generator.options.name}
3261
+ run: npx viverse-cli app publish ./dist --auto-create-app --name ${builder.options.name}
2913
3262
 
2914
3263
  `
2915
3264
  });
2916
- generator.addDependency("@viverse/cli");
2917
- generator.inject(
3265
+ builder.addDependency("@viverse/cli");
3266
+ builder.inject(
2918
3267
  "readme-start",
2919
3268
  `A GitHub CI/CD workflow for publishing to Viverse is configured.
2920
3269
 
@@ -2927,36 +3276,36 @@ You can also upload your project manually using the Viverse CLI:
2927
3276
  \`\`\`bash
2928
3277
  viverse-cli auth login -e <email> -p <password>
2929
3278
  npm run build
2930
- viverse-cli app publish ./dist --auto-create-app --name ${generator.options.name}
3279
+ viverse-cli app publish ./dist --auto-create-app --name ${builder.options.name}
2931
3280
  \`\`\`
2932
3281
  `
2933
3282
  );
2934
3283
  }
2935
3284
 
2936
- function generateXr(generator, options) {
3285
+ function planXr(builder, options) {
2937
3286
  if (options == null || options === false) {
2938
3287
  return;
2939
3288
  }
2940
3289
  if (options === true) {
2941
3290
  options = {};
2942
3291
  }
2943
- generator.addDependency("@react-three/xr");
2944
- generator.addDependency("@vitejs/plugin-basic-ssl");
2945
- generator.inject("import", "import { XR, createXRStore } from '@react-three/xr'");
2946
- generator.inject(
3292
+ builder.addDependency("@react-three/xr");
3293
+ builder.addDependency("@vitejs/plugin-basic-ssl");
3294
+ builder.inject("import", "import { XR, createXRStore } from '@react-three/xr'");
3295
+ builder.inject(
2947
3296
  `global-start`,
2948
3297
  `const store = createXRStore(${JSON.stringify(options.storeOptions ?? {})})`
2949
3298
  );
2950
- generator.inject("scene-start", "<XR store={store}>");
2951
- generator.inject("scene-end", "</XR>");
2952
- generator.inject("vite-config-import", "import basicSsl from '@vitejs/plugin-basic-ssl'");
2953
- generator.configureVite({
3299
+ builder.inject("scene-start", "<XR store={store}>");
3300
+ builder.inject("scene-end", "</XR>");
3301
+ builder.inject("vite-config-import", "import basicSsl from '@vitejs/plugin-basic-ssl';");
3302
+ builder.configureVite({
2954
3303
  server: {
2955
3304
  host: true
2956
3305
  },
2957
3306
  plugins: ["$raw:basicSsl()"]
2958
3307
  });
2959
- generator.inject(
3308
+ builder.inject(
2960
3309
  "dom-start",
2961
3310
  `<div style={{
2962
3311
  display: "flex",
@@ -2988,55 +3337,226 @@ function generateXr(generator, options) {
2988
3337
  Enter VR
2989
3338
  </button></div>`
2990
3339
  );
2991
- generator.inject(
3340
+ builder.inject(
2992
3341
  "readme-libraries",
2993
3342
  `[@react-three/xr](https://pmndrs.github.io/xr/docs/) - VR/AR support for @react-three/fiber`
2994
3343
  );
2995
3344
  }
2996
3345
 
2997
- function generateZustand(generator, options) {
3346
+ function planZustand(builder, options) {
2998
3347
  if (options == null) {
2999
3348
  return;
3000
3349
  }
3001
- generator.addDependency("zustand");
3002
- generator.inject(
3350
+ builder.addDependency("zustand");
3351
+ builder.inject(
3003
3352
  "readme-libraries",
3004
3353
  `[zustand](https://zustand.docs.pmnd.rs/) - small, fast and scalable state-management solution`
3005
3354
  );
3006
3355
  }
3007
3356
 
3008
- function merge(target, modification) {
3009
- if (modification == null) {
3010
- throw new Error(`Cannot merge "${modification}" modification into target "${target}"`);
3011
- }
3012
- if (target == null) {
3013
- return modification;
3014
- }
3015
- if (Array.isArray(target)) {
3016
- if (!Array.isArray(modification)) {
3017
- throw new Error(
3018
- `Cannot merge non-array modification "${modification}" into array target "${target}"`
3019
- );
3020
- }
3021
- return [...target, ...modification];
3022
- }
3023
- if (typeof target === "object") {
3024
- if (typeof modification != "object") {
3025
- throw new Error(
3026
- `Cannot merge non-object modification "${modification}" into object target "${target}"`
3027
- );
3357
+ function resolveProjectPlanInput(options) {
3358
+ const packageManager = options.packageManager ?? { name: "pnpm" };
3359
+ return {
3360
+ project: {
3361
+ githubUserName: options.githubUserName,
3362
+ githubRepoName: options.githubRepoName,
3363
+ name: options.name,
3364
+ projectType: options.projectType,
3365
+ template: options.template
3366
+ },
3367
+ aiAgents: {
3368
+ tool: "ai-agents",
3369
+ config: {
3370
+ platforms: options.aiPlatforms ?? []
3371
+ }
3372
+ },
3373
+ formatter: {
3374
+ tool: options.formatter ?? "prettier",
3375
+ config: structuredClone(defaultFormatterMetaConfig)
3376
+ },
3377
+ linter: {
3378
+ tool: options.linter ?? "oxlint",
3379
+ config: structuredClone(defaultLinterMetaConfig)
3380
+ },
3381
+ testing: {
3382
+ tool: options.testing ?? (options.projectType === "library" ? "vitest" : "none"),
3383
+ config: {}
3384
+ },
3385
+ typescript: {
3386
+ tool: "typescript",
3387
+ config: {
3388
+ configStrategy: options.configStrategy
3389
+ }
3390
+ },
3391
+ ide: {
3392
+ tool: options.ide ?? "vscode",
3393
+ config: {}
3394
+ },
3395
+ packageManager: {
3396
+ tool: packageManager.name,
3397
+ config: {
3398
+ version: packageManager.version,
3399
+ pnpmManageVersions: options.pnpmManageVersions
3400
+ }
3401
+ },
3402
+ libraryBundler: {
3403
+ tool: options.libraryBundler ?? "unbuild",
3404
+ config: {}
3405
+ },
3406
+ features: {
3407
+ fiber: options.fiber,
3408
+ handle: options.handle,
3409
+ drei: options.drei,
3410
+ koota: options.koota,
3411
+ leva: options.leva,
3412
+ offscreen: options.offscreen,
3413
+ postprocessing: options.postprocessing,
3414
+ rapier: options.rapier,
3415
+ triplex: options.triplex,
3416
+ viverse: options.viverse,
3417
+ uikit: options.uikit,
3418
+ xr: options.xr,
3419
+ zustand: options.zustand,
3420
+ githubPages: options.githubPages
3421
+ },
3422
+ context: {
3423
+ dependencies: options.dependencies,
3424
+ engine: options.engine,
3425
+ files: options.files,
3426
+ injections: options.injections,
3427
+ replacements: options.replacements,
3428
+ versions: options.versions,
3429
+ workspaceRoot: options.workspaceRoot,
3430
+ workspaceDependencies: options.workspaceDependencies
3028
3431
  }
3029
- const result = { ...target };
3030
- for (const modificationKey in modification) {
3031
- result[modificationKey] = merge(target[modificationKey], modification[modificationKey]);
3432
+ };
3433
+ }
3434
+ function projectPlanInputToOptions(input) {
3435
+ return {
3436
+ ...input.project,
3437
+ aiPlatforms: input.aiAgents.config.platforms.length > 0 ? input.aiAgents.config.platforms : void 0,
3438
+ formatter: input.formatter.tool,
3439
+ linter: input.linter.tool,
3440
+ testing: input.testing.tool,
3441
+ configStrategy: input.typescript.config.configStrategy,
3442
+ ide: input.ide.tool,
3443
+ packageManager: packageManagerSpecFromInput(input.packageManager),
3444
+ pnpmManageVersions: input.packageManager.config.pnpmManageVersions,
3445
+ libraryBundler: input.libraryBundler.tool,
3446
+ ...input.features,
3447
+ dependencies: input.context.dependencies,
3448
+ engine: input.context.engine,
3449
+ files: input.context.files,
3450
+ injections: input.context.injections,
3451
+ replacements: input.context.replacements,
3452
+ versions: input.context.versions,
3453
+ workspaceRoot: input.context.workspaceRoot,
3454
+ workspaceDependencies: input.context.workspaceDependencies
3455
+ };
3456
+ }
3457
+ function resolveWorkspacePlanInput(params) {
3458
+ return {
3459
+ project: {
3460
+ name: params.name
3461
+ },
3462
+ aiAgents: {
3463
+ tool: "ai-agents",
3464
+ config: {
3465
+ platforms: params.aiPlatforms ?? []
3466
+ }
3467
+ },
3468
+ formatter: {
3469
+ tool: params.formatter,
3470
+ config: structuredClone(defaultFormatterMetaConfig)
3471
+ },
3472
+ linter: {
3473
+ tool: params.linter,
3474
+ config: structuredClone(defaultLinterMetaConfig)
3475
+ },
3476
+ ide: {
3477
+ tool: params.ide ?? "vscode",
3478
+ config: {}
3479
+ },
3480
+ packageManager: {
3481
+ tool: params.packageManager.name,
3482
+ config: {
3483
+ version: params.packageManager.version,
3484
+ pnpmManageVersions: params.pnpmManageVersions
3485
+ }
3486
+ },
3487
+ context: {
3488
+ engine: params.engine,
3489
+ pnpmManageVersions: params.pnpmManageVersions,
3490
+ versions: params.versions
3032
3491
  }
3033
- return result;
3034
- }
3035
- console.warn(`target "${target}" is overwritten with modification "${modification}"`);
3036
- return modification;
3492
+ };
3493
+ }
3494
+ function workspacePlanInputToMonorepoParams(input) {
3495
+ return {
3496
+ name: input.project.name,
3497
+ linter: input.linter.tool,
3498
+ formatter: input.formatter.tool,
3499
+ packageManager: packageManagerSpecFromInput(input.packageManager),
3500
+ pnpmManageVersions: input.context.pnpmManageVersions,
3501
+ engine: input.context.engine,
3502
+ versions: input.context.versions,
3503
+ aiPlatforms: input.aiAgents.config.platforms.length > 0 ? input.aiAgents.config.platforms : void 0,
3504
+ ide: input.ide.tool
3505
+ };
3506
+ }
3507
+ function isProjectPlanInput(input) {
3508
+ return "project" in input && "formatter" in input && typeof input.formatter === "object";
3509
+ }
3510
+ function isWorkspacePlanInput(input) {
3511
+ return "project" in input && "formatter" in input && typeof input.formatter === "object";
3512
+ }
3513
+ function packageManagerSpecFromInput(packageManager) {
3514
+ return {
3515
+ name: packageManager.tool,
3516
+ version: packageManager.config.version
3517
+ };
3518
+ }
3519
+
3520
+ async function resolveProjectFacts(input) {
3521
+ const options = projectPlanInputToOptions(input);
3522
+ options.packageManager = await resolvePackageManager(options);
3523
+ options.engine = await resolveEngine(options);
3524
+ options.versions = await resolveProjectPackageVersions(options);
3525
+ return resolveProjectPlanInput(options);
3526
+ }
3527
+ async function resolveWorkspaceFacts(input) {
3528
+ const params = workspacePlanInputToMonorepoParams(input);
3529
+ const options = {
3530
+ name: params.name,
3531
+ linter: params.linter,
3532
+ formatter: params.formatter,
3533
+ packageManager: params.packageManager,
3534
+ engine: params.engine,
3535
+ pnpmManageVersions: params.pnpmManageVersions,
3536
+ versions: params.versions
3537
+ };
3538
+ const packageManager = await resolvePackageManager(options);
3539
+ const engine = await resolveEngine(options);
3540
+ const versions = await resolveMonorepoRootPackageVersions({
3541
+ linter: params.linter,
3542
+ formatter: params.formatter,
3543
+ engine,
3544
+ versions: params.versions
3545
+ });
3546
+ return resolveWorkspacePlanInput({
3547
+ ...params,
3548
+ packageManager,
3549
+ engine,
3550
+ versions
3551
+ });
3037
3552
  }
3038
3553
 
3039
- function generate(options) {
3554
+ async function planProject(input) {
3555
+ const planInput = isProjectPlanInput(input) ? input : resolveProjectPlanInput(input);
3556
+ return createProjectPlan(await resolveProjectFacts(planInput));
3557
+ }
3558
+ function createProjectPlan(planInput) {
3559
+ const options = projectPlanInputToOptions(planInput);
3040
3560
  const clonedOptions = structuredClone(options);
3041
3561
  const template = clonedOptions.template ?? "vanilla";
3042
3562
  const baseTemplate = getBaseTemplate(template);
@@ -3045,7 +3565,8 @@ function generate(options) {
3045
3565
  const isReact = baseTemplate === "react";
3046
3566
  const isR3f = baseTemplate === "r3f";
3047
3567
  const isLibrary = clonedOptions.projectType === "library";
3048
- const libraryBundler = clonedOptions.libraryBundler ?? "unbuild";
3568
+ const libraryBundler = planInput.libraryBundler.tool;
3569
+ const ide = planInput.ide.tool;
3049
3570
  const files = {
3050
3571
  ...clonedOptions.files
3051
3572
  };
@@ -3079,7 +3600,7 @@ function generate(options) {
3079
3600
  }
3080
3601
  }
3081
3602
  if (language === "typescript") {
3082
- const tsResult = generateTypescriptConfig({
3603
+ const tsResult = renderTypescriptConfig({
3083
3604
  baseTemplate,
3084
3605
  useConfigPackage: clonedOptions.workspaceRoot != null,
3085
3606
  configStrategy: clonedOptions.configStrategy,
@@ -3091,12 +3612,9 @@ function generate(options) {
3091
3612
  }
3092
3613
  const codeSnippets = {};
3093
3614
  const vscodeSettings = {};
3094
- const scripts = isLibrary ? {} : {
3095
- dev: "vite",
3096
- build: "vite build"
3097
- };
3615
+ const scripts = {};
3098
3616
  if (!isLibrary && (isReact || isR3f)) {
3099
- codeSnippets["vite-config-import"] = ["import react from '@vitejs/plugin-react'"];
3617
+ codeSnippets["vite-config-import"] = ["import react from '@vitejs/plugin-react';"];
3100
3618
  }
3101
3619
  if (!isLibrary && isR3f) {
3102
3620
  codeSnippets["import"] = [`import { Canvas } from "@react-three/fiber"`];
@@ -3113,7 +3631,7 @@ function generate(options) {
3113
3631
  viteConfig.resolve = { dedupe: ["three"] };
3114
3632
  }
3115
3633
  const isMonorepoPackage = clonedOptions.workspaceRoot != null;
3116
- const generator = {
3634
+ const builder = {
3117
3635
  options: clonedOptions,
3118
3636
  versions,
3119
3637
  getVersion(name2) {
@@ -3143,8 +3661,11 @@ function generate(options) {
3143
3661
  addFile(path, content) {
3144
3662
  files[path] = content;
3145
3663
  },
3664
+ addScripts(nextScripts) {
3665
+ Object.assign(scripts, nextScripts);
3666
+ },
3146
3667
  addScript(name2, command) {
3147
- scripts[name2] = command;
3668
+ this.addScripts({ [name2]: command });
3148
3669
  },
3149
3670
  inject(location, code) {
3150
3671
  let entries = codeSnippets[location];
@@ -3164,71 +3685,61 @@ function generate(options) {
3164
3685
  }
3165
3686
  };
3166
3687
  if (isR3f) {
3167
- generateDrei(generator, clonedOptions.drei);
3168
- generateHandle(generator, clonedOptions.handle);
3169
- generateKoota(generator, clonedOptions.koota);
3170
- generateLeva(generator, clonedOptions.leva);
3171
- generateOffscreen(generator, clonedOptions.offscreen);
3172
- generatePostprocessing(generator, clonedOptions.postprocessing);
3173
- generateRapier(generator, clonedOptions.rapier);
3174
- generateUikit(generator, clonedOptions.uikit);
3175
- generateXr(generator, clonedOptions.xr);
3176
- generateZustand(generator, clonedOptions.zustand);
3177
- generateFiber(generator, clonedOptions.fiber);
3178
- generateTriplex(generator, clonedOptions.triplex);
3179
- generateViverse(generator, clonedOptions.viverse);
3688
+ planDrei(builder, planInput.features.drei);
3689
+ planHandle(builder, planInput.features.handle);
3690
+ planKoota(builder, planInput.features.koota);
3691
+ planLeva(builder, planInput.features.leva);
3692
+ planOffscreen(builder, planInput.features.offscreen);
3693
+ planPostprocessing(builder, planInput.features.postprocessing);
3694
+ planRapier(builder, planInput.features.rapier);
3695
+ planUikit(builder, planInput.features.uikit);
3696
+ planXr(builder, planInput.features.xr);
3697
+ planZustand(builder, planInput.features.zustand);
3698
+ planFiber(builder, planInput.features.fiber);
3699
+ planTriplex(builder, planInput.features.triplex);
3700
+ planViverse(builder, planInput.features.viverse);
3180
3701
  }
3181
3702
  if (!isLibrary) {
3182
- generateGithubPages(generator, clonedOptions.githubPages);
3703
+ planGithubPages(builder, planInput.features.githubPages);
3183
3704
  }
3184
3705
  if (isLibrary) {
3185
3706
  if (libraryBundler === "unbuild") {
3186
- generateUnbuild(generator);
3707
+ planUnbuild(builder);
3187
3708
  } else if (libraryBundler === "tsdown") {
3188
- generateTsdown(generator);
3709
+ planTsdown(builder);
3189
3710
  }
3190
- const packageManager2 = getPackageManagerName(clonedOptions.packageManager);
3191
- generator.addScript("release", `${packageManager2} run build && ${packageManager2} publish`);
3192
3711
  }
3193
- const testing = clonedOptions.testing ?? (isLibrary ? "vitest" : "none");
3712
+ const testing = planInput.testing.tool;
3194
3713
  if (testing === "vitest") {
3195
- generateVitest(generator);
3196
- }
3197
- const linter = clonedOptions.linter;
3198
- const formatter = clonedOptions.formatter;
3199
- if (linter === "eslint") {
3200
- generateEslint(generator);
3201
- generator.addVscodeSetting("biome.enabled", false);
3202
- generator.addVscodeSetting("oxc.enable", false);
3203
- } else if (linter === "oxlint") {
3204
- generateOxlint(generator);
3205
- generator.addVscodeSetting("eslint.enable", false);
3206
- generator.addVscodeSetting("biome.enabled", false);
3207
- } else if (linter === "biome") {
3208
- generateBiome(generator, {
3209
- linter: true,
3210
- formatter: formatter === "biome"
3714
+ planVitest(builder, planInput.testing);
3715
+ }
3716
+ const linter = planInput.linter.tool;
3717
+ const formatter = planInput.formatter.tool;
3718
+ if (planInput.linter.tool === "eslint") {
3719
+ planEslint(builder, planInput.linter);
3720
+ } else if (planInput.linter.tool === "oxlint") {
3721
+ planOxlint(builder, planInput.linter);
3722
+ } else if (planInput.linter.tool === "biome") {
3723
+ planBiome(builder, {
3724
+ linter: planInput.linter,
3725
+ formatter: planInput.formatter.tool === "biome" ? planInput.formatter : void 0
3211
3726
  });
3212
- generator.addVscodeSetting("eslint.enable", false);
3213
- generator.addVscodeSetting("oxc.enable", false);
3214
3727
  }
3215
- if (formatter === "prettier") {
3216
- generatePrettier(generator);
3217
- } else if (formatter === "oxfmt") {
3218
- generateOxfmt(generator);
3219
- } else if (formatter === "biome" && linter !== "biome") {
3220
- generateBiome(generator, { linter: false, formatter: true });
3221
- generator.addVscodeSetting("eslint.enable", false);
3222
- generator.addVscodeSetting("oxc.enable", false);
3728
+ if (planInput.formatter.tool === "prettier") {
3729
+ planPrettier(builder, planInput.formatter);
3730
+ } else if (planInput.formatter.tool === "oxfmt") {
3731
+ planOxfmt(builder, planInput.formatter);
3732
+ } else if (planInput.formatter.tool === "biome" && planInput.linter.tool !== "biome") {
3733
+ planBiome(builder, { formatter: planInput.formatter });
3223
3734
  }
3224
3735
  for (const { code, location } of clonedOptions.injections ?? []) {
3225
- generator.inject(location, code);
3736
+ builder.inject(location, code);
3226
3737
  }
3227
3738
  if (!isLibrary) {
3228
- files["vite.config.ts"] = generateViteConfig({ viteConfig, codeSnippets });
3739
+ files["vite.config.ts"] = renderViteConfig({ viteConfig, codeSnippets });
3229
3740
  }
3230
3741
  const packageManager = getPackageManagerName(options.packageManager);
3231
- files["README.md"] = generateReadme({
3742
+ files["README.md"] = renderReadme({
3232
3743
  name,
3233
3744
  baseTemplate,
3234
3745
  isLibrary,
@@ -3238,7 +3749,7 @@ function generate(options) {
3238
3749
  });
3239
3750
  Object.assign(
3240
3751
  files,
3241
- generateSourceFiles({
3752
+ renderSourceFiles({
3242
3753
  name,
3243
3754
  baseTemplate,
3244
3755
  language,
@@ -3250,7 +3761,7 @@ function generate(options) {
3250
3761
  if (testing === "vitest") {
3251
3762
  Object.assign(
3252
3763
  files,
3253
- generateTestFiles({
3764
+ renderTestFiles({
3254
3765
  baseTemplate,
3255
3766
  language,
3256
3767
  isLibrary
@@ -3259,7 +3770,7 @@ function generate(options) {
3259
3770
  }
3260
3771
  Object.assign(
3261
3772
  files,
3262
- generatePackageJson({
3773
+ renderPackageJson({
3263
3774
  name,
3264
3775
  language,
3265
3776
  isLibrary,
@@ -3271,25 +3782,54 @@ function generate(options) {
3271
3782
  workspaceDependencies: clonedOptions.workspaceDependencies
3272
3783
  }).files
3273
3784
  );
3274
- if (!isMonorepoPackage) {
3275
- Object.assign(files, generateVscodeFiles$1({ codeSnippets, vscodeSettings }));
3785
+ if (!isMonorepoPackage && ide === "vscode") {
3786
+ Object.assign(
3787
+ files,
3788
+ renderVscodeFiles$1({
3789
+ codeSnippets,
3790
+ vscodeSettings,
3791
+ linter,
3792
+ formatter,
3793
+ configStrategy: clonedOptions.configStrategy,
3794
+ isMonorepo: false,
3795
+ packageManager
3796
+ })
3797
+ );
3276
3798
  }
3277
3799
  if (!isMonorepoPackage) {
3278
- files[".gitignore"] = generateGitignore("standalone");
3279
- files[".gitattributes"] = { type: "text", content: GitAttributes };
3800
+ files[".editorconfig"] = renderEditorConfig();
3801
+ files[".gitignore"] = renderGitignore("standalone");
3802
+ files[".gitattributes"] = { type: "text", content: gitAttributesContent };
3280
3803
  }
3281
- if (!isMonorepoPackage && clonedOptions.aiPlatforms?.length) {
3282
- generateAiFiles(files, {
3804
+ if (!isMonorepoPackage && planInput.aiAgents.config.platforms.length > 0) {
3805
+ renderAiFiles(files, {
3283
3806
  name,
3284
3807
  packageManager: getPackageManagerName(clonedOptions.packageManager),
3285
3808
  linter: clonedOptions.linter ?? "oxlint",
3286
3809
  formatter: clonedOptions.formatter ?? "prettier",
3287
3810
  isMonorepo: false,
3288
3811
  configStrategy: clonedOptions.configStrategy,
3289
- platforms: clonedOptions.aiPlatforms
3812
+ hasTypecheck: language === "typescript",
3813
+ platforms: planInput.aiAgents.config.platforms
3290
3814
  });
3291
3815
  }
3292
- return files;
3816
+ return {
3817
+ files,
3818
+ dependencies,
3819
+ devDependencies,
3820
+ peerDependencies,
3821
+ scripts,
3822
+ vscodeSettings,
3823
+ vscodeExtensions: [...new Set(codeSnippets["vscode-extension-suggestion"] ?? [])],
3824
+ injections: Object.entries(codeSnippets).flatMap(
3825
+ ([location, entries]) => (entries ?? []).map((code) => ({
3826
+ location,
3827
+ code
3828
+ }))
3829
+ ),
3830
+ replacements,
3831
+ warnings: []
3832
+ };
3293
3833
  }
3294
3834
  function resolveDependencySemver(name, versions, options = {}) {
3295
3835
  if (options.version != null) {
@@ -3298,22 +3838,30 @@ function resolveDependencySemver(name, versions, options = {}) {
3298
3838
  return formatResolvedPackageVersion(versions, name, options.prefix);
3299
3839
  }
3300
3840
 
3841
+ async function planWorkspace(input) {
3842
+ const planInput = isWorkspacePlanInput(input) ? input : resolveWorkspacePlanInput(input);
3843
+ const resolvedInput = await resolveWorkspaceFacts(planInput);
3844
+ const { files } = renderMonorepo(workspacePlanInputToMonorepoParams(resolvedInput));
3845
+ return {
3846
+ files,
3847
+ dependencies: {},
3848
+ devDependencies: {},
3849
+ peerDependencies: {},
3850
+ scripts: {},
3851
+ vscodeSettings: {},
3852
+ vscodeExtensions: [],
3853
+ injections: [],
3854
+ replacements: [],
3855
+ warnings: []
3856
+ };
3857
+ }
3858
+
3301
3859
  exports.AI_PLATFORM_HINTS = AI_PLATFORM_HINTS;
3302
3860
  exports.AI_PLATFORM_LABELS = AI_PLATFORM_LABELS;
3303
3861
  exports.ALL_AI_PLATFORMS = ALL_AI_PLATFORMS;
3304
3862
  exports.detectTooling = detectTooling;
3305
3863
  exports.formatResolvedPackageVersion = formatResolvedPackageVersion;
3306
- exports.generate = generate;
3307
- exports.generateAiFiles = generateAiFiles;
3308
- exports.generateEslintConfigPackage = generateEslintConfigPackage;
3309
- exports.generateGitignore = generateGitignore;
3310
- exports.generateMonorepo = generateMonorepo;
3311
- exports.generateOxfmtConfigPackage = generateOxfmtConfigPackage;
3312
- exports.generateOxlintConfigPackage = generateOxlintConfigPackage;
3313
- exports.generatePrettierConfigPackage = generatePrettierConfigPackage;
3314
3864
  exports.generateRandomName = generateRandomName;
3315
- exports.generateTypescriptConfigPackage = generateTypescriptConfigPackage;
3316
- exports.generateVscodeFiles = generateVscodeFiles;
3317
3865
  exports.getBaseTemplate = getBaseTemplate;
3318
3866
  exports.getEngineName = getEngineName;
3319
3867
  exports.getLanguageFromTemplate = getLanguageFromTemplate;
@@ -3325,12 +3873,32 @@ exports.getLatestPnpmVersion = getLatestPnpmVersion;
3325
3873
  exports.getLatestYarnVersion = getLatestYarnVersion;
3326
3874
  exports.getPackageManagerName = getPackageManagerName;
3327
3875
  exports.getResolvedPackageVersion = getResolvedPackageVersion;
3328
- exports.monorepo = monorepo;
3876
+ exports.merge = merge;
3877
+ exports.mergePackageJsonScripts = mergePackageJsonScripts;
3878
+ exports.packageJsonScripts = packageJsonScripts;
3329
3879
  exports.parseEngine = parseEngine;
3330
3880
  exports.parsePackageManager = parsePackageManager;
3331
3881
  exports.parseWorkspaceYamlContent = parseWorkspaceYamlContent;
3332
- exports.resolveEngine = resolveEngine;
3882
+ exports.planProject = planProject;
3883
+ exports.planWorkspace = planWorkspace;
3884
+ exports.projectPlanInputToOptions = projectPlanInputToOptions;
3885
+ exports.renderAiFiles = renderAiFiles;
3886
+ exports.renderEditorConfig = renderEditorConfig;
3887
+ exports.renderEslintConfigPackage = renderEslintConfigPackage;
3888
+ exports.renderGitignore = renderGitignore;
3889
+ exports.renderOxfmtConfigPackage = renderOxfmtConfigPackage;
3890
+ exports.renderOxlintConfig = renderOxlintConfig;
3891
+ exports.renderOxlintConfigPackage = renderOxlintConfigPackage;
3892
+ exports.renderPrettierConfigPackage = renderPrettierConfigPackage;
3893
+ exports.renderTypescriptConfigPackage = renderTypescriptConfigPackage;
3894
+ exports.renderViteConfig = renderViteConfig;
3895
+ exports.renderVscodeFiles = renderVscodeFiles;
3896
+ exports.renderVscodeFiles$1 = renderVscodeFiles$1;
3897
+ exports.resolveDefaultPackageJsonScripts = resolveDefaultPackageJsonScripts;
3333
3898
  exports.resolveMonorepoRootPackageVersions = resolveMonorepoRootPackageVersions;
3334
- exports.resolvePackageManager = resolvePackageManager;
3335
- exports.resolveProjectPackageVersions = resolveProjectPackageVersions;
3899
+ exports.resolveProjectPlanInput = resolveProjectPlanInput;
3900
+ exports.resolveWorkspacePlanInput = resolveWorkspacePlanInput;
3901
+ exports.toPrettierIgnoreContent = toPrettierIgnoreContent;
3902
+ exports.unique = unique;
3336
3903
  exports.validatePackageName = validatePackageName;
3904
+ exports.workspacePlanInputToMonorepoParams = workspacePlanInputToMonorepoParams;