create-krispya 0.7.0 → 0.9.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.
@@ -0,0 +1,3336 @@
1
+ 'use strict';
2
+
3
+ const promises = require('fs/promises');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const color = require('chalk');
7
+
8
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
9
+
10
+ const color__default = /*#__PURE__*/_interopDefaultCompat(color);
11
+
12
+ const HtmlContent = `<!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="UTF-8">
16
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
17
+ <title>$title</title>
18
+ </head>
19
+ <body style="margin: 0; overscroll-behavior: none; user-select: none; touch-action: none;">
20
+ <script type="module" src="$indexPath"><\/script>
21
+ <div style="width: 100dvw; height: 100dvh; overflow: hidden;" id="root"></div>
22
+ </body>
23
+ </html>`;
24
+ const ViteHtmlContent = `<!DOCTYPE html>
25
+ <html lang="en">
26
+ <head>
27
+ <meta charset="UTF-8">
28
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
29
+ <title>$title</title>
30
+ </head>
31
+ <body>
32
+ <div id="app"></div>
33
+ <script type="module" src="$indexPath"><\/script>
34
+ </body>
35
+ </html>`;
36
+ const IndexContent = `import { StrictMode } from 'react'
37
+ import { createRoot } from 'react-dom/client'
38
+ import { App } from './app.js'
39
+
40
+ createRoot(document.getElementById('root')!).render(
41
+ <StrictMode>
42
+ <App />
43
+ </StrictMode>,
44
+ )`;
45
+ const ViteIndexContent = `import './style.css'
46
+
47
+ document.querySelector('#app')!.innerHTML = \`
48
+ <h1>Hello Vite!</h1>
49
+ <p>Edit src/main.ts and save to see HMR in action.</p>
50
+ \``;
51
+ const ViteStyleContent = `body {
52
+ font-family: system-ui, -apple-system, sans-serif;
53
+ margin: 0;
54
+ padding: 2rem;
55
+ min-height: 100vh;
56
+ background: #1a1a1a;
57
+ color: #fff;
58
+ }
59
+
60
+ h1 {
61
+ color: #646cff;
62
+ }
63
+
64
+ a {
65
+ color: #646cff;
66
+ }`;
67
+ const GitAttributes = [
68
+ "* text eol=lf",
69
+ "*.png binary",
70
+ "*.jpg binary",
71
+ "*.jpeg binary",
72
+ "*.gif binary",
73
+ "*.ico binary",
74
+ "*.mov binary",
75
+ "*.mp4 binary",
76
+ "*.mp3 binary",
77
+ "*.flv binary",
78
+ "*.fla binary",
79
+ "*.wav binary",
80
+ "*.swf binary",
81
+ "*.gz binary",
82
+ "*.zip binary",
83
+ "*.7z binary",
84
+ "*.ttf binary",
85
+ "*.eot binary",
86
+ "*.woff binary",
87
+ "*.pyc binary",
88
+ "*.pdf binary",
89
+ "*.glb binary",
90
+ "*.gltf binary"
91
+ ].join("\n");
92
+ const defaultFormatterConfig = {
93
+ printWidth: 102,
94
+ tabWidth: 2,
95
+ useTabs: false,
96
+ semi: true,
97
+ singleQuote: true,
98
+ trailingComma: "es5",
99
+ bracketSpacing: true,
100
+ arrowParens: "always"
101
+ };
102
+ const defaultPrettierConfig = {
103
+ $schema: "https://json.schemastore.org/prettierrc",
104
+ ...defaultFormatterConfig,
105
+ overrides: [
106
+ {
107
+ files: ["*.md", "**/*.md"],
108
+ options: { semi: false }
109
+ },
110
+ {
111
+ files: ["*.yml", "*.yaml", "**/*.yml", "**/*.yaml"],
112
+ options: { semi: false }
113
+ }
114
+ ]
115
+ };
116
+ const defaultOxfmtConfig = {
117
+ printWidth: defaultFormatterConfig.printWidth,
118
+ tabWidth: defaultFormatterConfig.tabWidth,
119
+ useTabs: defaultFormatterConfig.useTabs,
120
+ semi: defaultFormatterConfig.semi,
121
+ singleQuote: defaultFormatterConfig.singleQuote,
122
+ trailingComma: defaultFormatterConfig.trailingComma,
123
+ bracketSpacing: defaultFormatterConfig.bracketSpacing,
124
+ arrowParens: defaultFormatterConfig.arrowParens
125
+ };
126
+ const defaultLinterConfig = {
127
+ ignorePatterns: ["dist"],
128
+ rules: {
129
+ noUnusedVars: {
130
+ level: "warn",
131
+ argsIgnorePattern: "^_",
132
+ varsIgnorePattern: "^_",
133
+ caughtErrorsIgnorePattern: "^_"
134
+ },
135
+ noUnusedExpressions: {
136
+ level: "warn",
137
+ allowShortCircuit: true
138
+ }
139
+ }
140
+ };
141
+
142
+ const ALL_AI_PLATFORMS = ["agents", "claude"];
143
+ const AI_PLATFORM_LABELS = {
144
+ agents: "AGENTS.md",
145
+ claude: "CLAUDE.md"
146
+ };
147
+ const AI_PLATFORM_HINTS = {
148
+ agents: "OpenAI, Cursor, Windsurf, etc.",
149
+ claude: "Claude Code"
150
+ };
151
+ function generateAiFiles(files, params) {
152
+ const { platforms, isMonorepo, configStrategy, ...rest } = params;
153
+ if (platforms.length === 0) return;
154
+ const content = generateWorkspace({
155
+ ...rest,
156
+ isMonorepo: !!isMonorepo});
157
+ const pointer = "See [`AGENTS.md`](./Agents.md) for agent context.\n";
158
+ const hasAgents = platforms.includes("agents");
159
+ const hasClaude = platforms.includes("claude");
160
+ const isSingleton = platforms.length === 1;
161
+ if (hasAgents) files["AGENTS.md"] = { type: "text", content };
162
+ if (hasClaude) {
163
+ if (isSingleton) {
164
+ files["CLAUDE.md"] = { type: "text", content };
165
+ } else {
166
+ files["CLAUDE.md"] = { type: "text", content: pointer };
167
+ }
168
+ }
169
+ }
170
+ function generateWorkspace(ctx) {
171
+ const { name, packageManager, linter, formatter, isMonorepo} = ctx;
172
+ const sections = [
173
+ `# ${name}`,
174
+ "",
175
+ `- **Type:** ${isMonorepo ? "pnpm monorepo" : "standalone project"}`,
176
+ `- **Package Manager:** ${packageManager}`,
177
+ `- **Linter:** ${linter}`,
178
+ `- **Formatter:** ${formatter}`,
179
+ "",
180
+ "## Commands",
181
+ "",
182
+ `- \`${packageManager} test\` \u2014 run tests`,
183
+ `- \`${packageManager} build\` \u2014 build`,
184
+ `- \`${packageManager} lint\` and \`${packageManager} format\` \u2014 run before committing`
185
+ ];
186
+ if (isMonorepo) {
187
+ sections.push(
188
+ "",
189
+ "- Use `workspace:*` for internal dependencies",
190
+ `- New packages: \`${packageManager} create krispya <name> --workspace\``
191
+ );
192
+ }
193
+ sections.push("");
194
+ return sections.join("\n");
195
+ }
196
+
197
+ function getLanguageFromTemplate(template) {
198
+ return template.endsWith("-js") ? "javascript" : "typescript";
199
+ }
200
+ function getBaseTemplate(template) {
201
+ return template.replace("-js", "");
202
+ }
203
+
204
+ async function getLatestNpmVersion(packageName, fallback) {
205
+ try {
206
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
207
+ const data = await response.json();
208
+ return data.version;
209
+ } catch {
210
+ return fallback;
211
+ }
212
+ }
213
+ function compareNumericSemver(a, b) {
214
+ const aParts = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
215
+ const bParts = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
216
+ const maxLength = Math.max(aParts.length, bParts.length);
217
+ for (let index = 0; index < maxLength; index += 1) {
218
+ const difference = (aParts[index] ?? 0) - (bParts[index] ?? 0);
219
+ if (difference !== 0) {
220
+ return difference;
221
+ }
222
+ }
223
+ return 0;
224
+ }
225
+ async function getLatestNpmMajorVersion(packageName, majorVersion, fallback) {
226
+ try {
227
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`);
228
+ const data = await response.json();
229
+ const latestMatchingVersion = Object.keys(data.versions ?? {}).filter((version) => version.split(".")[0] === majorVersion).sort((a, b) => compareNumericSemver(b, a))[0];
230
+ return latestMatchingVersion ?? fallback;
231
+ } catch {
232
+ return fallback;
233
+ }
234
+ }
235
+ async function getLatestPnpmVersion() {
236
+ return getLatestNpmVersion("pnpm", "10.11.0");
237
+ }
238
+ async function getLatestYarnVersion() {
239
+ return getLatestNpmVersion("yarn", "4.6.0");
240
+ }
241
+ async function getLatestNpmCliVersion() {
242
+ return getLatestNpmVersion("npm", "11.0.0");
243
+ }
244
+ async function getLatestNodeVersion() {
245
+ try {
246
+ const response = await fetch("https://nodejs.org/dist/index.json");
247
+ const data = await response.json();
248
+ const latestVersion = data[0];
249
+ if (latestVersion) {
250
+ return latestVersion.version.replace(/^v/, "");
251
+ }
252
+ return "25.0.0";
253
+ } catch {
254
+ return "25.0.0";
255
+ }
256
+ }
257
+ function validateNameSegment(segment, label) {
258
+ if (!segment.length) {
259
+ return `${label} is required`;
260
+ }
261
+ if (!/^[a-z0-9-]+$/.test(segment)) {
262
+ return `${label} must be lowercase and contain only letters, numbers, and hyphens`;
263
+ }
264
+ if (segment.startsWith("-") || segment.endsWith("-")) {
265
+ return `${label} cannot start or end with a hyphen`;
266
+ }
267
+ if (segment.includes("--")) {
268
+ return `${label} cannot contain consecutive hyphens`;
269
+ }
270
+ return void 0;
271
+ }
272
+ function validatePackageName(name) {
273
+ if (!name.length) {
274
+ return "Package name is required";
275
+ }
276
+ if (name.includes("..") || name.includes("\\")) {
277
+ return "Package name cannot contain path traversal sequences";
278
+ }
279
+ if (name.startsWith("@")) {
280
+ const slashIndex = name.indexOf("/");
281
+ if (slashIndex === -1) {
282
+ return "Scoped package name must include a package name after the scope (e.g., @scope/name)";
283
+ }
284
+ if (name.indexOf("/", slashIndex + 1) !== -1) {
285
+ return "Package name can only have one slash for scoped packages";
286
+ }
287
+ const scope = name.slice(1, slashIndex);
288
+ const packageName = name.slice(slashIndex + 1);
289
+ const scopeError = validateNameSegment(scope, "Scope");
290
+ if (scopeError) return scopeError;
291
+ const nameError = validateNameSegment(packageName, "Package name");
292
+ if (nameError) return nameError;
293
+ return void 0;
294
+ }
295
+ if (name.includes("/")) {
296
+ return "Unscoped package name cannot contain slashes. Use @scope/name format for scoped packages";
297
+ }
298
+ return validateNameSegment(name, "Package name");
299
+ }
300
+ function parseWorkspaceYamlContent(content) {
301
+ const directories = [];
302
+ let inPackagesSection = false;
303
+ for (const line of content.split("\n")) {
304
+ const trimmed = line.trim();
305
+ if (trimmed === "packages:") {
306
+ inPackagesSection = true;
307
+ continue;
308
+ }
309
+ if (inPackagesSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ") && !trimmed.startsWith("-")) {
310
+ break;
311
+ }
312
+ if (inPackagesSection && trimmed.startsWith("-")) {
313
+ const entry = trimmed.slice(1).trim().replace(/^["']|["']$/g, "").replace(/^\.\//, "").replace(/\/\*.*$/, "");
314
+ if (entry && !entry.startsWith(".")) {
315
+ directories.push(entry);
316
+ }
317
+ }
318
+ }
319
+ return directories;
320
+ }
321
+ async function pathExists(path) {
322
+ try {
323
+ await promises.access(path, fs.constants.F_OK);
324
+ return true;
325
+ } catch {
326
+ return false;
327
+ }
328
+ }
329
+ function detectLinterFromScript(script) {
330
+ if (!script) return void 0;
331
+ if (script.includes("oxlint")) return "oxlint";
332
+ if (script.includes("eslint")) return "eslint";
333
+ if (script.includes("biome check") || script.includes("biome lint")) return "biome";
334
+ return void 0;
335
+ }
336
+ function detectFormatterFromScript(script) {
337
+ if (!script) return void 0;
338
+ if (script.includes("prettier")) return "prettier";
339
+ if (script.includes("oxfmt")) return "oxfmt";
340
+ if (script.includes("biome format")) return "biome";
341
+ return void 0;
342
+ }
343
+ async function detectLinterFromConfig(root) {
344
+ if (await pathExists(path.join(root, ".config/oxlint"))) return "oxlint";
345
+ if (await pathExists(path.join(root, ".config/eslint"))) return "eslint";
346
+ if (await pathExists(path.join(root, "biome.json"))) {
347
+ try {
348
+ const content = await promises.readFile(path.join(root, "biome.json"), "utf-8");
349
+ const config = JSON.parse(content);
350
+ if (config.linter?.enabled !== false) return "biome";
351
+ } catch {
352
+ return "biome";
353
+ }
354
+ }
355
+ return void 0;
356
+ }
357
+ async function detectFormatterFromConfig(root) {
358
+ if (await pathExists(path.join(root, ".config/prettier"))) return "prettier";
359
+ if (await pathExists(path.join(root, ".config/oxfmt"))) return "oxfmt";
360
+ if (await pathExists(path.join(root, "biome.json"))) {
361
+ try {
362
+ const content = await promises.readFile(path.join(root, "biome.json"), "utf-8");
363
+ const config = JSON.parse(content);
364
+ if (config.formatter?.enabled !== false) return "biome";
365
+ } catch {
366
+ return "biome";
367
+ }
368
+ }
369
+ return void 0;
370
+ }
371
+ function detectLinterFromDeps(devDeps) {
372
+ if (!devDeps) return void 0;
373
+ if (devDeps["@biomejs/biome"]) return "biome";
374
+ if (devDeps.eslint) return "eslint";
375
+ if (devDeps.oxlint) return "oxlint";
376
+ return void 0;
377
+ }
378
+ function detectFormatterFromDeps(devDeps) {
379
+ if (!devDeps) return void 0;
380
+ if (devDeps["@biomejs/biome"]) return "biome";
381
+ if (devDeps.prettier) return "prettier";
382
+ if (devDeps.oxfmt) return "oxfmt";
383
+ return void 0;
384
+ }
385
+ async function detectTooling(root) {
386
+ try {
387
+ const pkgPath = path.join(root, "package.json");
388
+ const content = await promises.readFile(pkgPath, "utf-8");
389
+ const pkg = JSON.parse(content);
390
+ const linter = detectLinterFromScript(pkg.scripts?.lint) ?? await detectLinterFromConfig(root) ?? detectLinterFromDeps(pkg.devDependencies);
391
+ const formatter = detectFormatterFromScript(pkg.scripts?.format) ?? await detectFormatterFromConfig(root) ?? detectFormatterFromDeps(pkg.devDependencies);
392
+ return { linter, formatter };
393
+ } catch {
394
+ return { linter: void 0, formatter: void 0 };
395
+ }
396
+ }
397
+ function generateRandomName() {
398
+ const adjectives = [
399
+ "red",
400
+ "blue",
401
+ "green",
402
+ "yellow",
403
+ "purple",
404
+ "orange",
405
+ "pink",
406
+ "black",
407
+ "white",
408
+ "tiny",
409
+ "big",
410
+ "small",
411
+ "large",
412
+ "huge",
413
+ "giant",
414
+ "mini",
415
+ "mega",
416
+ "super",
417
+ "happy",
418
+ "sad",
419
+ "angry",
420
+ "calm",
421
+ "quiet",
422
+ "loud",
423
+ "silent",
424
+ "noisy",
425
+ "shiny",
426
+ "dull",
427
+ "bright",
428
+ "dark",
429
+ "fuzzy",
430
+ "smooth",
431
+ "rough",
432
+ "soft"
433
+ ];
434
+ const nouns = [
435
+ "apple",
436
+ "banana",
437
+ "cherry",
438
+ "date",
439
+ "elderberry",
440
+ "fig",
441
+ "grape",
442
+ "honeydew",
443
+ "cat",
444
+ "dog",
445
+ "elephant",
446
+ "fox",
447
+ "giraffe",
448
+ "horse",
449
+ "iguana",
450
+ "jaguar",
451
+ "mountain",
452
+ "river",
453
+ "ocean",
454
+ "desert",
455
+ "forest",
456
+ "jungle",
457
+ "meadow",
458
+ "valley",
459
+ "star",
460
+ "moon",
461
+ "sun",
462
+ "planet",
463
+ "comet",
464
+ "asteroid",
465
+ "galaxy",
466
+ "universe"
467
+ ];
468
+ const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
469
+ const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
470
+ return `${randomAdjective}-${randomNoun}`;
471
+ }
472
+
473
+ const PACKAGE_VERSION_DEFINITIONS = {
474
+ "@biomejs/biome": { fallbackVersion: "2.0.0" },
475
+ "@react-three/drei": { fallbackVersion: "10.0.0" },
476
+ "@react-three/fiber": { fallbackVersion: "9.0.0" },
477
+ "@react-three/handle": { fallbackVersion: "6.6.16" },
478
+ "@react-three/offscreen": { fallbackVersion: "0.0.8" },
479
+ "@react-three/postprocessing": { fallbackVersion: "3.0.4" },
480
+ "@react-three/rapier": { fallbackVersion: "2.1.0" },
481
+ "@react-three/uikit": { fallbackVersion: "0.8.15" },
482
+ "@react-three/xr": { fallbackVersion: "6.6.16" },
483
+ "@testing-library/dom": { fallbackVersion: "10.4.0" },
484
+ "@testing-library/react": { fallbackVersion: "16.2.0" },
485
+ "@types/react": { fallbackVersion: "19.0.0" },
486
+ "@types/react-dom": { fallbackVersion: "19.0.0" },
487
+ "@types/three": { fallbackVersion: "0.175.0", prefix: "~" },
488
+ "@vitejs/plugin-basic-ssl": { fallbackVersion: "2.0.0" },
489
+ "@vitejs/plugin-react": { fallbackVersion: "4.4.1" },
490
+ "@viverse/cli": { fallbackVersion: "0.9.5-beta.8" },
491
+ eslint: { fallbackVersion: "9.17.0" },
492
+ "eslint-plugin-react-hooks": { fallbackVersion: "5.1.0" },
493
+ jsdom: { fallbackVersion: "26.0.0" },
494
+ koota: { fallbackVersion: "0.4.0" },
495
+ leva: { fallbackVersion: "0.10.0" },
496
+ oxfmt: { fallbackVersion: "0.21.0" },
497
+ oxlint: { fallbackVersion: "1.36.0" },
498
+ prettier: { fallbackVersion: "3.4.2" },
499
+ react: { fallbackVersion: "19.0.0" },
500
+ "react-dom": { fallbackVersion: "19.0.0" },
501
+ three: { fallbackVersion: "0.175.0", prefix: "~" },
502
+ tsdown: { fallbackVersion: "0.12.0" },
503
+ typescript: { fallbackVersion: "5.9.3" },
504
+ "typescript-eslint": { fallbackVersion: "8.18.0" },
505
+ unbuild: { fallbackVersion: "3.5.0" },
506
+ vite: { fallbackVersion: "6.3.4" },
507
+ vitest: { fallbackVersion: "4.0.0" },
508
+ zustand: { fallbackVersion: "5.0.3" }
509
+ };
510
+ function addPackageName(packageNames, explicitVersions, packageName) {
511
+ if (!explicitVersions.has(packageName)) {
512
+ packageNames.add(packageName);
513
+ }
514
+ }
515
+ function getExplicitVersionPackages(options) {
516
+ return /* @__PURE__ */ new Set([
517
+ ...Object.keys(options.dependencies ?? {}),
518
+ ...Object.keys(options.versions ?? {})
519
+ ]);
520
+ }
521
+ function getPackageFallbackVersion(packageName) {
522
+ const definition = PACKAGE_VERSION_DEFINITIONS[packageName];
523
+ if (definition == null) {
524
+ throw new Error(`Missing package version definition for ${packageName}`);
525
+ }
526
+ return definition.fallbackVersion;
527
+ }
528
+ function getResolvedPackageVersion(versions, packageName) {
529
+ return versions[packageName] ?? getPackageFallbackVersion(packageName);
530
+ }
531
+ function formatResolvedPackageVersion(versions, packageName, prefix) {
532
+ const resolvedPrefix = prefix ?? PACKAGE_VERSION_DEFINITIONS[packageName]?.prefix ?? "^";
533
+ return `${resolvedPrefix}${getResolvedPackageVersion(versions, packageName)}`;
534
+ }
535
+ function assignResolvedPackageVersion(target, versions, packageName, prefix) {
536
+ target[packageName] = formatResolvedPackageVersion(versions, packageName, prefix);
537
+ }
538
+ function getPackageManagerSpec(packageManager) {
539
+ return packageManager ?? { name: "pnpm" };
540
+ }
541
+ function getPackageManagerName(packageManager) {
542
+ return getPackageManagerSpec(packageManager).name;
543
+ }
544
+ function formatPackageManager(packageManager) {
545
+ const spec = getPackageManagerSpec(packageManager);
546
+ return spec.version ? `${spec.name}@${spec.version}` : spec.name;
547
+ }
548
+ function parsePackageManager(packageManager) {
549
+ if (packageManager == null || packageManager.length === 0) {
550
+ return void 0;
551
+ }
552
+ const atIndex = packageManager.indexOf("@");
553
+ if (atIndex === -1) {
554
+ return { name: packageManager };
555
+ }
556
+ return {
557
+ name: packageManager.slice(0, atIndex),
558
+ version: packageManager.slice(atIndex + 1)
559
+ };
560
+ }
561
+ function getEngineSpec(engine) {
562
+ return engine ?? { name: "node" };
563
+ }
564
+ function getEngineName(engine) {
565
+ return getEngineSpec(engine).name;
566
+ }
567
+ function parseEngine(engines) {
568
+ if (engines == null) {
569
+ return void 0;
570
+ }
571
+ const [name, range] = Object.entries(engines).find(
572
+ ([engineName]) => engineName !== "npm" && engineName !== "pnpm" && engineName !== "yarn"
573
+ ) ?? [];
574
+ if (name == null) {
575
+ return void 0;
576
+ }
577
+ const version = range?.match(/(\d+(?:\.\d+(?:\.\d+)?)?)/)?.[1];
578
+ return { name, version };
579
+ }
580
+ async function resolvePackageManager(options) {
581
+ const packageManager = getPackageManagerSpec(options.packageManager);
582
+ if (packageManager.version == null) {
583
+ if (packageManager.name === "pnpm") {
584
+ packageManager.version = await getLatestPnpmVersion();
585
+ } else if (packageManager.name === "yarn") {
586
+ packageManager.version = await getLatestYarnVersion();
587
+ } else if (packageManager.name === "npm") {
588
+ packageManager.version = await getLatestNpmCliVersion();
589
+ }
590
+ }
591
+ return packageManager;
592
+ }
593
+ async function resolveEngine(options) {
594
+ const engine = getEngineSpec(options.engine);
595
+ if ((engine.version == null || engine.version === "latest") && engine.name === "node") {
596
+ engine.version = await getLatestNodeVersion();
597
+ }
598
+ return engine;
599
+ }
600
+ function formatNodeTypesVersion(versions = {}, engine) {
601
+ const resolvedVersion = versions["@types/node"];
602
+ if (resolvedVersion != null) {
603
+ return `^${resolvedVersion}`;
604
+ }
605
+ const engineSpec = getEngineSpec(engine);
606
+ if (engineSpec.name === "node" && engineSpec.version) {
607
+ const majorVersion = engineSpec.version.split(".")[0];
608
+ return `^${majorVersion}.0.0`;
609
+ }
610
+ return "^22.0.0";
611
+ }
612
+ async function resolveNodeTypesVersion(engine, versions = {}) {
613
+ if (versions["@types/node"] != null) {
614
+ return versions["@types/node"];
615
+ }
616
+ const engineSpec = getEngineSpec(engine);
617
+ if (engineSpec.name !== "node") {
618
+ return void 0;
619
+ }
620
+ const nodeVersion = engineSpec.version ?? await getLatestNodeVersion();
621
+ const majorVersion = nodeVersion.split(".")[0];
622
+ return getLatestNpmMajorVersion("@types/node", majorVersion, `${majorVersion}.0.0`);
623
+ }
624
+ async function resolvePackageVersions(packageNames, existingVersions = {}) {
625
+ const versions = { ...existingVersions };
626
+ const uniquePackageNames = [...new Set(packageNames)];
627
+ await Promise.all(
628
+ uniquePackageNames.map(async (packageName) => {
629
+ if (versions[packageName] != null) return;
630
+ versions[packageName] = await getLatestNpmVersion(
631
+ packageName,
632
+ getPackageFallbackVersion(packageName)
633
+ );
634
+ })
635
+ );
636
+ return versions;
637
+ }
638
+ async function resolveProjectPackageVersions(options) {
639
+ const packageNames = collectProjectPackageNames(options);
640
+ const versions = await resolvePackageVersions(
641
+ packageNames.filter((packageName) => packageName !== "@types/node"),
642
+ options.versions
643
+ );
644
+ const nodeTypesVersion = await resolveNodeTypesVersion(options.engine, versions);
645
+ if (nodeTypesVersion != null) {
646
+ versions["@types/node"] = nodeTypesVersion;
647
+ }
648
+ return versions;
649
+ }
650
+ async function resolveMonorepoRootPackageVersions(params) {
651
+ const packageNames = /* @__PURE__ */ new Set();
652
+ const explicitVersions = new Set(Object.keys(params.versions ?? {}));
653
+ addPackageName(packageNames, explicitVersions, getLinterPackage(params.linter));
654
+ if (params.formatter !== "biome" || params.linter !== "biome") {
655
+ addPackageName(packageNames, explicitVersions, getFormatterPackage(params.formatter));
656
+ }
657
+ const versions = await resolvePackageVersions(packageNames, params.versions);
658
+ const nodeTypesVersion = await resolveNodeTypesVersion(params.engine, versions);
659
+ if (nodeTypesVersion != null) {
660
+ versions["@types/node"] = nodeTypesVersion;
661
+ }
662
+ return versions;
663
+ }
664
+ function collectProjectPackageNames(options) {
665
+ const packageNames = /* @__PURE__ */ new Set();
666
+ const explicitVersions = getExplicitVersionPackages(options);
667
+ const template = options.template ?? "vanilla";
668
+ const baseTemplate = getBaseTemplate(template);
669
+ const language = getLanguageFromTemplate(template);
670
+ const isLibrary = options.projectType === "library";
671
+ const isReact = baseTemplate === "react" || baseTemplate === "r3f";
672
+ const isR3f = baseTemplate === "r3f";
673
+ const isTypescript = language === "typescript";
674
+ const inWorkspace = options.workspaceRoot != null;
675
+ const testing = options.testing ?? (isLibrary ? "vitest" : "none");
676
+ const linter = options.linter ?? "oxlint";
677
+ const formatter = options.formatter ?? "prettier";
678
+ const bundler = options.libraryBundler ?? "unbuild";
679
+ const packageManager = getPackageManagerName(options.packageManager);
680
+ const engine = getEngineSpec(options.engine);
681
+ if (getEngineName(engine) === "node") {
682
+ addPackageName(packageNames, explicitVersions, "@types/node");
683
+ }
684
+ if (isTypescript) {
685
+ addPackageName(packageNames, explicitVersions, "typescript");
686
+ }
687
+ if (!isLibrary) {
688
+ addPackageName(packageNames, explicitVersions, "vite");
689
+ }
690
+ if (isReact) {
691
+ if (!isLibrary) {
692
+ addPackageName(packageNames, explicitVersions, "react");
693
+ addPackageName(packageNames, explicitVersions, "react-dom");
694
+ addPackageName(packageNames, explicitVersions, "@vitejs/plugin-react");
695
+ }
696
+ if (isTypescript) {
697
+ addPackageName(packageNames, explicitVersions, "@types/react");
698
+ addPackageName(packageNames, explicitVersions, "@types/react-dom");
699
+ }
700
+ }
701
+ if (isR3f) {
702
+ if (!isLibrary) {
703
+ addPackageName(packageNames, explicitVersions, "three");
704
+ addPackageName(packageNames, explicitVersions, "@react-three/fiber");
705
+ }
706
+ if (isTypescript) {
707
+ addPackageName(packageNames, explicitVersions, "@types/three");
708
+ }
709
+ if (isEnabledOption(options.drei)) {
710
+ addPackageName(packageNames, explicitVersions, "@react-three/drei");
711
+ }
712
+ if (isEnabledOption(options.handle)) {
713
+ addPackageName(packageNames, explicitVersions, "@react-three/handle");
714
+ }
715
+ if (isEnabledOption(options.koota)) {
716
+ addPackageName(packageNames, explicitVersions, "koota");
717
+ }
718
+ if (isEnabledOption(options.leva)) {
719
+ addPackageName(packageNames, explicitVersions, "leva");
720
+ }
721
+ if (isEnabledOption(options.rapier)) {
722
+ addPackageName(packageNames, explicitVersions, "@react-three/rapier");
723
+ }
724
+ if (isEnabledOption(options.uikit)) {
725
+ addPackageName(packageNames, explicitVersions, "@react-three/uikit");
726
+ }
727
+ if (isEnabledOption(options.zustand)) {
728
+ addPackageName(packageNames, explicitVersions, "zustand");
729
+ }
730
+ if (isEnabledOption(options.xr)) {
731
+ addPackageName(packageNames, explicitVersions, "@react-three/xr");
732
+ addPackageName(packageNames, explicitVersions, "@vitejs/plugin-basic-ssl");
733
+ }
734
+ if (!isEnabledOption(options.xr) && isEnabledOption(options.offscreen)) {
735
+ addPackageName(packageNames, explicitVersions, "@react-three/offscreen");
736
+ }
737
+ if (!isEnabledOption(options.xr) && isEnabledOption(options.postprocessing)) {
738
+ addPackageName(packageNames, explicitVersions, "@react-three/postprocessing");
739
+ }
740
+ if (isEnabledOption(options.viverse) && packageManager === "npm") {
741
+ addPackageName(packageNames, explicitVersions, "@viverse/cli");
742
+ }
743
+ }
744
+ if (testing === "vitest") {
745
+ addPackageName(packageNames, explicitVersions, "vitest");
746
+ if (isReact) {
747
+ addPackageName(packageNames, explicitVersions, "@testing-library/react");
748
+ addPackageName(packageNames, explicitVersions, "@testing-library/dom");
749
+ addPackageName(packageNames, explicitVersions, "jsdom");
750
+ }
751
+ }
752
+ if (linter === "eslint") {
753
+ addPackageName(packageNames, explicitVersions, "eslint");
754
+ if (isTypescript) {
755
+ addPackageName(packageNames, explicitVersions, "typescript-eslint");
756
+ }
757
+ if (isReact) {
758
+ addPackageName(packageNames, explicitVersions, "eslint-plugin-react-hooks");
759
+ }
760
+ } else if (linter === "oxlint") {
761
+ if (!inWorkspace) {
762
+ addPackageName(packageNames, explicitVersions, "oxlint");
763
+ }
764
+ } else if (linter === "biome") {
765
+ addPackageName(packageNames, explicitVersions, "@biomejs/biome");
766
+ }
767
+ if (formatter === "prettier") {
768
+ addPackageName(packageNames, explicitVersions, "prettier");
769
+ } else if (formatter === "oxfmt") {
770
+ if (!inWorkspace) {
771
+ addPackageName(packageNames, explicitVersions, "oxfmt");
772
+ }
773
+ } else if (formatter === "biome") {
774
+ addPackageName(packageNames, explicitVersions, "@biomejs/biome");
775
+ }
776
+ if (isLibrary) {
777
+ addPackageName(packageNames, explicitVersions, bundler === "unbuild" ? "unbuild" : "tsdown");
778
+ }
779
+ return [...packageNames];
780
+ }
781
+ function getLinterPackage(linter) {
782
+ if (linter === "biome") {
783
+ return "@biomejs/biome";
784
+ }
785
+ return linter;
786
+ }
787
+ function getFormatterPackage(formatter) {
788
+ if (formatter === "biome") {
789
+ return "@biomejs/biome";
790
+ }
791
+ return formatter;
792
+ }
793
+ function isEnabledOption(option) {
794
+ return option != null && option !== false;
795
+ }
796
+
797
+ function generateTypescriptConfig(baseTemplateOrParams) {
798
+ const params = typeof baseTemplateOrParams === "string" ? { baseTemplate: baseTemplateOrParams } : baseTemplateOrParams;
799
+ const {
800
+ baseTemplate,
801
+ useConfigPackage,
802
+ configStrategy = "stealth",
803
+ engine,
804
+ versions = {}
805
+ } = params;
806
+ const isReact = baseTemplate === "react";
807
+ const isR3f = baseTemplate === "r3f";
808
+ const files = {};
809
+ const devDependencies = {};
810
+ assignResolvedPackageVersion(devDependencies, versions, "typescript");
811
+ if (getEngineName(engine) === "node") {
812
+ devDependencies["@types/node"] = formatNodeTypesVersion(versions, engine);
813
+ } else {
814
+ devDependencies["@types/node"] = "^22.0.0";
815
+ }
816
+ if (isReact || isR3f) {
817
+ assignResolvedPackageVersion(devDependencies, versions, "@types/react");
818
+ assignResolvedPackageVersion(devDependencies, versions, "@types/react-dom");
819
+ }
820
+ if (isR3f) {
821
+ assignResolvedPackageVersion(devDependencies, versions, "@types/three", "~");
822
+ }
823
+ if (useConfigPackage) {
824
+ devDependencies["@config/typescript"] = "workspace:*";
825
+ const baseConfig = isReact || isR3f ? "@config/typescript/react.json" : "@config/typescript/app.json";
826
+ files["tsconfig.json"] = {
827
+ type: "text",
828
+ content: JSON.stringify(
829
+ {
830
+ $schema: "https://json.schemastore.org/tsconfig",
831
+ extends: baseConfig,
832
+ include: ["src/**/*", "tests/**/*"]
833
+ },
834
+ null,
835
+ 2
836
+ )
837
+ };
838
+ return { files, devDependencies };
839
+ }
840
+ if (configStrategy === "stealth") {
841
+ const tsConfig = {
842
+ $schema: "https://json.schemastore.org/tsconfig",
843
+ files: [],
844
+ references: [
845
+ { path: "./.config/tsconfig.app.json" },
846
+ { path: "./.config/tsconfig.node.json" }
847
+ ]
848
+ };
849
+ files["tsconfig.json"] = {
850
+ type: "text",
851
+ content: JSON.stringify(tsConfig, null, 2)
852
+ };
853
+ const tsConfigApp = {
854
+ $schema: "https://json.schemastore.org/tsconfig",
855
+ compilerOptions: {
856
+ target: "ESNext",
857
+ module: "ESNext",
858
+ moduleResolution: "bundler",
859
+ lib: ["DOM", "DOM.Iterable", "ESNext"],
860
+ esModuleInterop: true,
861
+ allowSyntheticDefaultImports: true,
862
+ strict: true,
863
+ skipLibCheck: true,
864
+ composite: true,
865
+ rewriteRelativeImportExtensions: true,
866
+ erasableSyntaxOnly: true,
867
+ ...isReact || isR3f ? { jsx: "react-jsx" } : {}
868
+ },
869
+ include: ["../src", "../tests"]
870
+ };
871
+ files[".config/tsconfig.app.json"] = {
872
+ type: "text",
873
+ content: JSON.stringify(tsConfigApp, null, 2)
874
+ };
875
+ const tsConfigNode = {
876
+ $schema: "https://json.schemastore.org/tsconfig",
877
+ compilerOptions: {
878
+ target: "ESNext",
879
+ module: "ESNext",
880
+ moduleResolution: "bundler",
881
+ lib: ["ESNext"],
882
+ esModuleInterop: true,
883
+ allowSyntheticDefaultImports: true,
884
+ strict: true,
885
+ skipLibCheck: true,
886
+ composite: true,
887
+ rewriteRelativeImportExtensions: true,
888
+ erasableSyntaxOnly: true
889
+ },
890
+ include: ["../*.config.ts", "./*.ts"]
891
+ };
892
+ files[".config/tsconfig.node.json"] = {
893
+ type: "text",
894
+ content: JSON.stringify(tsConfigNode, null, 2)
895
+ };
896
+ } else {
897
+ const tsConfig = {
898
+ $schema: "https://json.schemastore.org/tsconfig",
899
+ files: [],
900
+ references: [{ path: "./tsconfig.app.json" }, { path: "./tsconfig.node.json" }]
901
+ };
902
+ files["tsconfig.json"] = {
903
+ type: "text",
904
+ content: JSON.stringify(tsConfig, null, 2)
905
+ };
906
+ const tsConfigApp = {
907
+ $schema: "https://json.schemastore.org/tsconfig",
908
+ compilerOptions: {
909
+ target: "ESNext",
910
+ module: "ESNext",
911
+ moduleResolution: "bundler",
912
+ lib: ["DOM", "DOM.Iterable", "ESNext"],
913
+ esModuleInterop: true,
914
+ allowSyntheticDefaultImports: true,
915
+ strict: true,
916
+ skipLibCheck: true,
917
+ composite: true,
918
+ rewriteRelativeImportExtensions: true,
919
+ erasableSyntaxOnly: true,
920
+ ...isReact || isR3f ? { jsx: "react-jsx" } : {}
921
+ },
922
+ include: ["src", "tests"]
923
+ };
924
+ files["tsconfig.app.json"] = {
925
+ type: "text",
926
+ content: JSON.stringify(tsConfigApp, null, 2)
927
+ };
928
+ const tsConfigNode = {
929
+ $schema: "https://json.schemastore.org/tsconfig",
930
+ compilerOptions: {
931
+ target: "ESNext",
932
+ module: "ESNext",
933
+ moduleResolution: "bundler",
934
+ lib: ["ESNext"],
935
+ esModuleInterop: true,
936
+ allowSyntheticDefaultImports: true,
937
+ strict: true,
938
+ skipLibCheck: true,
939
+ composite: true,
940
+ rewriteRelativeImportExtensions: true,
941
+ erasableSyntaxOnly: true
942
+ },
943
+ include: ["*.config.ts"]
944
+ };
945
+ files["tsconfig.node.json"] = {
946
+ type: "text",
947
+ content: JSON.stringify(tsConfigNode, null, 2)
948
+ };
949
+ }
950
+ return { files, devDependencies };
951
+ }
952
+
953
+ const DEFAULT_LIBRARY_VERSION = "0.1.0";
954
+ function generatePackageJson(params) {
955
+ const {
956
+ name,
957
+ language,
958
+ isLibrary,
959
+ dependencies,
960
+ devDependencies,
961
+ peerDependencies,
962
+ scripts,
963
+ options,
964
+ workspaceDependencies
965
+ } = params;
966
+ const files = {};
967
+ const packageManager = getPackageManagerSpec(options.packageManager);
968
+ const isPnpm = packageManager.name === "pnpm";
969
+ const packageJson = {
970
+ name,
971
+ description: "Built with \u{1F339} create-krispya",
972
+ type: "module"
973
+ };
974
+ if (isLibrary) {
975
+ packageJson.version = DEFAULT_LIBRARY_VERSION;
976
+ packageJson.main = "./dist/index.mjs";
977
+ packageJson.module = "./dist/index.mjs";
978
+ if (language === "typescript") {
979
+ packageJson.types = "./dist/index.d.ts";
980
+ }
981
+ packageJson.exports = {
982
+ ".": {
983
+ ...language === "typescript" && { types: "./dist/index.d.ts" },
984
+ import: "./dist/index.mjs",
985
+ require: "./dist/index.cjs"
986
+ }
987
+ };
988
+ packageJson.files = ["dist"];
989
+ }
990
+ const sortKeys = (obj) => Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
991
+ const allDependencies = { ...dependencies };
992
+ if (workspaceDependencies && workspaceDependencies.length > 0) {
993
+ for (const pkgName of workspaceDependencies) {
994
+ allDependencies[pkgName] = "workspace:*";
995
+ }
996
+ }
997
+ const allDevDependencies = { ...devDependencies };
998
+ const engine = getEngineSpec(options.engine);
999
+ if (getEngineName(engine) === "node" && engine.version) {
1000
+ allDevDependencies["@types/node"] ??= formatNodeTypesVersion(
1001
+ options.versions,
1002
+ options.engine
1003
+ );
1004
+ }
1005
+ packageJson.scripts = scripts;
1006
+ packageJson.dependencies = sortKeys(allDependencies);
1007
+ if (Object.keys(allDevDependencies).length > 0) {
1008
+ packageJson.devDependencies = sortKeys(allDevDependencies);
1009
+ }
1010
+ if (isLibrary && Object.keys(peerDependencies).length > 0) {
1011
+ packageJson.peerDependencies = sortKeys(peerDependencies);
1012
+ }
1013
+ const isMonorepoPackage = options.workspaceRoot != null;
1014
+ if (!isMonorepoPackage) {
1015
+ const engines = {};
1016
+ if (packageManager.version != null) {
1017
+ const majorVersion = packageManager.version.split(".")[0];
1018
+ engines[packageManager.name] = `>=${majorVersion}.0.0`;
1019
+ packageJson.packageManager = formatPackageManager(packageManager);
1020
+ }
1021
+ if (engine.version != null) {
1022
+ const majorVersion = engine.version.split(".")[0];
1023
+ engines[engine.name] = `>=${majorVersion}.0.0`;
1024
+ }
1025
+ if (Object.keys(engines).length > 0) {
1026
+ packageJson.engines = engines;
1027
+ }
1028
+ }
1029
+ files["package.json"] = {
1030
+ type: "text",
1031
+ content: JSON.stringify(packageJson, null, 2)
1032
+ };
1033
+ if (isPnpm && !options.workspaceRoot) {
1034
+ const manageVersions = options.pnpmManageVersions ?? true;
1035
+ const workspaceLines = [];
1036
+ if (manageVersions) {
1037
+ workspaceLines.push("manage-package-manager-versions: true", "");
1038
+ }
1039
+ workspaceLines.push("onlyBuiltDependencies:", " - esbuild");
1040
+ files["pnpm-workspace.yaml"] = {
1041
+ type: "text",
1042
+ content: workspaceLines.join("\n")
1043
+ };
1044
+ }
1045
+ return { files };
1046
+ }
1047
+
1048
+ function generateReadme(params) {
1049
+ const { name, baseTemplate, isLibrary, libraryBundler, packageManager, codeSnippets } = params;
1050
+ const isVanilla = baseTemplate === "vanilla";
1051
+ const isReact = baseTemplate === "react";
1052
+ const isR3f = baseTemplate === "r3f";
1053
+ const ext = "ts";
1054
+ const jsxExt = "tsx";
1055
+ codeSnippets["readme-libraries"] ??= [];
1056
+ codeSnippets["readme-commands"] ??= [];
1057
+ if (isLibrary) ; else if (isVanilla) {
1058
+ codeSnippets["readme-libraries"].unshift(
1059
+ `[Vite](https://vitejs.dev/) - Next generation frontend tooling`
1060
+ );
1061
+ } else if (isReact) {
1062
+ codeSnippets["readme-libraries"].unshift(
1063
+ `[React](https://react.dev/) - A JavaScript library for building user interfaces`,
1064
+ `[Vite](https://vitejs.dev/) - Next generation frontend tooling`
1065
+ );
1066
+ } else {
1067
+ codeSnippets["readme-libraries"].unshift(
1068
+ `[React](https://react.dev/) - A JavaScript library for building user interfaces`,
1069
+ `[Three.js](https://threejs.org/) - JavaScript 3D library`,
1070
+ `[@react-three/fiber](https://docs.pmnd.rs/react-three-fiber) - lets you create Three.js scenes using React components`
1071
+ );
1072
+ }
1073
+ if (isLibrary) {
1074
+ codeSnippets["readme-commands"].unshift(
1075
+ `\`${packageManager} install\` to install the dependencies`,
1076
+ `\`${packageManager} run build\` to build the library into the \`dist\` folder`,
1077
+ `\`${packageManager} run test\` to run the tests`,
1078
+ `\`${packageManager} run release\` to build and publish to npm`
1079
+ );
1080
+ } else {
1081
+ codeSnippets["readme-commands"].unshift(
1082
+ `\`${packageManager} install\` to install the dependencies`,
1083
+ `\`${packageManager} run dev\` to run the development server and preview the app with live updates`,
1084
+ `\`${packageManager} run build\` to build the app into the \`dist\` folder`,
1085
+ `\`${packageManager} run test\` to run the tests`
1086
+ );
1087
+ }
1088
+ let architectureDesc;
1089
+ if (isLibrary) {
1090
+ architectureDesc = [
1091
+ `- \`src/index.${isReact || isR3f ? jsxExt : ext}\` is the main entry point for your library exports`,
1092
+ `- Add your library code in the \`src\` folder`,
1093
+ `- \`tests/\` contains your test files`
1094
+ ];
1095
+ } else if (isVanilla) {
1096
+ architectureDesc = [
1097
+ `- \`src/main.${ext}\` is the entry point for your application`,
1098
+ `- \`tests/\` contains your test files`,
1099
+ `- Static assets can be placed in the \`public\` folder`
1100
+ ];
1101
+ } else if (isReact) {
1102
+ architectureDesc = [
1103
+ `- \`src/app.${jsxExt}\` defines the main application component`,
1104
+ `- \`src/index.${jsxExt}\` renders the React app into the DOM`,
1105
+ `- \`tests/\` contains your test files`,
1106
+ `- Static assets can be placed in the \`public\` folder`
1107
+ ];
1108
+ } else {
1109
+ architectureDesc = [
1110
+ `- \`app.${jsxExt}\` defines the main application component containing your 3D content`,
1111
+ `- Modify the content inside the \`<Canvas>\` component to change what is visible on screen`,
1112
+ `- \`tests/\` contains your test files`,
1113
+ `- Static assets can be placed in the \`public\` folder`
1114
+ ];
1115
+ }
1116
+ const bundlerDescription = isLibrary ? libraryBundler === "unbuild" ? `This library uses [unbuild](https://github.com/unjs/unbuild) for building.` : `This library uses [tsdown](https://github.com/nicepkg/tsdown) for building.` : `This project uses [Vite](https://vitejs.dev/) as the bundler for fast development and optimized production builds.`;
1117
+ const content = [
1118
+ `# ${name}`,
1119
+ `This ${isLibrary ? "library" : "project"} was generated with create-krispya`,
1120
+ ...codeSnippets["readme-start"] ?? [],
1121
+ "\n",
1122
+ `## Project Architecture`,
1123
+ bundlerDescription,
1124
+ ...architectureDesc,
1125
+ "\n",
1126
+ `## Libraries`,
1127
+ `The following libraries are used - checkout the linked docs to learn more`,
1128
+ ...(codeSnippets["readme-libraries"] ?? []).map((library) => `- ${library}`),
1129
+ "\n",
1130
+ codeSnippets["readme-tools"] && `## Tools`,
1131
+ ...(codeSnippets["readme-tools"] ?? []).map((tool) => `- ${tool}`),
1132
+ codeSnippets["readme-tools"] && `
1133
+ `,
1134
+ `## Development Commands`,
1135
+ ...(codeSnippets["readme-commands"] ?? []).map((command) => `- ${command}`),
1136
+ ...codeSnippets["readme-end"] ?? []
1137
+ ].filter(Boolean).join("\n");
1138
+ return { type: "text", content };
1139
+ }
1140
+
1141
+ function generateSourceFiles(params) {
1142
+ const { name, baseTemplate, language, isLibrary, codeSnippets, replacements } = params;
1143
+ const files = {};
1144
+ const ext = language === "typescript" ? "ts" : "js";
1145
+ const jsxExt = language === "typescript" ? "tsx" : "jsx";
1146
+ const isVanilla = baseTemplate === "vanilla";
1147
+ const isReact = baseTemplate === "react";
1148
+ const isR3f = baseTemplate === "r3f";
1149
+ if (isLibrary) {
1150
+ const libExt = isReact || isR3f ? jsxExt : ext;
1151
+ let libContent;
1152
+ if (isVanilla) {
1153
+ libContent = [
1154
+ `// Library entry point`,
1155
+ `export function hello(name: string = "world"): string {`,
1156
+ ` return \`Hello, \${name}!\``,
1157
+ `}`
1158
+ ].join("\n");
1159
+ } else if (isReact) {
1160
+ libContent = [
1161
+ `// Library entry point`,
1162
+ `export function MyComponent({ message = "Hello from library!" }: { message?: string }) {`,
1163
+ ` return <div>{message}</div>`,
1164
+ `}`
1165
+ ].join("\n");
1166
+ } else {
1167
+ libContent = [
1168
+ `// Library entry point`,
1169
+ `export function MyMesh({ color = "orange" }: { color?: string }) {`,
1170
+ ` return (`,
1171
+ ` <mesh>`,
1172
+ ` <boxGeometry />`,
1173
+ ` <meshStandardMaterial color={color} />`,
1174
+ ` </mesh>`,
1175
+ ` )`,
1176
+ `}`
1177
+ ].join("\n");
1178
+ }
1179
+ files[`src/index.${libExt}`] = { type: "text", content: libContent };
1180
+ } else if (isVanilla) {
1181
+ files[`src/main.${ext}`] = { type: "text", content: ViteIndexContent };
1182
+ files["src/style.css"] = { type: "text", content: ViteStyleContent };
1183
+ const indexHtml = ViteHtmlContent.replace("$indexPath", `./src/main.${ext}`).replace(
1184
+ "$title",
1185
+ name
1186
+ );
1187
+ files["index.html"] = { type: "text", content: indexHtml };
1188
+ } else {
1189
+ files[`src/index.tsx`] = { type: "text", content: IndexContent };
1190
+ const indexHtml = HtmlContent.replace(
1191
+ "$indexPath",
1192
+ language === "javascript" ? "./src/index.jsx" : "./src/index.tsx"
1193
+ ).replace("$title", name);
1194
+ files["index.html"] = { type: "text", content: indexHtml };
1195
+ codeSnippets["dom-end"]?.reverse();
1196
+ codeSnippets["global-end"]?.reverse();
1197
+ codeSnippets["scene-end"]?.reverse();
1198
+ let appCode;
1199
+ if (isReact) {
1200
+ appCode = [
1201
+ ...codeSnippets["import"] ?? [],
1202
+ ...codeSnippets["global-start"] ?? [],
1203
+ `export function App() {`,
1204
+ " return (",
1205
+ ' <div style={{ padding: "2rem" }}>',
1206
+ " <h1>Hello React!</h1>",
1207
+ " <p>Edit src/app.tsx and save to see changes.</p>",
1208
+ " </div>",
1209
+ " )",
1210
+ "}",
1211
+ ...codeSnippets["global-end"] ?? []
1212
+ ].join("\n");
1213
+ } else {
1214
+ appCode = [
1215
+ ...codeSnippets["import"] ?? [],
1216
+ ...codeSnippets["global-start"] ?? [],
1217
+ `export function App() {`,
1218
+ " return <>",
1219
+ ...codeSnippets["dom-start"] ?? [],
1220
+ ...codeSnippets["dom"] ?? [],
1221
+ " <Canvas>",
1222
+ ...codeSnippets["scene-start"] ?? [],
1223
+ ...codeSnippets["scene"] ?? [],
1224
+ ...codeSnippets["scene-end"] ?? [],
1225
+ " </Canvas>",
1226
+ ...codeSnippets["dom-end"] ?? [],
1227
+ " </>",
1228
+ "}",
1229
+ ...codeSnippets["global-end"] ?? []
1230
+ ].join("\n");
1231
+ }
1232
+ for (const { search, replace } of replacements) {
1233
+ appCode = appCode.replace(search, replace);
1234
+ }
1235
+ files[`src/app.tsx`] = { type: "text", content: appCode };
1236
+ }
1237
+ return files;
1238
+ }
1239
+
1240
+ function generateTestFiles(params) {
1241
+ const { baseTemplate, language, isLibrary } = params;
1242
+ const files = {};
1243
+ const ext = language === "typescript" ? "ts" : "js";
1244
+ const jsxExt = language === "typescript" ? "tsx" : "jsx";
1245
+ const isVanilla = baseTemplate === "vanilla";
1246
+ const isReact = baseTemplate === "react";
1247
+ const isR3f = baseTemplate === "r3f";
1248
+ if (isLibrary) {
1249
+ const testExt = isReact || isR3f ? jsxExt : ext;
1250
+ let testContent;
1251
+ if (isVanilla) {
1252
+ testContent = [
1253
+ `import { describe, it, expect } from "vitest"`,
1254
+ `import { hello } from "../src/index.js"`,
1255
+ ``,
1256
+ `describe("hello", () => {`,
1257
+ ` it("returns greeting with default name", () => {`,
1258
+ ` expect(hello()).toBe("Hello, world!")`,
1259
+ ` })`,
1260
+ ``,
1261
+ ` it("returns greeting with custom name", () => {`,
1262
+ ` expect(hello("vitest")).toBe("Hello, vitest!")`,
1263
+ ` })`,
1264
+ `})`
1265
+ ].join("\n");
1266
+ } else if (isReact) {
1267
+ testContent = [
1268
+ `import { describe, it, expect } from "vitest"`,
1269
+ `import { render, screen } from "@testing-library/react"`,
1270
+ `import { MyComponent } from "../src/index.js"`,
1271
+ ``,
1272
+ `describe("MyComponent", () => {`,
1273
+ ` it("renders with default message", () => {`,
1274
+ ` render(<MyComponent />)`,
1275
+ ` expect(screen.getByText("Hello from library!")).toBeDefined()`,
1276
+ ` })`,
1277
+ ``,
1278
+ ` it("renders with custom message", () => {`,
1279
+ ` render(<MyComponent message="Custom message" />)`,
1280
+ ` expect(screen.getByText("Custom message")).toBeDefined()`,
1281
+ ` })`,
1282
+ `})`
1283
+ ].join("\n");
1284
+ } else {
1285
+ testContent = [
1286
+ `import { describe, it, expect } from "vitest"`,
1287
+ `import { MyMesh } from "../src/index.js"`,
1288
+ ``,
1289
+ `describe("MyMesh", () => {`,
1290
+ ` it("is defined", () => {`,
1291
+ ` expect(MyMesh).toBeDefined()`,
1292
+ ` })`,
1293
+ `})`
1294
+ ].join("\n");
1295
+ }
1296
+ files[`tests/index.test.${testExt}`] = {
1297
+ type: "text",
1298
+ content: testContent
1299
+ };
1300
+ } else if (isVanilla) {
1301
+ const testContent = [
1302
+ `import { describe, it, expect } from "vitest"`,
1303
+ ``,
1304
+ `describe("example", () => {`,
1305
+ ` it("works", () => {`,
1306
+ ` expect(1 + 1).toBe(2)`,
1307
+ ` })`,
1308
+ `})`
1309
+ ].join("\n");
1310
+ files[`tests/main.test.${ext}`] = { type: "text", content: testContent };
1311
+ } else if (isReact) {
1312
+ const testContent = [
1313
+ `import { describe, it, expect } from "vitest"`,
1314
+ `import { render, screen } from "@testing-library/react"`,
1315
+ `import { App } from "../src/app.js"`,
1316
+ ``,
1317
+ `describe("App", () => {`,
1318
+ ` it("renders heading", () => {`,
1319
+ ` render(<App />)`,
1320
+ ` expect(screen.getByText("Hello React!")).toBeDefined()`,
1321
+ ` })`,
1322
+ `})`
1323
+ ].join("\n");
1324
+ files[`tests/app.test.${jsxExt}`] = { type: "text", content: testContent };
1325
+ } else {
1326
+ const testContent = [
1327
+ `import { describe, it, expect } from "vitest"`,
1328
+ `import { App } from "../src/app.js"`,
1329
+ ``,
1330
+ `describe("App", () => {`,
1331
+ ` it("is defined", () => {`,
1332
+ ` expect(App).toBeDefined()`,
1333
+ ` })`,
1334
+ `})`
1335
+ ].join("\n");
1336
+ files[`tests/app.test.${jsxExt}`] = { type: "text", content: testContent };
1337
+ }
1338
+ return files;
1339
+ }
1340
+
1341
+ const COMMON_GITIGNORE_LINES = [
1342
+ "node_modules",
1343
+ "dist",
1344
+ "*.tsbuildinfo",
1345
+ ".env",
1346
+ ".env.*",
1347
+ "!.env.example"
1348
+ ];
1349
+ function generateGitignore(variant) {
1350
+ const lines = variant === "workspace-root" ? [...COMMON_GITIGNORE_LINES, ".DS_Store"] : COMMON_GITIGNORE_LINES;
1351
+ return {
1352
+ type: "text",
1353
+ content: lines.join("\n")
1354
+ };
1355
+ }
1356
+
1357
+ function generateVscodeFiles$1(params) {
1358
+ const { codeSnippets, vscodeSettings } = params;
1359
+ const files = {};
1360
+ if (codeSnippets["vscode-extension-suggestion"]?.length) {
1361
+ const uniqueRecommendations = [...new Set(codeSnippets["vscode-extension-suggestion"])];
1362
+ files[".vscode/extensions.json"] = {
1363
+ type: "text",
1364
+ content: JSON.stringify(
1365
+ {
1366
+ recommendations: uniqueRecommendations
1367
+ },
1368
+ null,
1369
+ 2
1370
+ )
1371
+ };
1372
+ }
1373
+ if (Object.keys(vscodeSettings).length > 0) {
1374
+ const sortedSettings = Object.fromEntries(
1375
+ Object.entries(vscodeSettings).sort(([a], [b]) => a.localeCompare(b))
1376
+ );
1377
+ files[".vscode/settings.json"] = {
1378
+ type: "text",
1379
+ content: JSON.stringify(sortedSettings, null, 2)
1380
+ };
1381
+ }
1382
+ return files;
1383
+ }
1384
+
1385
+ function formatValue(value, indent) {
1386
+ const spaces = " ".repeat(indent);
1387
+ const innerSpaces = " ".repeat(indent + 1);
1388
+ if (typeof value === "string") {
1389
+ if (value.startsWith("$raw:")) {
1390
+ return value.slice(5);
1391
+ }
1392
+ return JSON.stringify(value);
1393
+ }
1394
+ if (typeof value === "number" || typeof value === "boolean") {
1395
+ return String(value);
1396
+ }
1397
+ if (value === null) {
1398
+ return "null";
1399
+ }
1400
+ if (Array.isArray(value)) {
1401
+ if (value.length === 0) return "[]";
1402
+ const items = value.map((v) => `${innerSpaces}${formatValue(v, indent + 1)}`);
1403
+ return `[
1404
+ ${items.join(",\n")}
1405
+ ${spaces}]`;
1406
+ }
1407
+ if (typeof value === "object") {
1408
+ const entries = Object.entries(value);
1409
+ if (entries.length === 0) return "{}";
1410
+ const props = entries.map(
1411
+ ([key, val]) => `${innerSpaces}${key}: ${formatValue(val, indent + 1)}`
1412
+ );
1413
+ return `{
1414
+ ${props.join(",\n")}
1415
+ ${spaces}}`;
1416
+ }
1417
+ return String(value);
1418
+ }
1419
+ function generateViteConfig(params) {
1420
+ const { viteConfig, codeSnippets } = params;
1421
+ const configBody = formatValue(viteConfig, 0);
1422
+ const viteConfigContent = [
1423
+ `import { defineConfig } from "vite"`,
1424
+ ...codeSnippets["vite-config-import"] ?? [],
1425
+ ``,
1426
+ `export default defineConfig(${configBody})`,
1427
+ ``
1428
+ ].join("\n");
1429
+ return { type: "text", content: viteConfigContent };
1430
+ }
1431
+
1432
+ function generateTypescriptConfigPackage(files) {
1433
+ const basePath = ".config/typescript";
1434
+ files[`${basePath}/package.json`] = {
1435
+ type: "text",
1436
+ content: JSON.stringify(
1437
+ {
1438
+ name: "@config/typescript",
1439
+ version: "0.1.0",
1440
+ private: true,
1441
+ files: ["base.json", "app.json", "node.json", "react.json"]
1442
+ },
1443
+ null,
1444
+ 2
1445
+ )
1446
+ };
1447
+ files[`${basePath}/README.md`] = {
1448
+ type: "text",
1449
+ content: `# \`@config/typescript\`
1450
+
1451
+ These are base shared \`tsconfig.json\`s from which all other \`tsconfig.json\`s inherit.
1452
+
1453
+ ## Usage
1454
+
1455
+ In your package's \`tsconfig.json\`:
1456
+
1457
+ \`\`\`json
1458
+ {
1459
+ "extends": "@config/typescript/app.json",
1460
+ "include": ["src/**/*", "tests"]
1461
+ }
1462
+ \`\`\`
1463
+
1464
+ ## Available Configs
1465
+
1466
+ - \`base.json\` - Common TypeScript compiler options
1467
+ - \`app.json\` - For browser/DOM code (extends base)
1468
+ - \`node.json\` - For Node.js code (extends base)
1469
+ - \`react.json\` - For React projects with JSX (extends app)
1470
+ `
1471
+ };
1472
+ files[`${basePath}/base.json`] = {
1473
+ type: "text",
1474
+ content: JSON.stringify(
1475
+ {
1476
+ $schema: "https://json.schemastore.org/tsconfig",
1477
+ compilerOptions: {
1478
+ target: "ESNext",
1479
+ module: "ESNext",
1480
+ moduleResolution: "bundler",
1481
+ esModuleInterop: true,
1482
+ allowSyntheticDefaultImports: true,
1483
+ strict: true,
1484
+ skipLibCheck: true,
1485
+ composite: true,
1486
+ rewriteRelativeImportExtensions: true,
1487
+ erasableSyntaxOnly: true
1488
+ }
1489
+ },
1490
+ null,
1491
+ 2
1492
+ )
1493
+ };
1494
+ files[`${basePath}/app.json`] = {
1495
+ type: "text",
1496
+ content: JSON.stringify(
1497
+ {
1498
+ $schema: "https://json.schemastore.org/tsconfig",
1499
+ extends: "./base.json",
1500
+ compilerOptions: {
1501
+ lib: ["DOM", "DOM.Iterable", "ESNext"]
1502
+ }
1503
+ },
1504
+ null,
1505
+ 2
1506
+ )
1507
+ };
1508
+ files[`${basePath}/node.json`] = {
1509
+ type: "text",
1510
+ content: JSON.stringify(
1511
+ {
1512
+ $schema: "https://json.schemastore.org/tsconfig",
1513
+ extends: "./base.json",
1514
+ compilerOptions: {
1515
+ lib: ["ESNext"]
1516
+ }
1517
+ },
1518
+ null,
1519
+ 2
1520
+ )
1521
+ };
1522
+ files[`${basePath}/react.json`] = {
1523
+ type: "text",
1524
+ content: JSON.stringify(
1525
+ {
1526
+ $schema: "https://json.schemastore.org/tsconfig",
1527
+ extends: "./app.json",
1528
+ compilerOptions: {
1529
+ jsx: "react-jsx"
1530
+ }
1531
+ },
1532
+ null,
1533
+ 2
1534
+ )
1535
+ };
1536
+ }
1537
+ function generateOxlintConfigPackage(files) {
1538
+ const basePath = ".config/oxlint";
1539
+ const { rules } = defaultLinterConfig;
1540
+ files[`${basePath}/package.json`] = {
1541
+ type: "text",
1542
+ content: JSON.stringify(
1543
+ {
1544
+ name: "@config/oxlint",
1545
+ version: "0.1.0",
1546
+ private: true,
1547
+ files: ["base.json", "react.json"]
1548
+ },
1549
+ null,
1550
+ 2
1551
+ )
1552
+ };
1553
+ files[`${basePath}/README.md`] = {
1554
+ type: "text",
1555
+ content: `# \`@config/oxlint\`
1556
+
1557
+ Shared oxlint configurations for the monorepo.
1558
+
1559
+ ## Usage
1560
+
1561
+ Run oxlint with a config:
1562
+
1563
+ \`\`\`bash
1564
+ oxlint -c node_modules/@config/oxlint/base.json
1565
+ \`\`\`
1566
+
1567
+ ## Available Configs
1568
+
1569
+ - \`base.json\` - Base linting rules for TypeScript projects
1570
+ - \`react.json\` - Extends base with React-specific rules
1571
+ `
1572
+ };
1573
+ files[`${basePath}/base.json`] = {
1574
+ type: "text",
1575
+ content: JSON.stringify(
1576
+ {
1577
+ $schema: "./node_modules/oxlint/configuration_schema.json",
1578
+ plugins: ["unicorn", "typescript", "oxc"],
1579
+ rules: {
1580
+ "no-unused-vars": [
1581
+ rules.noUnusedVars.level,
1582
+ {
1583
+ argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1584
+ varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1585
+ caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1586
+ }
1587
+ ],
1588
+ "no-useless-escape": "off",
1589
+ "no-unused-expressions": [
1590
+ rules.noUnusedExpressions.level,
1591
+ { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1592
+ ]
1593
+ },
1594
+ ignorePatterns: defaultLinterConfig.ignorePatterns
1595
+ },
1596
+ null,
1597
+ 2
1598
+ )
1599
+ };
1600
+ files[`${basePath}/react.json`] = {
1601
+ type: "text",
1602
+ content: JSON.stringify(
1603
+ {
1604
+ $schema: "./node_modules/oxlint/configuration_schema.json",
1605
+ plugins: ["unicorn", "typescript", "oxc", "react"],
1606
+ rules: {
1607
+ "no-unused-vars": [
1608
+ rules.noUnusedVars.level,
1609
+ {
1610
+ argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
1611
+ varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
1612
+ caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
1613
+ }
1614
+ ],
1615
+ "no-useless-escape": "off",
1616
+ "no-unused-expressions": [
1617
+ rules.noUnusedExpressions.level,
1618
+ { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
1619
+ ]
1620
+ },
1621
+ ignorePatterns: defaultLinterConfig.ignorePatterns
1622
+ },
1623
+ null,
1624
+ 2
1625
+ )
1626
+ };
1627
+ }
1628
+ function generateEslintConfigPackage(files) {
1629
+ const basePath = ".config/eslint";
1630
+ files[`${basePath}/package.json`] = {
1631
+ type: "text",
1632
+ content: JSON.stringify(
1633
+ {
1634
+ name: "@config/eslint",
1635
+ version: "0.1.0",
1636
+ private: true,
1637
+ type: "module",
1638
+ exports: {
1639
+ "./base": "./base.js",
1640
+ "./react": "./react.js"
1641
+ },
1642
+ files: ["base.js", "react.js"],
1643
+ devDependencies: {
1644
+ "@eslint/js": "^9.17.0",
1645
+ "typescript-eslint": "^8.18.0"
1646
+ }
1647
+ },
1648
+ null,
1649
+ 2
1650
+ )
1651
+ };
1652
+ files[`${basePath}/README.md`] = {
1653
+ type: "text",
1654
+ content: `# \`@config/eslint\`
1655
+
1656
+ Shared ESLint configurations for the monorepo.
1657
+
1658
+ ## Usage
1659
+
1660
+ In your package's \`eslint.config.js\`:
1661
+
1662
+ \`\`\`js
1663
+ import base from "@config/eslint/base";
1664
+
1665
+ export default [...base];
1666
+ \`\`\`
1667
+
1668
+ Or for React projects:
1669
+
1670
+ \`\`\`js
1671
+ import react from "@config/eslint/react";
1672
+
1673
+ export default [...react];
1674
+ \`\`\`
1675
+
1676
+ ## Available Configs
1677
+
1678
+ - \`base\` - Base linting rules for TypeScript projects
1679
+ - \`react\` - Extends base with React-specific rules
1680
+ `
1681
+ };
1682
+ files[`${basePath}/base.js`] = {
1683
+ type: "text",
1684
+ content: `import js from "@eslint/js";
1685
+ import tseslint from "typescript-eslint";
1686
+
1687
+ export default tseslint.config(
1688
+ js.configs.recommended,
1689
+ ...tseslint.configs.recommended,
1690
+ {
1691
+ rules: {
1692
+ "@typescript-eslint/no-unused-vars": [
1693
+ "error",
1694
+ {
1695
+ argsIgnorePattern: "^_",
1696
+ varsIgnorePattern: "^_",
1697
+ caughtErrorsIgnorePattern: "^_",
1698
+ },
1699
+ ],
1700
+ },
1701
+ },
1702
+ {
1703
+ ignores: ["dist/**", "node_modules/**"],
1704
+ }
1705
+ );
1706
+ `
1707
+ };
1708
+ files[`${basePath}/react.js`] = {
1709
+ type: "text",
1710
+ content: `import js from "@eslint/js";
1711
+ import tseslint from "typescript-eslint";
1712
+
1713
+ export default tseslint.config(
1714
+ js.configs.recommended,
1715
+ ...tseslint.configs.recommended,
1716
+ {
1717
+ rules: {
1718
+ "@typescript-eslint/no-unused-vars": [
1719
+ "error",
1720
+ {
1721
+ argsIgnorePattern: "^_",
1722
+ varsIgnorePattern: "^_",
1723
+ caughtErrorsIgnorePattern: "^_",
1724
+ },
1725
+ ],
1726
+ },
1727
+ },
1728
+ {
1729
+ ignores: ["dist/**", "node_modules/**"],
1730
+ }
1731
+ );
1732
+ `
1733
+ };
1734
+ }
1735
+ function generatePrettierConfigPackage(files) {
1736
+ const basePath = ".config/prettier";
1737
+ files[`${basePath}/package.json`] = {
1738
+ type: "text",
1739
+ content: JSON.stringify(
1740
+ {
1741
+ name: "@config/prettier",
1742
+ version: "0.1.0",
1743
+ private: true,
1744
+ type: "module",
1745
+ exports: {
1746
+ ".": "./base.json"
1747
+ },
1748
+ files: ["base.json"]
1749
+ },
1750
+ null,
1751
+ 2
1752
+ )
1753
+ };
1754
+ files[`${basePath}/README.md`] = {
1755
+ type: "text",
1756
+ content: `# \`@config/prettier\`
1757
+
1758
+ Shared Prettier configuration for the monorepo.
1759
+
1760
+ ## Usage
1761
+
1762
+ In your package's \`package.json\`:
1763
+
1764
+ \`\`\`json
1765
+ {
1766
+ "prettier": "@config/prettier"
1767
+ }
1768
+ \`\`\`
1769
+
1770
+ Or in \`.prettierrc.json\`:
1771
+
1772
+ \`\`\`json
1773
+ "@config/prettier"
1774
+ \`\`\`
1775
+
1776
+ ## Available Configs
1777
+
1778
+ - Default export - Base formatter settings
1779
+ `
1780
+ };
1781
+ files[`${basePath}/base.json`] = {
1782
+ type: "text",
1783
+ content: JSON.stringify(defaultPrettierConfig, null, 2)
1784
+ };
1785
+ }
1786
+ function generateOxfmtConfigPackage(files) {
1787
+ const basePath = ".config/oxfmt";
1788
+ files[`${basePath}/package.json`] = {
1789
+ type: "text",
1790
+ content: JSON.stringify(
1791
+ {
1792
+ name: "@config/oxfmt",
1793
+ version: "0.1.0",
1794
+ private: true,
1795
+ files: ["base.json"]
1796
+ },
1797
+ null,
1798
+ 2
1799
+ )
1800
+ };
1801
+ files[`${basePath}/README.md`] = {
1802
+ type: "text",
1803
+ content: `# \`@config/oxfmt\`
1804
+
1805
+ Shared oxfmt (formatter) configuration for the monorepo.
1806
+
1807
+ ## Usage
1808
+
1809
+ Run oxfmt with the config:
1810
+
1811
+ \`\`\`bash
1812
+ oxfmt -c node_modules/@config/oxfmt/base.json --write .
1813
+ \`\`\`
1814
+
1815
+ ## Available Configs
1816
+
1817
+ - \`base.json\` - Base formatter settings (Prettier-compatible)
1818
+ `
1819
+ };
1820
+ files[`${basePath}/base.json`] = {
1821
+ type: "text",
1822
+ content: JSON.stringify(defaultOxfmtConfig, null, 2)
1823
+ };
1824
+ }
1825
+
1826
+ function generateMonorepo(params) {
1827
+ const {
1828
+ name,
1829
+ linter,
1830
+ formatter,
1831
+ packageManager,
1832
+ pnpmManageVersions,
1833
+ engine,
1834
+ versions = {},
1835
+ aiPlatforms
1836
+ } = params;
1837
+ const files = {};
1838
+ const isPnpm = packageManager.name === "pnpm";
1839
+ const devDependencies = {};
1840
+ if (engine?.name === "node" && engine.version) {
1841
+ devDependencies["@types/node"] = formatNodeTypesVersion(versions, engine);
1842
+ } else {
1843
+ devDependencies["@types/node"] = "^22.0.0";
1844
+ }
1845
+ if (linter === "oxlint") {
1846
+ assignResolvedPackageVersion(devDependencies, versions, "oxlint");
1847
+ } else if (linter === "eslint") {
1848
+ assignResolvedPackageVersion(devDependencies, versions, "eslint");
1849
+ } else if (linter === "biome") {
1850
+ assignResolvedPackageVersion(devDependencies, versions, "@biomejs/biome");
1851
+ }
1852
+ if (formatter === "oxfmt") {
1853
+ assignResolvedPackageVersion(devDependencies, versions, "oxfmt");
1854
+ } else if (formatter === "prettier") {
1855
+ assignResolvedPackageVersion(devDependencies, versions, "prettier");
1856
+ }
1857
+ const rootPackageJson = {
1858
+ name: "root",
1859
+ version: "0.0.0",
1860
+ private: true,
1861
+ type: "module",
1862
+ scripts: {
1863
+ dev: "pnpm --filter './apps/*' run dev",
1864
+ build: "pnpm --filter './packages/*' run build && pnpm --filter './apps/*' run build",
1865
+ test: "pnpm -r run test",
1866
+ lint: linter === "oxlint" ? "oxlint ." : linter === "biome" ? "biome check ." : "eslint .",
1867
+ format: formatter === "oxfmt" ? "oxfmt -c .config/oxfmt/base.json ." : formatter === "biome" ? "biome format . --write" : "prettier --config .config/prettier/base.json --write ."
1868
+ },
1869
+ devDependencies
1870
+ };
1871
+ const engines = {};
1872
+ if (isPnpm && packageManager.version) {
1873
+ const majorVersion = packageManager.version.split(".")[0];
1874
+ engines.pnpm = `>=${majorVersion}.0.0`;
1875
+ rootPackageJson.packageManager = formatPackageManager(packageManager);
1876
+ }
1877
+ if (engine?.version) {
1878
+ const majorVersion = engine.version.split(".")[0];
1879
+ engines[engine.name] = `>=${majorVersion}.0.0`;
1880
+ }
1881
+ if (Object.keys(engines).length > 0) {
1882
+ rootPackageJson.engines = engines;
1883
+ }
1884
+ files["package.json"] = {
1885
+ type: "text",
1886
+ content: JSON.stringify(rootPackageJson, null, 2)
1887
+ };
1888
+ if (isPnpm) {
1889
+ const workspaceLines = [];
1890
+ if (pnpmManageVersions) {
1891
+ workspaceLines.push("manage-package-manager-versions: true", "");
1892
+ }
1893
+ workspaceLines.push("packages:", ' - ".config/*"', ' - "apps/*"', ' - "packages/*"', "");
1894
+ workspaceLines.push("onlyBuiltDependencies:", " - esbuild");
1895
+ files["pnpm-workspace.yaml"] = {
1896
+ type: "text",
1897
+ content: workspaceLines.join("\n")
1898
+ };
1899
+ }
1900
+ files["tsconfig.json"] = {
1901
+ type: "text",
1902
+ content: JSON.stringify(
1903
+ {
1904
+ extends: "@config/typescript/base.json",
1905
+ compilerOptions: {
1906
+ noEmit: true
1907
+ },
1908
+ references: []
1909
+ },
1910
+ null,
1911
+ 2
1912
+ )
1913
+ };
1914
+ generateTypescriptConfigPackage(files);
1915
+ if (linter === "oxlint") {
1916
+ generateOxlintConfigPackage(files);
1917
+ files["oxlint.json"] = {
1918
+ type: "text",
1919
+ content: JSON.stringify(
1920
+ {
1921
+ $schema: "./node_modules/oxlint/configuration_schema.json",
1922
+ extends: ["@config/oxlint/base.json"]
1923
+ },
1924
+ null,
1925
+ 2
1926
+ )
1927
+ };
1928
+ } else if (linter === "eslint") {
1929
+ generateEslintConfigPackage(files);
1930
+ files["eslint.config.js"] = {
1931
+ type: "text",
1932
+ content: `import base from "@config/eslint/base";
1933
+
1934
+ export default [...base];
1935
+ `
1936
+ };
1937
+ } else if (linter === "biome") {
1938
+ const biomeVersion = getResolvedPackageVersion(versions, "@biomejs/biome");
1939
+ const biomeConfig = {
1940
+ $schema: `https://biomejs.dev/schemas/${biomeVersion}/schema.json`,
1941
+ vcs: {
1942
+ enabled: true,
1943
+ clientKind: "git",
1944
+ useIgnoreFile: true
1945
+ },
1946
+ linter: {
1947
+ enabled: true,
1948
+ rules: {
1949
+ recommended: true
1950
+ }
1951
+ },
1952
+ formatter: {
1953
+ enabled: formatter === "biome"
1954
+ }
1955
+ };
1956
+ files["biome.json"] = {
1957
+ type: "text",
1958
+ content: JSON.stringify(biomeConfig, null, 2)
1959
+ };
1960
+ }
1961
+ if (formatter === "oxfmt") {
1962
+ generateOxfmtConfigPackage(files);
1963
+ } else if (formatter === "prettier") {
1964
+ generatePrettierConfigPackage(files);
1965
+ }
1966
+ files[".gitignore"] = generateGitignore("workspace-root");
1967
+ files[".gitattributes"] = {
1968
+ type: "text",
1969
+ content: `* text=auto eol=lf
1970
+ *.{cmd,[cC][mM][dD]} text eol=crlf
1971
+ *.{bat,[bB][aA][tT]} text eol=crlf
1972
+ `
1973
+ };
1974
+ generateVscodeFiles(files, linter, formatter);
1975
+ files["README.md"] = {
1976
+ type: "text",
1977
+ content: `# ${name}
1978
+
1979
+ This monorepo workspace was generated with create-krispya.
1980
+
1981
+ ## Structure
1982
+
1983
+ - \`apps/\` - Applications
1984
+ - \`packages/\` - Shared packages and libraries
1985
+ - \`.config/\` - Shared configuration packages
1986
+
1987
+ ## Development Commands
1988
+
1989
+ - \`${packageManager.name} install\` to install all dependencies
1990
+ - \`${packageManager.name} run dev\` to run all applications in development mode
1991
+ - \`${packageManager.name} run build\` to build all packages and applications
1992
+ - \`${packageManager.name} run test\` to run tests across the workspace
1993
+ - \`${packageManager.name} run lint\` to lint all code
1994
+ - \`${packageManager.name} run format\` to format all code
1995
+
1996
+ ## Adding Packages
1997
+
1998
+ To add a new package to this workspace, run create-krispya from this directory and it will detect the monorepo.
1999
+ `
2000
+ };
2001
+ if (aiPlatforms && aiPlatforms.length > 0) {
2002
+ generateAiFiles(files, {
2003
+ name,
2004
+ packageManager: packageManager.name,
2005
+ linter,
2006
+ formatter,
2007
+ isMonorepo: true,
2008
+ platforms: aiPlatforms
2009
+ });
2010
+ }
2011
+ return { files };
2012
+ }
2013
+ function generateVscodeFiles(files, linter, formatter) {
2014
+ const recommendations = [];
2015
+ const settings = {};
2016
+ if (linter === "oxlint") {
2017
+ recommendations.push("oxc.oxc-vscode");
2018
+ settings["oxc.enable"] = true;
2019
+ settings["eslint.enable"] = false;
2020
+ settings["biome.enabled"] = false;
2021
+ } else if (linter === "eslint") {
2022
+ recommendations.push("dbaeumer.vscode-eslint");
2023
+ settings["eslint.enable"] = true;
2024
+ settings["oxc.enable"] = false;
2025
+ settings["biome.enabled"] = false;
2026
+ } else if (linter === "biome") {
2027
+ recommendations.push("biomejs.biome");
2028
+ settings["biome.enabled"] = true;
2029
+ settings["eslint.enable"] = false;
2030
+ settings["oxc.enable"] = false;
2031
+ }
2032
+ if (formatter === "oxfmt") {
2033
+ if (!recommendations.includes("oxc.oxc-vscode")) {
2034
+ recommendations.push("oxc.oxc-vscode");
2035
+ }
2036
+ settings["editor.defaultFormatter"] = "oxc.oxc-vscode";
2037
+ settings["[json]"] = {
2038
+ "editor.defaultFormatter": "vscode.json-language-features"
2039
+ };
2040
+ settings["[jsonc]"] = {
2041
+ "editor.defaultFormatter": "vscode.json-language-features"
2042
+ };
2043
+ } else if (formatter === "prettier") {
2044
+ recommendations.push("esbenp.prettier-vscode");
2045
+ settings["editor.defaultFormatter"] = "esbenp.prettier-vscode";
2046
+ } else if (formatter === "biome") {
2047
+ if (!recommendations.includes("biomejs.biome")) {
2048
+ recommendations.push("biomejs.biome");
2049
+ }
2050
+ settings["editor.defaultFormatter"] = "biomejs.biome";
2051
+ }
2052
+ files[".vscode/extensions.json"] = {
2053
+ type: "text",
2054
+ content: JSON.stringify({ recommendations }, null, 2)
2055
+ };
2056
+ const codeSnippets = {};
2057
+ if (recommendations.length > 0) {
2058
+ codeSnippets["vscode-extension-suggestion"] = recommendations;
2059
+ }
2060
+ Object.assign(
2061
+ files,
2062
+ generateVscodeFiles$1({
2063
+ codeSnippets,
2064
+ vscodeSettings: settings
2065
+ })
2066
+ );
2067
+ }
2068
+
2069
+ const monorepo = {
2070
+ __proto__: null,
2071
+ generateEslintConfigPackage: generateEslintConfigPackage,
2072
+ generateMonorepo: generateMonorepo,
2073
+ generateOxfmtConfigPackage: generateOxfmtConfigPackage,
2074
+ generateOxlintConfigPackage: generateOxlintConfigPackage,
2075
+ generatePrettierConfigPackage: generatePrettierConfigPackage,
2076
+ generateTypescriptConfigPackage: generateTypescriptConfigPackage,
2077
+ generateVscodeFiles: generateVscodeFiles
2078
+ };
2079
+
2080
+ function toBiomeLevel(level) {
2081
+ return level;
2082
+ }
2083
+ function generateBiome(generator, options) {
2084
+ if (options == null || !options.linter && !options.formatter) {
2085
+ return;
2086
+ }
2087
+ const version = generator.getVersion("@biomejs/biome");
2088
+ generator.addDevDependency("@biomejs/biome");
2089
+ const { rules } = defaultLinterConfig;
2090
+ const biomeConfig = {
2091
+ $schema: `https://biomejs.dev/schemas/${version}/schema.json`
2092
+ };
2093
+ if (options.linter) {
2094
+ biomeConfig.linter = {
2095
+ enabled: true,
2096
+ rules: {
2097
+ recommended: true,
2098
+ correctness: {
2099
+ noUnusedVariables: toBiomeLevel(rules.noUnusedVars.level)
2100
+ }
2101
+ }
2102
+ };
2103
+ } else {
2104
+ biomeConfig.linter = {
2105
+ enabled: false
2106
+ };
2107
+ }
2108
+ if (options.formatter) {
2109
+ biomeConfig.formatter = {
2110
+ enabled: true,
2111
+ lineWidth: defaultFormatterConfig.printWidth,
2112
+ indentWidth: defaultFormatterConfig.tabWidth,
2113
+ indentStyle: "space"
2114
+ };
2115
+ biomeConfig.javascript = {
2116
+ formatter: {
2117
+ semicolons: "always" ,
2118
+ quoteStyle: "single" ,
2119
+ trailingCommas: defaultFormatterConfig.trailingComma,
2120
+ bracketSpacing: defaultFormatterConfig.bracketSpacing,
2121
+ arrowParentheses: "always"
2122
+ }
2123
+ };
2124
+ biomeConfig.json = {
2125
+ formatter: {
2126
+ indentWidth: 2
2127
+ }
2128
+ };
2129
+ } else {
2130
+ biomeConfig.formatter = {
2131
+ enabled: false
2132
+ };
2133
+ }
2134
+ const isStealth = generator.isStealthConfig();
2135
+ if (isStealth) {
2136
+ generator.addFile(".config/biome.json", {
2137
+ type: "text",
2138
+ content: JSON.stringify(biomeConfig, null, 2)
2139
+ });
2140
+ if (options.linter) {
2141
+ generator.addScript("lint", "biome lint --config-path .config .");
2142
+ }
2143
+ if (options.formatter) {
2144
+ generator.addScript("format", "biome format --config-path .config --write .");
2145
+ }
2146
+ generator.addVscodeSetting("biome.linter.configPath", ".config/biome.json");
2147
+ } else {
2148
+ generator.addFile("biome.json", {
2149
+ type: "text",
2150
+ content: JSON.stringify(biomeConfig, null, 2)
2151
+ });
2152
+ if (options.linter) {
2153
+ generator.addScript("lint", "biome lint .");
2154
+ }
2155
+ if (options.formatter) {
2156
+ generator.addScript("format", "biome format --write .");
2157
+ }
2158
+ }
2159
+ const roles = [];
2160
+ if (options.linter) roles.push("linter");
2161
+ if (options.formatter) roles.push("formatter");
2162
+ generator.inject(
2163
+ "readme-tools",
2164
+ `[Biome](https://biomejs.dev/) - Fast ${roles.join(" and ")} for JavaScript and TypeScript`
2165
+ );
2166
+ generator.inject("vscode-extension-suggestion", "biomejs.biome");
2167
+ generator.addVscodeSetting("biome.enabled", true);
2168
+ if (options.formatter) {
2169
+ generator.addVscodeSetting("editor.defaultFormatter", "biomejs.biome");
2170
+ }
2171
+ }
2172
+
2173
+ function generateDrei(generator, options) {
2174
+ if (options == null) {
2175
+ return;
2176
+ }
2177
+ generator.addDependency("@react-three/drei");
2178
+ generator.inject("import", `import { Environment } from "@react-three/drei"`);
2179
+ generator.inject("scene", '<Environment background preset="city" />');
2180
+ generator.inject(
2181
+ "readme-libraries",
2182
+ `[@react-three/drei](https://drei.docs.pmnd.rs/) - Useful helpers for @react-three/fiber`
2183
+ );
2184
+ }
2185
+
2186
+ function toEslintLevel(level) {
2187
+ return level;
2188
+ }
2189
+ function generateEslint(generator, options) {
2190
+ generator.addDevDependency("eslint");
2191
+ const template = generator.options.template ?? "vanilla";
2192
+ const baseTemplate = getBaseTemplate(template);
2193
+ const isTypescript = getLanguageFromTemplate(template) === "typescript";
2194
+ const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2195
+ const { rules } = defaultLinterConfig;
2196
+ const imports = ['import js from "@eslint/js"'];
2197
+ const configs = ["js.configs.recommended"];
2198
+ if (isTypescript) {
2199
+ generator.addDevDependency("typescript-eslint");
2200
+ imports.push('import tseslint from "typescript-eslint"');
2201
+ configs.push("...tseslint.configs.recommended");
2202
+ }
2203
+ if (isReact) {
2204
+ generator.addDevDependency("eslint-plugin-react-hooks");
2205
+ imports.push('import reactHooks from "eslint-plugin-react-hooks"');
2206
+ }
2207
+ const ignoresArray = JSON.stringify(defaultLinterConfig.ignorePatterns);
2208
+ const unusedVarsRule = isTypescript ? "@typescript-eslint/no-unused-vars" : "no-unused-vars";
2209
+ const rulesConfig = {
2210
+ [unusedVarsRule]: [
2211
+ toEslintLevel(rules.noUnusedVars.level),
2212
+ {
2213
+ argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
2214
+ varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
2215
+ caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
2216
+ }
2217
+ ],
2218
+ "no-unused-expressions": [
2219
+ toEslintLevel(rules.noUnusedExpressions.level),
2220
+ { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
2221
+ ]
2222
+ };
2223
+ const rulesString = JSON.stringify(rulesConfig, null, 4).replace(/\n/g, "\n ");
2224
+ const configContent = [
2225
+ ...imports,
2226
+ "",
2227
+ "export default [",
2228
+ ` { ignores: ${ignoresArray} },`,
2229
+ ` ${configs.join(",\n ")},`,
2230
+ isReact ? ` {
2231
+ plugins: {
2232
+ "react-hooks": reactHooks,
2233
+ },
2234
+ rules: reactHooks.configs.recommended.rules,
2235
+ },` : "",
2236
+ ` {
2237
+ rules: ${rulesString},
2238
+ },`,
2239
+ "]"
2240
+ ].filter(Boolean).join("\n");
2241
+ const isStealth = generator.isStealthConfig();
2242
+ if (isStealth) {
2243
+ generator.addFile(".config/eslint.config.js", {
2244
+ type: "text",
2245
+ content: configContent
2246
+ });
2247
+ generator.addScript("lint", "eslint --config .config/eslint.config.js .");
2248
+ generator.addVscodeSetting("eslint.options", {
2249
+ overrideConfigFile: ".config/eslint.config.js"
2250
+ });
2251
+ } else {
2252
+ generator.addFile("eslint.config.js", {
2253
+ type: "text",
2254
+ content: configContent
2255
+ });
2256
+ generator.addScript("lint", "eslint .");
2257
+ }
2258
+ generator.inject(
2259
+ "readme-tools",
2260
+ "[ESLint](https://eslint.org/) - Linter for JavaScript and TypeScript"
2261
+ );
2262
+ generator.inject("vscode-extension-suggestion", "dbaeumer.vscode-eslint");
2263
+ generator.addVscodeSetting("eslint.enable", true);
2264
+ }
2265
+
2266
+ function generateFiber(generator, _options) {
2267
+ generator.inject("import", `import { Box } from "./box.js"`);
2268
+ generator.inject(
2269
+ "scene",
2270
+ [
2271
+ `<ambientLight intensity={Math.PI / 2} />`,
2272
+ `<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />`,
2273
+ `<pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />`,
2274
+ `<Box position={[-1.2, 0, 0]} />`,
2275
+ `<Box position={[1.2, 0, 0]} />`
2276
+ ].join("\n")
2277
+ );
2278
+ generator.addFile("src/box.tsx", {
2279
+ type: "text",
2280
+ content: `import type { Mesh } from 'three'
2281
+ import { useRef, useState } from 'react'
2282
+ import { useFrame, ThreeElements } from '@react-three/fiber'
2283
+
2284
+ export function Box(props: ThreeElements['mesh']) {
2285
+ const meshRef = useRef<Mesh>(null!)
2286
+ const [hovered, setHover] = useState(false)
2287
+ const [active, setActive] = useState(false)
2288
+ useFrame((state, delta) => (meshRef.current.rotation.x += delta))
2289
+ return (
2290
+ <mesh
2291
+ {...props}
2292
+ ref={meshRef}
2293
+ scale={active ? 1.5 : 1}
2294
+ onClick={(event) => setActive(!active)}
2295
+ onPointerOver={(event) => setHover(true)}
2296
+ onPointerOut={(event) => setHover(false)}>
2297
+ <boxGeometry args={[1, 1, 1]} />
2298
+ <meshStandardMaterial color={hovered ? 'hotpink' : '#2f74c0'} />
2299
+ </mesh>
2300
+ )
2301
+ }`
2302
+ });
2303
+ }
2304
+
2305
+ function generateGithubPages(generator, options) {
2306
+ if (options === false || getPackageManagerName(generator.options.packageManager) !== "npm") {
2307
+ return;
2308
+ }
2309
+ generator.addFile(".github/workflows/gh-pages.yml", {
2310
+ type: "text",
2311
+ content: `name: Deploy to Github Pages
2312
+
2313
+ on:
2314
+ push:
2315
+ branches:
2316
+ - main
2317
+
2318
+ jobs:
2319
+ build-and-deploy:
2320
+ runs-on: ubuntu-latest
2321
+ permissions:
2322
+ pages: write
2323
+ contents: read
2324
+ id-token: write
2325
+
2326
+ environment:
2327
+ name: github-pages
2328
+ url: \${{ steps.deployment.outputs.page_url }}
2329
+
2330
+ steps:
2331
+ - name: Checkout code
2332
+ uses: actions/checkout@v3
2333
+
2334
+ - name: Setup Node
2335
+ uses: actions/setup-node@v3
2336
+ with:
2337
+ node-version: 20
2338
+
2339
+ - name: Install dependencies
2340
+ run: npm install
2341
+
2342
+ - name: Build project
2343
+ run: npm run build
2344
+
2345
+ - name: Upload artifact
2346
+ uses: actions/upload-pages-artifact@v3
2347
+ with:
2348
+ path: './dist'
2349
+
2350
+ - name: Deploy to GitHub Pages
2351
+ id: deployment
2352
+ uses: actions/deploy-pages@v4
2353
+ `
2354
+ });
2355
+ generator.inject("readme-start", `A github pages deployment action is configurd.`);
2356
+ if (generator.options.githubUserName != null && generator.options.githubRepoName != null) {
2357
+ const address = `${generator.options.githubUserName}.github.io/${generator.options.githubRepoName}`;
2358
+ generator.inject(
2359
+ "readme-start",
2360
+ `Your app will be publish at [${address}](https://${address}) once the github action is finished.
2361
+ `
2362
+ );
2363
+ }
2364
+ }
2365
+
2366
+ function generateHandle(generator, options) {
2367
+ if (options == null) {
2368
+ return;
2369
+ }
2370
+ generator.addDependency("@react-three/handle");
2371
+ generator.inject(
2372
+ "readme-libraries",
2373
+ `[@react-three/handle](https://pmndrs.github.io/xr/docs/handles/introduction) - interactive controls and handles for your 3D objects`
2374
+ );
2375
+ }
2376
+
2377
+ function generateKoota(generator, options) {
2378
+ if (options == null) {
2379
+ return;
2380
+ }
2381
+ generator.addDependency("koota");
2382
+ generator.inject(
2383
+ "readme-libraries",
2384
+ `[koota](https://github.com/pmndrs/koota) - ECS-based state management library optimized for real-time apps, games, and XR experiences`
2385
+ );
2386
+ }
2387
+
2388
+ function generateLeva(generator, options) {
2389
+ if (options == null) {
2390
+ return;
2391
+ }
2392
+ generator.addDependency("leva");
2393
+ generator.inject(
2394
+ "readme-libraries",
2395
+ `[leva](https://github.com/pmndrs/leva) - HTML GUI panel for React with lightweight, beautiful and extensible controls`
2396
+ );
2397
+ }
2398
+
2399
+ function generateOffscreen(generator, options) {
2400
+ if (options == null) {
2401
+ return;
2402
+ }
2403
+ if (generator.options.xr != null) {
2404
+ console.info(
2405
+ color__default.blue("Info:"),
2406
+ "@react-three/offscreen is disabled because it is not supported with XR"
2407
+ );
2408
+ return;
2409
+ }
2410
+ generator.addDependency("@react-three/offscreen");
2411
+ generator.inject(
2412
+ "readme-libraries",
2413
+ `[@react-three/offscreen](https://github.com/pmndrs/offscreen) - Offload your scene to a worker thread for better performance`
2414
+ );
2415
+ }
2416
+
2417
+ function generateOxfmt(generator, options) {
2418
+ const isMonorepo = generator.options.workspaceRoot != null;
2419
+ if (isMonorepo) {
2420
+ generator.addDevDependency("@config/oxfmt", { version: "workspace:*" });
2421
+ const configPath = "node_modules/@config/oxfmt/base.json";
2422
+ generator.addScript("format", `oxfmt -c ${configPath} --write .`);
2423
+ generator.addVscodeSetting("oxc.fmt.configPath", configPath);
2424
+ } else {
2425
+ generator.addDevDependency("oxfmt");
2426
+ const isStealth = generator.isStealthConfig();
2427
+ if (isStealth) {
2428
+ generator.addFile(".config/oxfmt.json", {
2429
+ type: "text",
2430
+ content: JSON.stringify(defaultOxfmtConfig, null, 2)
2431
+ });
2432
+ generator.addScript("format", "oxfmt -c .config/oxfmt.json --write .");
2433
+ generator.addVscodeSetting("oxc.fmt.configPath", ".config/oxfmt.json");
2434
+ } else {
2435
+ generator.addFile("oxfmt.json", {
2436
+ type: "text",
2437
+ content: JSON.stringify(defaultOxfmtConfig, null, 2)
2438
+ });
2439
+ generator.addScript("format", "oxfmt -c oxfmt.json --write .");
2440
+ }
2441
+ }
2442
+ generator.inject(
2443
+ "readme-tools",
2444
+ "[Oxfmt](https://oxc.rs/docs/guide/usage/formatter) - Fast Prettier-compatible code formatter"
2445
+ );
2446
+ generator.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2447
+ generator.addVscodeSetting("editor.defaultFormatter", "oxc.oxc-vscode");
2448
+ generator.addVscodeSetting("[json]", {
2449
+ "editor.defaultFormatter": "vscode.json-language-features"
2450
+ });
2451
+ generator.addVscodeSetting("[jsonc]", {
2452
+ "editor.defaultFormatter": "vscode.json-language-features"
2453
+ });
2454
+ generator.addVscodeSetting("[markdown]", {
2455
+ "editor.defaultFormatter": "vscode.markdown-language-features"
2456
+ });
2457
+ generator.addVscodeSetting("[yaml]", {
2458
+ "editor.defaultFormatter": "redhat.vscode-yaml"
2459
+ });
2460
+ }
2461
+
2462
+ function toOxlintLevel(level) {
2463
+ return level;
2464
+ }
2465
+ function generateOxlint(generator, options) {
2466
+ const template = generator.options.template ?? "vanilla";
2467
+ const baseTemplate = getBaseTemplate(template);
2468
+ const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2469
+ const isMonorepo = generator.options.workspaceRoot != null;
2470
+ if (isMonorepo) {
2471
+ generator.addDevDependency("@config/oxlint", { version: "workspace:*" });
2472
+ const configPath = isReact ? "node_modules/@config/oxlint/react.json" : "node_modules/@config/oxlint/base.json";
2473
+ generator.addScript("lint", `oxlint -c ${configPath}`);
2474
+ generator.addVscodeSetting("oxc.configPath", configPath);
2475
+ } else {
2476
+ generator.addDevDependency("oxlint");
2477
+ const isStealth = generator.isStealthConfig();
2478
+ const { rules } = defaultLinterConfig;
2479
+ const plugins = ["unicorn", "typescript", "oxc"];
2480
+ if (isReact) {
2481
+ plugins.push("react");
2482
+ }
2483
+ const oxlintConfig = {
2484
+ $schema: isStealth ? "../node_modules/oxlint/configuration_schema.json" : "./node_modules/oxlint/configuration_schema.json",
2485
+ plugins,
2486
+ rules: {
2487
+ "no-unused-vars": [
2488
+ toOxlintLevel(rules.noUnusedVars.level),
2489
+ {
2490
+ argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
2491
+ varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
2492
+ caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
2493
+ }
2494
+ ],
2495
+ "no-useless-escape": "off",
2496
+ "no-unused-expressions": [
2497
+ toOxlintLevel(rules.noUnusedExpressions.level),
2498
+ { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
2499
+ ]
2500
+ },
2501
+ ignorePatterns: defaultLinterConfig.ignorePatterns
2502
+ };
2503
+ if (isStealth) {
2504
+ generator.addFile(".config/oxlint.json", {
2505
+ type: "text",
2506
+ content: JSON.stringify(oxlintConfig, null, 2)
2507
+ });
2508
+ generator.addScript("lint", "oxlint -c .config/oxlint.json");
2509
+ generator.addVscodeSetting("oxc.configPath", ".config/oxlint.json");
2510
+ } else {
2511
+ generator.addFile("oxlint.json", {
2512
+ type: "text",
2513
+ content: JSON.stringify(oxlintConfig, null, 2)
2514
+ });
2515
+ generator.addScript("lint", "oxlint");
2516
+ }
2517
+ }
2518
+ generator.inject(
2519
+ "readme-tools",
2520
+ "[Oxlint](https://oxc.rs/docs/guide/usage/linter) - A fast linter for JavaScript and TypeScript"
2521
+ );
2522
+ generator.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
2523
+ generator.addVscodeSetting("oxc.enable", true);
2524
+ }
2525
+
2526
+ function generatePostprocessing(generator, options) {
2527
+ if (options == null) {
2528
+ return;
2529
+ }
2530
+ if (generator.options.xr != null) {
2531
+ console.info(
2532
+ color__default.blue("Info:"),
2533
+ "@react-three/postprocessing is disabled because it is not supported with XR"
2534
+ );
2535
+ return;
2536
+ }
2537
+ generator.addDependency("@react-three/postprocessing");
2538
+ generator.inject(
2539
+ "readme-libraries",
2540
+ `[@react-three/postprocessing](https://react-postprocessing.docs.pmnd.rs/) - Post-processing effects for @react-three/fiber`
2541
+ );
2542
+ }
2543
+
2544
+ function generatePrettier(generator, options) {
2545
+ generator.addDevDependency("prettier");
2546
+ const isStealth = generator.isStealthConfig();
2547
+ if (isStealth) {
2548
+ generator.addFile(".config/prettier.json", {
2549
+ type: "text",
2550
+ content: JSON.stringify(defaultPrettierConfig, null, 2)
2551
+ });
2552
+ generator.addScript("format", "prettier --config .config/prettier.json --write .");
2553
+ generator.addVscodeSetting("prettier.configPath", ".config/prettier.json");
2554
+ } else {
2555
+ generator.addFile(".prettierrc", {
2556
+ type: "text",
2557
+ content: JSON.stringify(defaultPrettierConfig, null, 2)
2558
+ });
2559
+ generator.addScript("format", "prettier --write .");
2560
+ }
2561
+ generator.inject("readme-tools", "[Prettier](https://prettier.io/) - Opinionated code formatter");
2562
+ generator.inject("vscode-extension-suggestion", "esbenp.prettier-vscode");
2563
+ generator.addVscodeSetting("editor.defaultFormatter", "esbenp.prettier-vscode");
2564
+ }
2565
+
2566
+ function generateRapier(generator, options) {
2567
+ if (options == null) {
2568
+ return;
2569
+ }
2570
+ generator.addDependency("@react-three/rapier");
2571
+ generator.inject(
2572
+ "readme-libraries",
2573
+ `[@react-three/rapier](https://github.com/pmndrs/react-three-rapier) - Physics based on Rapier for your @react-three/fiber scene`
2574
+ );
2575
+ }
2576
+
2577
+ function unique(...array) {
2578
+ const set = /* @__PURE__ */ new Set();
2579
+ for (const arr of array) {
2580
+ for (const item of arr) {
2581
+ set.add(item);
2582
+ }
2583
+ }
2584
+ return Array.from(set);
2585
+ }
2586
+
2587
+ function generateProvidersModule(generator) {
2588
+ const canvasProviders = [];
2589
+ const globalProviders = [];
2590
+ const providerDefs = {
2591
+ uikit: {
2592
+ type: "layout-effect",
2593
+ props: [
2594
+ {
2595
+ declaredPropDefaultValue: '"light"',
2596
+ declaredPropName: "colorMode",
2597
+ propName: "colorMode",
2598
+ propValue: "colorMode",
2599
+ declaredPropType: '"light" | "dark"'
2600
+ }
2601
+ ],
2602
+ code: `
2603
+ setPreferredColorScheme(colorMode);
2604
+ `,
2605
+ import: 'import { setPreferredColorScheme } from "@react-three/uikit"'
2606
+ },
2607
+ rapier: {
2608
+ component: "Physics",
2609
+ type: "wrapped-jsx",
2610
+ import: 'import { Physics } from "@react-three/rapier";',
2611
+ props: [
2612
+ {
2613
+ declaredPropDefaultValue: false,
2614
+ declaredPropName: "physicsEnabled",
2615
+ propName: "paused",
2616
+ propValue: "!physicsEnabled",
2617
+ declaredPropType: "boolean"
2618
+ },
2619
+ {
2620
+ declaredPropDefaultValue: true,
2621
+ declaredPropName: "debugPhysics",
2622
+ propName: "debug",
2623
+ propValue: "debugPhysics",
2624
+ declaredPropType: "boolean"
2625
+ }
2626
+ ]
2627
+ },
2628
+ postprocessing: {
2629
+ type: "inline-jsx",
2630
+ code: `
2631
+ <EffectComposer enabled={postProcessingEnabled}>
2632
+ <DepthOfField
2633
+ focusDistance={0}
2634
+ focalLength={0.02}
2635
+ bokehScale={2}
2636
+ height={480}
2637
+ />
2638
+ <Bloom luminanceThreshold={0} luminanceSmoothing={0.9} height={300} />
2639
+ </EffectComposer>
2640
+ `,
2641
+ import: 'import { Bloom, DepthOfField, EffectComposer } from "@react-three/postprocessing";',
2642
+ props: [
2643
+ {
2644
+ declaredPropDefaultValue: true,
2645
+ declaredPropName: "postProcessingEnabled",
2646
+ propName: "enabled",
2647
+ propValue: "postProcessingEnabled",
2648
+ declaredPropType: "boolean"
2649
+ }
2650
+ ]
2651
+ }
2652
+ };
2653
+ if (generator.options.rapier) {
2654
+ canvasProviders.push("rapier");
2655
+ }
2656
+ if (!!generator.options.postprocessing && !generator.options.xr) {
2657
+ canvasProviders.push("postprocessing");
2658
+ }
2659
+ if (generator.options.uikit) {
2660
+ globalProviders.push("uikit");
2661
+ }
2662
+ function generateProviderFunction(name, { jsdoc, providers }) {
2663
+ const resolvedProviders = providers.map((provider) => providerDefs[provider]);
2664
+ const providerProps = resolvedProviders.flatMap((provider) => provider.props || []);
2665
+ const providerImports = resolvedProviders.flatMap((provider) => provider.import);
2666
+ const wrappedComponents = resolvedProviders.filter(
2667
+ (provider) => provider.type === "wrapped-jsx"
2668
+ );
2669
+ const inlineComponents = resolvedProviders.filter(
2670
+ (provider) => provider.type === "inline-jsx"
2671
+ );
2672
+ const layoutEffects = resolvedProviders.filter(
2673
+ (provider) => provider.type === "layout-effect"
2674
+ );
2675
+ const declaredProps = providerProps.map((prop) => `${prop.declaredPropName} = ${prop.declaredPropDefaultValue}`).join(", ");
2676
+ const declaredTypes = providerProps.map((prop) => `${prop.declaredPropName}?: ${prop.declaredPropType}`).join("; ");
2677
+ const reactImports = ["type ReactNode"];
2678
+ if (layoutEffects.length) {
2679
+ reactImports.push("useLayoutEffect");
2680
+ }
2681
+ return {
2682
+ reactImports,
2683
+ imports: providerImports,
2684
+ code: `
2685
+ /**
2686
+ ${jsdoc.split("\n").map((line) => ` * ${line}`).join("\n")}
2687
+ */
2688
+ export function ${name}({ children, ${declaredProps} }: { children: ReactNode; ${declaredTypes} }) {
2689
+ ${layoutEffects.length ? `
2690
+ useLayoutEffect(() => {
2691
+ ${layoutEffects.map((effect) => effect.code).join("\n")}
2692
+ }, [${layoutEffects.map((effect) => effect.props?.[0]?.propValue)}]);
2693
+ ` : ""}
2694
+ return (
2695
+ <>
2696
+ ${inlineComponents.map((provider) => provider.code)}
2697
+ ${wrappedComponents.reduce((acc, provider) => {
2698
+ const props = provider.props?.map((prop) => `${prop.propName}={${prop.propValue}}`).join(" ");
2699
+ return `<${provider.component} ${props}>${acc}</${provider.component}>`;
2700
+ }, "{children}")}
2701
+ </>
2702
+ );
2703
+ }`
2704
+ };
2705
+ }
2706
+ const global = generateProviderFunction("GlobalProvider", {
2707
+ jsdoc: "The global provider is rendered at the root of your application,\nuse it to set up global configuration like themes.\nProps defined on this component appear as controls inside Triplex.\n\nSee: https://triplex.dev/docs/building-your-scene/providers#global-provider",
2708
+ providers: globalProviders
2709
+ });
2710
+ const canvas = generateProviderFunction("CanvasProvider", {
2711
+ jsdoc: "The canvas provider is rendered as a child inside the React Three Fiber canvas,\nuse it to set up canvas specific configuration like post-processing and physics.\nProps defined on this component appear as controls inside Triplex.\n\nSee: https://triplex.dev/docs/building-your-scene/providers#canvas-provider",
2712
+ providers: canvasProviders
2713
+ });
2714
+ return `
2715
+ import { ${unique(global.reactImports, canvas.reactImports).sort().join(", ")} } from "react";
2716
+ ${unique(global.imports, canvas.imports).sort().join("\n")}
2717
+
2718
+ ${global.code}
2719
+ ${canvas.code}
2720
+ `;
2721
+ }
2722
+ function generateTriplex(generator, options) {
2723
+ if (options == null) {
2724
+ return;
2725
+ }
2726
+ generator.inject("vscode-extension-suggestion", "trytriplex.triplex-vsce");
2727
+ generator.inject(
2728
+ "readme-tools",
2729
+ `[Triplex](https://triplex.dev) - Your visual workspace for React / Three Fiber. Get started by installing [Triplex for VS Code](https://triplex.dev/docs/get-started/vscode). Don't use Visual Studio Code? Download [Triplex Standalone](https://triplex.dev/docs/get-started/standalone).`
2730
+ );
2731
+ generator.addFile(".triplex/providers.tsx", {
2732
+ content: generateProvidersModule(generator),
2733
+ type: "text"
2734
+ });
2735
+ generator.addFile(".triplex/config.json", {
2736
+ content: JSON.stringify(
2737
+ {
2738
+ $schema: "https://triplex.dev/config.schema.json",
2739
+ provider: "./providers.tsx"
2740
+ },
2741
+ null,
2742
+ 2
2743
+ ),
2744
+ type: "text"
2745
+ });
2746
+ }
2747
+
2748
+ function generateTsdown(generator) {
2749
+ generator.addDevDependency("tsdown");
2750
+ const template = generator.options.template ?? "vanilla";
2751
+ const baseTemplate = getBaseTemplate(template);
2752
+ const language = getLanguageFromTemplate(template);
2753
+ const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2754
+ const ext = language === "typescript" ? "ts" : "js";
2755
+ const configLines = [
2756
+ `import { defineConfig } from "tsdown"`,
2757
+ ``,
2758
+ `export default defineConfig({`,
2759
+ ` entry: ["./src/index.${ext}${isReact ? "x" : ""}"],`,
2760
+ ` format: ["esm", "cjs"],`,
2761
+ ` dts: ${language === "typescript"},`,
2762
+ ` clean: true,`
2763
+ ];
2764
+ if (isReact) {
2765
+ configLines.push(` esbuild: {`);
2766
+ configLines.push(` jsx: "automatic",`);
2767
+ configLines.push(` },`);
2768
+ }
2769
+ configLines.push(`})`);
2770
+ generator.addFile(`tsdown.config.${ext}`, {
2771
+ type: "text",
2772
+ content: configLines.join("\n")
2773
+ });
2774
+ generator.addScript("build", "tsdown");
2775
+ generator.inject(
2776
+ "readme-libraries",
2777
+ "[tsdown](https://github.com/nicepkg/tsdown) - Fast TypeScript bundler powered by esbuild"
2778
+ );
2779
+ }
2780
+
2781
+ function generateUikit(generator, options) {
2782
+ if (options == null) {
2783
+ return;
2784
+ }
2785
+ generator.addDependency("@react-three/uikit");
2786
+ generator.inject(
2787
+ "readme-libraries",
2788
+ `[@react-three/uikit](https://pmndrs.github.io/uikit/docs/) - UI primitives for React Three Fiber`
2789
+ );
2790
+ }
2791
+
2792
+ function generateUnbuild(generator) {
2793
+ generator.addDevDependency("unbuild");
2794
+ const template = generator.options.template ?? "vanilla";
2795
+ const baseTemplate = getBaseTemplate(template);
2796
+ const language = getLanguageFromTemplate(template);
2797
+ const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2798
+ const ext = language === "typescript" ? "ts" : "js";
2799
+ const isMonorepo = generator.options.workspaceRoot != null;
2800
+ const buildConfigLines = [
2801
+ `import { defineBuildConfig } from "unbuild"`,
2802
+ ``,
2803
+ `export default defineBuildConfig({`,
2804
+ ` entries: ["./src/index"],`,
2805
+ ` declaration: ${language === "typescript"},`,
2806
+ ` clean: true,`,
2807
+ ` rollup: {`,
2808
+ ` emitCJS: true,`
2809
+ ];
2810
+ if (isReact) {
2811
+ buildConfigLines.push(` esbuild: {`);
2812
+ buildConfigLines.push(` jsx: "automatic",`);
2813
+ buildConfigLines.push(` },`);
2814
+ }
2815
+ buildConfigLines.push(` },`);
2816
+ buildConfigLines.push(`})`);
2817
+ const isStealth = generator.isStealthConfig() && !isMonorepo;
2818
+ if (isStealth) {
2819
+ generator.addFile(`.config/build.config.${ext}`, {
2820
+ type: "text",
2821
+ content: buildConfigLines.join("\n")
2822
+ });
2823
+ generator.addScript("build", `unbuild --config .config/build.config.${ext}`);
2824
+ } else {
2825
+ generator.addFile(`build.config.${ext}`, {
2826
+ type: "text",
2827
+ content: buildConfigLines.join("\n")
2828
+ });
2829
+ generator.addScript("build", "unbuild");
2830
+ }
2831
+ generator.inject(
2832
+ "readme-libraries",
2833
+ "[unbuild](https://github.com/unjs/unbuild) - Unified JavaScript build system"
2834
+ );
2835
+ }
2836
+
2837
+ function generateVitest(generator) {
2838
+ generator.addDevDependency("vitest");
2839
+ const template = generator.options.template ?? "vanilla";
2840
+ const baseTemplate = getBaseTemplate(template);
2841
+ const isReact = baseTemplate === "react" || baseTemplate === "r3f";
2842
+ if (isReact) {
2843
+ generator.addDevDependency("@testing-library/react");
2844
+ generator.addDevDependency("@testing-library/dom");
2845
+ generator.addDevDependency("jsdom");
2846
+ }
2847
+ if (isReact) {
2848
+ generator.configureVite({ test: { environment: "jsdom" } });
2849
+ }
2850
+ generator.addScript("test", "vitest");
2851
+ generator.inject(
2852
+ "readme-tools",
2853
+ "[Vitest](https://vitest.dev/) - Fast unit test framework powered by Vite"
2854
+ );
2855
+ }
2856
+
2857
+ function generateViverse(generator, options) {
2858
+ if (options == null || getPackageManagerName(generator.options.packageManager) !== "npm") {
2859
+ return;
2860
+ }
2861
+ generator.addFile(".github/workflows/viverse.yml", {
2862
+ type: "text",
2863
+ content: `name: Deploy to Viverse
2864
+
2865
+ on:
2866
+ push:
2867
+ branches:
2868
+ - main
2869
+ workflow_dispatch:
2870
+
2871
+ jobs:
2872
+ check-secrets:
2873
+ runs-on: ubuntu-latest
2874
+ outputs:
2875
+ secrets-available: \${{ steps.check.outputs.secrets-available }}
2876
+ steps:
2877
+ - id: check
2878
+ run: |
2879
+ if [[ -n "\${{ secrets.VIVERSE_EMAIL }}" && -n "\${{ secrets.VIVERSE_PASSWORD }}" ]]; then
2880
+ echo "secrets-available=true" >> $GITHUB_OUTPUT
2881
+ else
2882
+ echo "secrets-available=false" >> $GITHUB_OUTPUT
2883
+ fi
2884
+
2885
+ build-and-deploy:
2886
+ runs-on: ubuntu-latest
2887
+ needs: check-secrets
2888
+ # Only run if secrets are present
2889
+ if: needs.check-secrets.outputs.secrets-available == 'true'
2890
+ permissions:
2891
+ contents: read
2892
+
2893
+ steps:
2894
+ - name: Checkout code
2895
+ uses: actions/checkout@v3
2896
+
2897
+ - name: Setup Node
2898
+ uses: actions/setup-node@v3
2899
+ with:
2900
+ node-version: 22
2901
+
2902
+ - name: Install dependencies
2903
+ run: npm install
2904
+
2905
+ - name: Build project
2906
+ run: npm run build
2907
+
2908
+ - name: Viverse Login
2909
+ run: npx viverse-cli auth login -e \${{ secrets.VIVERSE_EMAIL }} -p \${{ secrets.VIVERSE_PASSWORD }}
2910
+
2911
+ - name: Deploy to Viverse
2912
+ run: npx viverse-cli app publish ./dist --auto-create-app --name ${generator.options.name}
2913
+
2914
+ `
2915
+ });
2916
+ generator.addDependency("@viverse/cli");
2917
+ generator.inject(
2918
+ "readme-start",
2919
+ `A GitHub CI/CD workflow for publishing to Viverse is configured.
2920
+
2921
+ To use publish to viverse via the CI/CD workflow:
2922
+ 1. Set \`VIVERSE_EMAIL\` and \`VIVERSE_PASSWORD\` secrets in your repository settings under \`Secrets and Variables\` > \`Actions\` > \`New repository secret\`
2923
+ 2. Manually trigger the "Deploy to Viverse" workflow or push to the main branch
2924
+
2925
+ **Manual CLI Upload:**
2926
+ You can also upload your project manually using the Viverse CLI:
2927
+ \`\`\`bash
2928
+ viverse-cli auth login -e <email> -p <password>
2929
+ npm run build
2930
+ viverse-cli app publish ./dist --auto-create-app --name ${generator.options.name}
2931
+ \`\`\`
2932
+ `
2933
+ );
2934
+ }
2935
+
2936
+ function generateXr(generator, options) {
2937
+ if (options == null || options === false) {
2938
+ return;
2939
+ }
2940
+ if (options === true) {
2941
+ options = {};
2942
+ }
2943
+ generator.addDependency("@react-three/xr");
2944
+ generator.addDependency("@vitejs/plugin-basic-ssl");
2945
+ generator.inject("import", "import { XR, createXRStore } from '@react-three/xr'");
2946
+ generator.inject(
2947
+ `global-start`,
2948
+ `const store = createXRStore(${JSON.stringify(options.storeOptions ?? {})})`
2949
+ );
2950
+ generator.inject("scene-start", "<XR store={store}>");
2951
+ generator.inject("scene-end", "</XR>");
2952
+ generator.inject("vite-config-import", "import basicSsl from '@vitejs/plugin-basic-ssl'");
2953
+ generator.configureVite({
2954
+ server: {
2955
+ host: true
2956
+ },
2957
+ plugins: ["$raw:basicSsl()"]
2958
+ });
2959
+ generator.inject(
2960
+ "dom-start",
2961
+ `<div style={{
2962
+ display: "flex",
2963
+ flexDirection: "row",
2964
+ gap: "1rem",
2965
+ position: 'absolute',
2966
+ zIndex: 10000,
2967
+ background: 'black',
2968
+ borderRadius: '0.5rem',
2969
+ border: 'none',
2970
+ fontWeight: 'bold',
2971
+ color: 'white',
2972
+ cursor: 'pointer',
2973
+ fontSize: '1.5rem',
2974
+ bottom: '1rem',
2975
+ left: '50%',
2976
+ boxShadow: '0px 0px 20px rgba(0,0,0,1)',
2977
+ transform: 'translate(-50%, 0)',
2978
+ }}><button
2979
+ style={{ cursor: "pointer", padding: '1rem 2rem', fontSize: "1rem", background: "none", color: "white", border: "none" }}
2980
+ onClick={() => store.enterAR()}
2981
+ >
2982
+ Enter AR
2983
+ </button>
2984
+ <button
2985
+ style={{ cursor: "pointer", padding: '1rem 2rem', fontSize: "1rem", background: "none", color: "white", border: "none" }}
2986
+ onClick={() => store.enterVR()}
2987
+ >
2988
+ Enter VR
2989
+ </button></div>`
2990
+ );
2991
+ generator.inject(
2992
+ "readme-libraries",
2993
+ `[@react-three/xr](https://pmndrs.github.io/xr/docs/) - VR/AR support for @react-three/fiber`
2994
+ );
2995
+ }
2996
+
2997
+ function generateZustand(generator, options) {
2998
+ if (options == null) {
2999
+ return;
3000
+ }
3001
+ generator.addDependency("zustand");
3002
+ generator.inject(
3003
+ "readme-libraries",
3004
+ `[zustand](https://zustand.docs.pmnd.rs/) - small, fast and scalable state-management solution`
3005
+ );
3006
+ }
3007
+
3008
+ function merge(target, modification) {
3009
+ if (modification == null) {
3010
+ throw new Error(`Cannot merge "${modification}" modification into target "${target}"`);
3011
+ }
3012
+ if (target == null) {
3013
+ return modification;
3014
+ }
3015
+ if (Array.isArray(target)) {
3016
+ if (!Array.isArray(modification)) {
3017
+ throw new Error(
3018
+ `Cannot merge non-array modification "${modification}" into array target "${target}"`
3019
+ );
3020
+ }
3021
+ return [...target, ...modification];
3022
+ }
3023
+ if (typeof target === "object") {
3024
+ if (typeof modification != "object") {
3025
+ throw new Error(
3026
+ `Cannot merge non-object modification "${modification}" into object target "${target}"`
3027
+ );
3028
+ }
3029
+ const result = { ...target };
3030
+ for (const modificationKey in modification) {
3031
+ result[modificationKey] = merge(target[modificationKey], modification[modificationKey]);
3032
+ }
3033
+ return result;
3034
+ }
3035
+ console.warn(`target "${target}" is overwritten with modification "${modification}"`);
3036
+ return modification;
3037
+ }
3038
+
3039
+ function generate(options) {
3040
+ const clonedOptions = structuredClone(options);
3041
+ const template = clonedOptions.template ?? "vanilla";
3042
+ const baseTemplate = getBaseTemplate(template);
3043
+ const language = getLanguageFromTemplate(template);
3044
+ const isVanilla = baseTemplate === "vanilla";
3045
+ const isReact = baseTemplate === "react";
3046
+ const isR3f = baseTemplate === "r3f";
3047
+ const isLibrary = clonedOptions.projectType === "library";
3048
+ const libraryBundler = clonedOptions.libraryBundler ?? "unbuild";
3049
+ const files = {
3050
+ ...clonedOptions.files
3051
+ };
3052
+ const replacements = clonedOptions.replacements ?? [];
3053
+ const versions = clonedOptions.versions ?? {};
3054
+ const dependencies = {
3055
+ ...clonedOptions.dependencies
3056
+ };
3057
+ const devDependencies = {};
3058
+ const peerDependencies = {};
3059
+ if (!isLibrary) {
3060
+ assignResolvedPackageVersion(devDependencies, versions, "vite");
3061
+ }
3062
+ if (isReact || isR3f) {
3063
+ if (isLibrary) {
3064
+ peerDependencies["react"] = "^18.0.0 || ^19.0.0";
3065
+ peerDependencies["react-dom"] = "^18.0.0 || ^19.0.0";
3066
+ } else {
3067
+ assignResolvedPackageVersion(dependencies, versions, "react");
3068
+ assignResolvedPackageVersion(dependencies, versions, "react-dom");
3069
+ assignResolvedPackageVersion(devDependencies, versions, "@vitejs/plugin-react");
3070
+ }
3071
+ }
3072
+ if (isR3f) {
3073
+ if (isLibrary) {
3074
+ peerDependencies["three"] = ">=0.150.0";
3075
+ peerDependencies["@react-three/fiber"] = "^8.0.0 || ^9.0.0";
3076
+ } else {
3077
+ assignResolvedPackageVersion(dependencies, versions, "three", "~");
3078
+ assignResolvedPackageVersion(dependencies, versions, "@react-three/fiber");
3079
+ }
3080
+ }
3081
+ if (language === "typescript") {
3082
+ const tsResult = generateTypescriptConfig({
3083
+ baseTemplate,
3084
+ useConfigPackage: clonedOptions.workspaceRoot != null,
3085
+ configStrategy: clonedOptions.configStrategy,
3086
+ engine: clonedOptions.engine,
3087
+ versions
3088
+ });
3089
+ Object.assign(files, tsResult.files);
3090
+ Object.assign(devDependencies, tsResult.devDependencies);
3091
+ }
3092
+ const codeSnippets = {};
3093
+ const vscodeSettings = {};
3094
+ const scripts = isLibrary ? {} : {
3095
+ dev: "vite",
3096
+ build: "vite build"
3097
+ };
3098
+ if (!isLibrary && (isReact || isR3f)) {
3099
+ codeSnippets["vite-config-import"] = ["import react from '@vitejs/plugin-react'"];
3100
+ }
3101
+ if (!isLibrary && isR3f) {
3102
+ codeSnippets["import"] = [`import { Canvas } from "@react-three/fiber"`];
3103
+ }
3104
+ const defaultName = isVanilla ? "vanilla-app" : isReact ? "react-app" : "react-three-app";
3105
+ const name = clonedOptions.name ?? defaultName;
3106
+ let viteConfig = {
3107
+ base: "./"
3108
+ };
3109
+ if (!isLibrary && (isReact || isR3f)) {
3110
+ viteConfig.plugins = ["$raw:react()"];
3111
+ }
3112
+ if (!isLibrary && isR3f) {
3113
+ viteConfig.resolve = { dedupe: ["three"] };
3114
+ }
3115
+ const isMonorepoPackage = clonedOptions.workspaceRoot != null;
3116
+ const generator = {
3117
+ options: clonedOptions,
3118
+ versions,
3119
+ getVersion(name2) {
3120
+ return getResolvedPackageVersion(versions, name2);
3121
+ },
3122
+ isStealthConfig() {
3123
+ return (clonedOptions.configStrategy ?? "stealth") === "stealth";
3124
+ },
3125
+ addDependency(name2, options2) {
3126
+ if (dependencies[name2] != null) {
3127
+ return;
3128
+ }
3129
+ dependencies[name2] = resolveDependencySemver(name2, versions, options2);
3130
+ },
3131
+ addDevDependency(name2, options2) {
3132
+ if (devDependencies[name2] != null) {
3133
+ return;
3134
+ }
3135
+ devDependencies[name2] = resolveDependencySemver(name2, versions, options2);
3136
+ },
3137
+ addPeerDependency(name2, semver) {
3138
+ if (peerDependencies[name2] != null) {
3139
+ return;
3140
+ }
3141
+ peerDependencies[name2] = semver;
3142
+ },
3143
+ addFile(path, content) {
3144
+ files[path] = content;
3145
+ },
3146
+ addScript(name2, command) {
3147
+ scripts[name2] = command;
3148
+ },
3149
+ inject(location, code) {
3150
+ let entries = codeSnippets[location];
3151
+ if (entries == null) {
3152
+ codeSnippets[location] = entries = [];
3153
+ }
3154
+ entries.push(code);
3155
+ },
3156
+ replace(search, replace) {
3157
+ replacements.push({ search, replace });
3158
+ },
3159
+ configureVite(config) {
3160
+ viteConfig = merge(viteConfig, config);
3161
+ },
3162
+ addVscodeSetting(key, value) {
3163
+ vscodeSettings[key] = value;
3164
+ }
3165
+ };
3166
+ if (isR3f) {
3167
+ generateDrei(generator, clonedOptions.drei);
3168
+ generateHandle(generator, clonedOptions.handle);
3169
+ generateKoota(generator, clonedOptions.koota);
3170
+ generateLeva(generator, clonedOptions.leva);
3171
+ generateOffscreen(generator, clonedOptions.offscreen);
3172
+ generatePostprocessing(generator, clonedOptions.postprocessing);
3173
+ generateRapier(generator, clonedOptions.rapier);
3174
+ generateUikit(generator, clonedOptions.uikit);
3175
+ generateXr(generator, clonedOptions.xr);
3176
+ generateZustand(generator, clonedOptions.zustand);
3177
+ generateFiber(generator, clonedOptions.fiber);
3178
+ generateTriplex(generator, clonedOptions.triplex);
3179
+ generateViverse(generator, clonedOptions.viverse);
3180
+ }
3181
+ if (!isLibrary) {
3182
+ generateGithubPages(generator, clonedOptions.githubPages);
3183
+ }
3184
+ if (isLibrary) {
3185
+ if (libraryBundler === "unbuild") {
3186
+ generateUnbuild(generator);
3187
+ } else if (libraryBundler === "tsdown") {
3188
+ generateTsdown(generator);
3189
+ }
3190
+ const packageManager2 = getPackageManagerName(clonedOptions.packageManager);
3191
+ generator.addScript("release", `${packageManager2} run build && ${packageManager2} publish`);
3192
+ }
3193
+ const testing = clonedOptions.testing ?? (isLibrary ? "vitest" : "none");
3194
+ if (testing === "vitest") {
3195
+ generateVitest(generator);
3196
+ }
3197
+ const linter = clonedOptions.linter;
3198
+ const formatter = clonedOptions.formatter;
3199
+ if (linter === "eslint") {
3200
+ generateEslint(generator);
3201
+ generator.addVscodeSetting("biome.enabled", false);
3202
+ generator.addVscodeSetting("oxc.enable", false);
3203
+ } else if (linter === "oxlint") {
3204
+ generateOxlint(generator);
3205
+ generator.addVscodeSetting("eslint.enable", false);
3206
+ generator.addVscodeSetting("biome.enabled", false);
3207
+ } else if (linter === "biome") {
3208
+ generateBiome(generator, {
3209
+ linter: true,
3210
+ formatter: formatter === "biome"
3211
+ });
3212
+ generator.addVscodeSetting("eslint.enable", false);
3213
+ generator.addVscodeSetting("oxc.enable", false);
3214
+ }
3215
+ if (formatter === "prettier") {
3216
+ generatePrettier(generator);
3217
+ } else if (formatter === "oxfmt") {
3218
+ generateOxfmt(generator);
3219
+ } else if (formatter === "biome" && linter !== "biome") {
3220
+ generateBiome(generator, { linter: false, formatter: true });
3221
+ generator.addVscodeSetting("eslint.enable", false);
3222
+ generator.addVscodeSetting("oxc.enable", false);
3223
+ }
3224
+ for (const { code, location } of clonedOptions.injections ?? []) {
3225
+ generator.inject(location, code);
3226
+ }
3227
+ if (!isLibrary) {
3228
+ files["vite.config.ts"] = generateViteConfig({ viteConfig, codeSnippets });
3229
+ }
3230
+ const packageManager = getPackageManagerName(options.packageManager);
3231
+ files["README.md"] = generateReadme({
3232
+ name,
3233
+ baseTemplate,
3234
+ isLibrary,
3235
+ libraryBundler,
3236
+ packageManager,
3237
+ codeSnippets
3238
+ });
3239
+ Object.assign(
3240
+ files,
3241
+ generateSourceFiles({
3242
+ name,
3243
+ baseTemplate,
3244
+ language,
3245
+ isLibrary,
3246
+ codeSnippets,
3247
+ replacements
3248
+ })
3249
+ );
3250
+ if (testing === "vitest") {
3251
+ Object.assign(
3252
+ files,
3253
+ generateTestFiles({
3254
+ baseTemplate,
3255
+ language,
3256
+ isLibrary
3257
+ })
3258
+ );
3259
+ }
3260
+ Object.assign(
3261
+ files,
3262
+ generatePackageJson({
3263
+ name,
3264
+ language,
3265
+ isLibrary,
3266
+ dependencies,
3267
+ devDependencies,
3268
+ peerDependencies,
3269
+ scripts,
3270
+ options: clonedOptions,
3271
+ workspaceDependencies: clonedOptions.workspaceDependencies
3272
+ }).files
3273
+ );
3274
+ if (!isMonorepoPackage) {
3275
+ Object.assign(files, generateVscodeFiles$1({ codeSnippets, vscodeSettings }));
3276
+ }
3277
+ if (!isMonorepoPackage) {
3278
+ files[".gitignore"] = generateGitignore("standalone");
3279
+ files[".gitattributes"] = { type: "text", content: GitAttributes };
3280
+ }
3281
+ if (!isMonorepoPackage && clonedOptions.aiPlatforms?.length) {
3282
+ generateAiFiles(files, {
3283
+ name,
3284
+ packageManager: getPackageManagerName(clonedOptions.packageManager),
3285
+ linter: clonedOptions.linter ?? "oxlint",
3286
+ formatter: clonedOptions.formatter ?? "prettier",
3287
+ isMonorepo: false,
3288
+ configStrategy: clonedOptions.configStrategy,
3289
+ platforms: clonedOptions.aiPlatforms
3290
+ });
3291
+ }
3292
+ return files;
3293
+ }
3294
+ function resolveDependencySemver(name, versions, options = {}) {
3295
+ if (options.version != null) {
3296
+ return options.version;
3297
+ }
3298
+ return formatResolvedPackageVersion(versions, name, options.prefix);
3299
+ }
3300
+
3301
+ exports.AI_PLATFORM_HINTS = AI_PLATFORM_HINTS;
3302
+ exports.AI_PLATFORM_LABELS = AI_PLATFORM_LABELS;
3303
+ exports.ALL_AI_PLATFORMS = ALL_AI_PLATFORMS;
3304
+ exports.detectTooling = detectTooling;
3305
+ exports.formatResolvedPackageVersion = formatResolvedPackageVersion;
3306
+ exports.generate = generate;
3307
+ exports.generateAiFiles = generateAiFiles;
3308
+ exports.generateEslintConfigPackage = generateEslintConfigPackage;
3309
+ exports.generateGitignore = generateGitignore;
3310
+ exports.generateMonorepo = generateMonorepo;
3311
+ exports.generateOxfmtConfigPackage = generateOxfmtConfigPackage;
3312
+ exports.generateOxlintConfigPackage = generateOxlintConfigPackage;
3313
+ exports.generatePrettierConfigPackage = generatePrettierConfigPackage;
3314
+ exports.generateRandomName = generateRandomName;
3315
+ exports.generateTypescriptConfigPackage = generateTypescriptConfigPackage;
3316
+ exports.generateVscodeFiles = generateVscodeFiles;
3317
+ exports.getBaseTemplate = getBaseTemplate;
3318
+ exports.getEngineName = getEngineName;
3319
+ exports.getLanguageFromTemplate = getLanguageFromTemplate;
3320
+ exports.getLatestNodeVersion = getLatestNodeVersion;
3321
+ exports.getLatestNpmCliVersion = getLatestNpmCliVersion;
3322
+ exports.getLatestNpmMajorVersion = getLatestNpmMajorVersion;
3323
+ exports.getLatestNpmVersion = getLatestNpmVersion;
3324
+ exports.getLatestPnpmVersion = getLatestPnpmVersion;
3325
+ exports.getLatestYarnVersion = getLatestYarnVersion;
3326
+ exports.getPackageManagerName = getPackageManagerName;
3327
+ exports.getResolvedPackageVersion = getResolvedPackageVersion;
3328
+ exports.monorepo = monorepo;
3329
+ exports.parseEngine = parseEngine;
3330
+ exports.parsePackageManager = parsePackageManager;
3331
+ exports.parseWorkspaceYamlContent = parseWorkspaceYamlContent;
3332
+ exports.resolveEngine = resolveEngine;
3333
+ exports.resolveMonorepoRootPackageVersions = resolveMonorepoRootPackageVersions;
3334
+ exports.resolvePackageManager = resolvePackageManager;
3335
+ exports.resolveProjectPackageVersions = resolveProjectPackageVersions;
3336
+ exports.validatePackageName = validatePackageName;