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