create-krispya 0.9.0 → 0.10.0

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