create-krispya 0.1.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.
Files changed (52) hide show
  1. package/LICENSE +15 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +497 -0
  4. package/dist/constants.d.ts +33 -0
  5. package/dist/constants.js +110 -0
  6. package/dist/index.d.ts +86 -0
  7. package/dist/index.js +394 -0
  8. package/dist/integrations/biome.d.ts +8 -0
  9. package/dist/integrations/biome.js +80 -0
  10. package/dist/integrations/drei.d.ts +3 -0
  11. package/dist/integrations/drei.js +9 -0
  12. package/dist/integrations/eslint.d.ts +3 -0
  13. package/dist/integrations/eslint.js +78 -0
  14. package/dist/integrations/fiber.d.ts +8 -0
  15. package/dist/integrations/fiber.js +35 -0
  16. package/dist/integrations/github-pages.d.ts +3 -0
  17. package/dist/integrations/github-pages.js +58 -0
  18. package/dist/integrations/handle.d.ts +3 -0
  19. package/dist/integrations/handle.js +7 -0
  20. package/dist/integrations/koota.d.ts +8 -0
  21. package/dist/integrations/koota.js +7 -0
  22. package/dist/integrations/leva.d.ts +3 -0
  23. package/dist/integrations/leva.js +7 -0
  24. package/dist/integrations/offscreen.d.ts +3 -0
  25. package/dist/integrations/offscreen.js +12 -0
  26. package/dist/integrations/oxfmt.d.ts +3 -0
  27. package/dist/integrations/oxfmt.js +27 -0
  28. package/dist/integrations/oxlint.d.ts +3 -0
  29. package/dist/integrations/oxlint.js +52 -0
  30. package/dist/integrations/postprocessing.d.ts +3 -0
  31. package/dist/integrations/postprocessing.js +12 -0
  32. package/dist/integrations/prettier.d.ts +3 -0
  33. package/dist/integrations/prettier.js +28 -0
  34. package/dist/integrations/rapier.d.ts +3 -0
  35. package/dist/integrations/rapier.js +7 -0
  36. package/dist/integrations/triplex.d.ts +26 -0
  37. package/dist/integrations/triplex.js +159 -0
  38. package/dist/integrations/uikit.d.ts +3 -0
  39. package/dist/integrations/uikit.js +7 -0
  40. package/dist/integrations/viverse.d.ts +3 -0
  41. package/dist/integrations/viverse.js +74 -0
  42. package/dist/integrations/xr.d.ts +5 -0
  43. package/dist/integrations/xr.js +51 -0
  44. package/dist/integrations/zustand.d.ts +8 -0
  45. package/dist/integrations/zustand.js +7 -0
  46. package/dist/lib/array.d.ts +1 -0
  47. package/dist/lib/array.js +9 -0
  48. package/dist/merge.d.ts +1 -0
  49. package/dist/merge.js +26 -0
  50. package/dist/utils.d.ts +22 -0
  51. package/dist/utils.js +123 -0
  52. package/package.json +38 -0
@@ -0,0 +1,86 @@
1
+ import { GenerateDreiOptions } from "./integrations/drei.js";
2
+ import { GenerateFiberOptions } from "./integrations/fiber.js";
3
+ import { GenerateGithubPagesOptions } from "./integrations/github-pages.js";
4
+ import { GenerateHandleOptions } from "./integrations/handle.js";
5
+ import { GenerateKootaOptions } from "./integrations/koota.js";
6
+ import { GenerateLevaOptions } from "./integrations/leva.js";
7
+ import { GenerateOffscreenOptions } from "./integrations/offscreen.js";
8
+ import { GeneratePostprocessingOptions } from "./integrations/postprocessing.js";
9
+ import { GenerateRapierOptions } from "./integrations/rapier.js";
10
+ import { GenerateUikitOptions } from "./integrations/uikit.js";
11
+ import { GenerateXrOptions } from "./integrations/xr.js";
12
+ import { GenerateZustandOptions } from "./integrations/zustand.js";
13
+ import { GenerateTriplexOptions } from "./integrations/triplex.js";
14
+ import { GenerateViverseOptions } from "./integrations/viverse.js";
15
+ export * from "./utils.js";
16
+ export type Template = "vanilla" | "vanilla-js" | "react" | "react-js" | "r3f" | "r3f-js";
17
+ export type BaseTemplate = "vanilla" | "react" | "r3f";
18
+ export declare function getLanguageFromTemplate(template: Template): "javascript" | "typescript";
19
+ export declare function getBaseTemplate(template: Template): BaseTemplate;
20
+ export type PackageVersions = {
21
+ vite?: string;
22
+ eslint?: string;
23
+ oxlint?: string;
24
+ oxfmt?: string;
25
+ prettier?: string;
26
+ biome?: string;
27
+ };
28
+ export type GenerateOptions = {
29
+ githubUserName?: string;
30
+ githubRepoName?: string;
31
+ name: string;
32
+ template?: Template;
33
+ linter?: Linter;
34
+ formatter?: Formatter;
35
+ versions?: PackageVersions;
36
+ fiber?: GenerateFiberOptions;
37
+ handle?: GenerateHandleOptions;
38
+ drei?: GenerateDreiOptions;
39
+ koota?: GenerateKootaOptions;
40
+ leva?: GenerateLevaOptions;
41
+ offscreen?: GenerateOffscreenOptions;
42
+ postprocessing?: GeneratePostprocessingOptions;
43
+ rapier?: GenerateRapierOptions;
44
+ triplex?: GenerateTriplexOptions;
45
+ viverse?: GenerateViverseOptions;
46
+ uikit?: GenerateUikitOptions;
47
+ xr?: GenerateXrOptions;
48
+ zustand?: GenerateZustandOptions;
49
+ githubPages?: GenerateGithubPagesOptions;
50
+ dependencies?: Record<string, string>;
51
+ files?: Record<string, File>;
52
+ injections?: Array<{
53
+ location: CodeInjectionLocation;
54
+ code: string;
55
+ }>;
56
+ replacements?: Array<{
57
+ search: string;
58
+ replace: string;
59
+ }>;
60
+ packageManager?: string;
61
+ pnpmVersion?: string;
62
+ pnpmManageVersions?: boolean;
63
+ nodeVersion?: string;
64
+ };
65
+ export type File = {
66
+ type: "text";
67
+ content: string;
68
+ } | {
69
+ type: "remote";
70
+ url: string;
71
+ };
72
+ export type Linter = "eslint" | "oxlint" | "biome";
73
+ export type Formatter = "prettier" | "oxfmt" | "biome";
74
+ export type CodeInjectionLocation = "vite-config-import" | "import" | "global-start" | "global-end" | "dom-start" | "dom" | "dom-end" | "scene-start" | "scene" | "scene-end" | "readme-start" | "readme-end" | "readme-libraries" | "readme-tools" | "readme-commands" | "vscode-extension-suggestion" | "vscode-setting";
75
+ export type Generator = {
76
+ get options(): GenerateOptions;
77
+ get versions(): PackageVersions;
78
+ addDependency(name: string, semver: string): void;
79
+ addFile(path: string, file: File): void;
80
+ addScript(name: string, command: string): void;
81
+ inject(location: CodeInjectionLocation, code: string): void;
82
+ replace(search: string, replace: string): void;
83
+ configureVite(object: any): void;
84
+ addVscodeSetting(key: string, value: unknown): void;
85
+ };
86
+ export declare function generate(options: GenerateOptions): Record<string, File>;
package/dist/index.js ADDED
@@ -0,0 +1,394 @@
1
+ import { GitAttributes, HtmlContent, IndexContent, ViteHtmlContent, ViteIndexContent, ViteStyleContent, } from "./constants.js";
2
+ import { generateBiome } from "./integrations/biome.js";
3
+ import { generateDrei } from "./integrations/drei.js";
4
+ import { generateEslint } from "./integrations/eslint.js";
5
+ import { generateFiber } from "./integrations/fiber.js";
6
+ import { generateGithubPages } from "./integrations/github-pages.js";
7
+ import { generateHandle } from "./integrations/handle.js";
8
+ import { generateKoota } from "./integrations/koota.js";
9
+ import { generateLeva } from "./integrations/leva.js";
10
+ import { generateOffscreen } from "./integrations/offscreen.js";
11
+ import { generateOxfmt } from "./integrations/oxfmt.js";
12
+ import { generateOxlint } from "./integrations/oxlint.js";
13
+ import { generatePostprocessing, } from "./integrations/postprocessing.js";
14
+ import { generatePrettier } from "./integrations/prettier.js";
15
+ import { generateRapier } from "./integrations/rapier.js";
16
+ import { generateUikit } from "./integrations/uikit.js";
17
+ import { generateXr } from "./integrations/xr.js";
18
+ import { generateZustand } from "./integrations/zustand.js";
19
+ import { generateTriplex } from "./integrations/triplex.js";
20
+ import { merge } from "./merge.js";
21
+ import { generateViverse } from "./integrations/viverse.js";
22
+ export * from "./utils.js";
23
+ export function getLanguageFromTemplate(template) {
24
+ return template.endsWith("-js") ? "javascript" : "typescript";
25
+ }
26
+ export function getBaseTemplate(template) {
27
+ return template.replace("-js", "");
28
+ }
29
+ export function generate(options) {
30
+ //deep cloning since integrations might decide to modify the options
31
+ const clonedOptions = structuredClone(options);
32
+ const template = clonedOptions.template ?? "vanilla";
33
+ const baseTemplate = getBaseTemplate(template);
34
+ const language = getLanguageFromTemplate(template);
35
+ const isVanilla = baseTemplate === "vanilla";
36
+ const isReact = baseTemplate === "react";
37
+ const isR3f = baseTemplate === "r3f";
38
+ const files = {
39
+ ...clonedOptions.files,
40
+ };
41
+ const replacements = clonedOptions.replacements ?? [];
42
+ // Base dependencies - always include vite
43
+ const versions = clonedOptions.versions ?? {};
44
+ const dependencies = {
45
+ vite: versions.vite ? `^${versions.vite}` : "^6.3.4",
46
+ ...clonedOptions.dependencies,
47
+ };
48
+ // Add React dependencies for react and r3f templates
49
+ if (isReact || isR3f) {
50
+ dependencies["react"] = "^19.0.0";
51
+ dependencies["react-dom"] = "^19.0.0";
52
+ dependencies["@vitejs/plugin-react"] = "^4.4.1";
53
+ }
54
+ // Add Three.js dependencies for r3f template
55
+ if (isR3f) {
56
+ dependencies["three"] = "~0.175.0";
57
+ dependencies["@react-three/fiber"] = "^9.0.0";
58
+ }
59
+ // TypeScript configuration
60
+ if (language === "typescript") {
61
+ const tsConfig = {
62
+ compilerOptions: {
63
+ target: "ESNext",
64
+ module: "ESNext",
65
+ moduleResolution: "bundler",
66
+ esModuleInterop: true,
67
+ strict: true,
68
+ skipLibCheck: true,
69
+ outDir: "dist",
70
+ },
71
+ include: ["src/**/*"],
72
+ };
73
+ // Add JSX config for React templates
74
+ if (isReact || isR3f) {
75
+ tsConfig.compilerOptions.jsx = "react-jsx";
76
+ dependencies["@types/react"] = "^19.0.0";
77
+ dependencies["@types/react-dom"] = "^19.0.0";
78
+ }
79
+ // Add Three.js types for r3f
80
+ if (isR3f) {
81
+ dependencies["@types/three"] = "~0.175.0";
82
+ }
83
+ files["tsconfig.json"] = {
84
+ type: "text",
85
+ content: JSON.stringify(tsConfig, null, 2),
86
+ };
87
+ }
88
+ const codeSnippets = {};
89
+ const vscodeSettings = {};
90
+ const scripts = {
91
+ dev: "vite",
92
+ build: "vite build",
93
+ };
94
+ // Setup vite config imports based on template
95
+ if (isReact || isR3f) {
96
+ codeSnippets["vite-config-import"] = ["import react from '@vitejs/plugin-react'"];
97
+ }
98
+ // Setup R3F-specific imports
99
+ if (isR3f) {
100
+ codeSnippets["import"] = [`import { Canvas } from "@react-three/fiber"`];
101
+ }
102
+ const defaultName = isVanilla ? "vanilla-app" : isReact ? "react-app" : "react-three-app";
103
+ const name = clonedOptions.name ?? defaultName;
104
+ // Build vite config based on template
105
+ let viteConfig = {
106
+ base: "./",
107
+ };
108
+ if (isReact || isR3f) {
109
+ viteConfig.plugins = ["$raw:react()"];
110
+ }
111
+ if (isR3f) {
112
+ viteConfig.resolve = { dedupe: ["three"] };
113
+ }
114
+ const generator = {
115
+ options: clonedOptions,
116
+ versions,
117
+ addDependency(name, semver) {
118
+ const existingSemver = dependencies[name];
119
+ if (existingSemver != null) {
120
+ //TODO: intersect existingSemver with semver and write to semver
121
+ //TODO: throw error if no overlap
122
+ }
123
+ dependencies[name] = semver;
124
+ },
125
+ addFile(path, content) {
126
+ files[path] = content;
127
+ },
128
+ addScript(name, command) {
129
+ scripts[name] = command;
130
+ },
131
+ inject(location, code) {
132
+ let entries = codeSnippets[location];
133
+ if (entries == null) {
134
+ codeSnippets[location] = entries = [];
135
+ }
136
+ entries.push(code);
137
+ },
138
+ replace(search, replace) {
139
+ replacements.push({ search, replace });
140
+ },
141
+ configureVite(config) {
142
+ viteConfig = merge(viteConfig, config);
143
+ },
144
+ addVscodeSetting(key, value) {
145
+ vscodeSettings[key] = value;
146
+ },
147
+ };
148
+ // Only run R3F integrations for r3f template
149
+ if (isR3f) {
150
+ generateDrei(generator, clonedOptions.drei);
151
+ generateHandle(generator, clonedOptions.handle);
152
+ generateKoota(generator, clonedOptions.koota);
153
+ generateLeva(generator, clonedOptions.leva);
154
+ generateOffscreen(generator, clonedOptions.offscreen);
155
+ generatePostprocessing(generator, clonedOptions.postprocessing);
156
+ generateRapier(generator, clonedOptions.rapier);
157
+ generateUikit(generator, clonedOptions.uikit);
158
+ generateXr(generator, clonedOptions.xr);
159
+ generateZustand(generator, clonedOptions.zustand);
160
+ generateFiber(generator, clonedOptions.fiber);
161
+ generateTriplex(generator, clonedOptions.triplex);
162
+ generateViverse(generator, clonedOptions.viverse);
163
+ }
164
+ // GitHub Pages works for all templates
165
+ generateGithubPages(generator, clonedOptions.githubPages);
166
+ // Linter and formatter integrations
167
+ const linter = clonedOptions.linter;
168
+ const formatter = clonedOptions.formatter;
169
+ // Generate linter integrations
170
+ if (linter === "eslint") {
171
+ generateEslint(generator, true);
172
+ generator.addVscodeSetting("biome.enabled", false);
173
+ generator.addVscodeSetting("oxc.enable", false);
174
+ }
175
+ else if (linter === "oxlint") {
176
+ generateOxlint(generator, true);
177
+ generator.addVscodeSetting("eslint.enable", false);
178
+ generator.addVscodeSetting("biome.enabled", false);
179
+ }
180
+ else if (linter === "biome") {
181
+ generateBiome(generator, { linter: true, formatter: formatter === "biome" });
182
+ generator.addVscodeSetting("eslint.enable", false);
183
+ generator.addVscodeSetting("oxc.enable", false);
184
+ }
185
+ // Generate formatter integrations (skip biome if already handled above)
186
+ if (formatter === "prettier") {
187
+ generatePrettier(generator, true);
188
+ }
189
+ else if (formatter === "oxfmt") {
190
+ generateOxfmt(generator, true);
191
+ }
192
+ else if (formatter === "biome" && linter !== "biome") {
193
+ // Only generate biome for formatting if it wasn't already generated for linting
194
+ generateBiome(generator, { linter: false, formatter: true });
195
+ generator.addVscodeSetting("eslint.enable", false);
196
+ generator.addVscodeSetting("oxc.enable", false);
197
+ }
198
+ for (const { code, location } of clonedOptions.injections ?? []) {
199
+ generator.inject(location, code);
200
+ }
201
+ // Generate vite.config.js
202
+ const viteConfigContent = [
203
+ `import { defineConfig } from 'vite'`,
204
+ ...(codeSnippets["vite-config-import"] ?? []),
205
+ `export default defineConfig(${JSON.stringify(viteConfig).replace(/"\$raw:([^"]+)"/g, (_, raw) => raw)})`,
206
+ ].join("\n");
207
+ files["vite.config.js"] = { type: "text", content: viteConfigContent };
208
+ const packageManager = options.packageManager ?? "pnpm";
209
+ const isPnpm = packageManager === "pnpm";
210
+ // Build package.json with conditional pnpm-specific fields
211
+ const packageJson = {
212
+ name,
213
+ type: "module",
214
+ dependencies,
215
+ scripts,
216
+ };
217
+ // Add engines field if needed
218
+ const engines = {};
219
+ if (isPnpm) {
220
+ const pnpmVersion = options.pnpmVersion ?? "10.11.0";
221
+ const majorVersion = pnpmVersion.split(".")[0];
222
+ engines.pnpm = `>=${majorVersion}.0.0`;
223
+ packageJson.packageManager = `pnpm@${pnpmVersion}`;
224
+ }
225
+ if (options.nodeVersion) {
226
+ const majorVersion = options.nodeVersion.split(".")[0];
227
+ engines.node = `>=${majorVersion}.0.0`;
228
+ }
229
+ if (Object.keys(engines).length > 0) {
230
+ packageJson.engines = engines;
231
+ }
232
+ files["package.json"] = {
233
+ type: "text",
234
+ content: JSON.stringify(packageJson, null, 2),
235
+ };
236
+ // Add pnpm-workspace.yaml when pnpm is selected
237
+ if (isPnpm) {
238
+ const manageVersions = options.pnpmManageVersions ?? true;
239
+ const workspaceLines = [];
240
+ if (manageVersions) {
241
+ workspaceLines.push("manage-package-manager-versions: true", "");
242
+ }
243
+ workspaceLines.push("onlyBuiltDependencies:", " - esbuild");
244
+ files["pnpm-workspace.yaml"] = {
245
+ type: "text",
246
+ content: workspaceLines.join("\n"),
247
+ };
248
+ }
249
+ files[".gitignore"] = { type: "text", content: ["node_modules", "dist"].join("\n") };
250
+ files[".gitattributes"] = { type: "text", content: GitAttributes };
251
+ codeSnippets["readme-libraries"] ??= [];
252
+ codeSnippets["readme-commands"] ??= [];
253
+ // Add library descriptions based on template
254
+ if (isVanilla) {
255
+ codeSnippets["readme-libraries"].unshift(`[Vite](https://vitejs.dev/) - Next generation frontend tooling`);
256
+ }
257
+ else if (isReact) {
258
+ codeSnippets["readme-libraries"].unshift(`[React](https://react.dev/) - A JavaScript library for building user interfaces`, `[Vite](https://vitejs.dev/) - Next generation frontend tooling`);
259
+ }
260
+ else {
261
+ codeSnippets["readme-libraries"].unshift(`[React](https://react.dev/) - A JavaScript library for building user interfaces`, `[Three.js](https://threejs.org/) - JavaScript 3D library`, `[@react-three/fiber](https://docs.pmnd.rs/react-three-fiber) - lets you create Three.js scenes using React components`);
262
+ }
263
+ codeSnippets["readme-commands"].unshift(`\`${packageManager} install\` to install the dependencies`, `\`${packageManager} run dev\` to run the development server and preview the app with live updates`, `\`${packageManager} run build\` to build the app into the \`dist\` folder`);
264
+ // Generate template-specific architecture description
265
+ const ext = language === "javascript" ? "js" : "ts";
266
+ const jsxExt = language === "javascript" ? "jsx" : "tsx";
267
+ let architectureDesc;
268
+ if (isVanilla) {
269
+ architectureDesc = [
270
+ `- \`src/main.${ext}\` is the entry point for your application`,
271
+ `- Static assets can be placed in the \`public\` folder`,
272
+ ];
273
+ }
274
+ else if (isReact) {
275
+ architectureDesc = [
276
+ `- \`src/app.${jsxExt}\` defines the main application component`,
277
+ `- \`src/index.${jsxExt}\` renders the React app into the DOM`,
278
+ `- Static assets can be placed in the \`public\` folder`,
279
+ ];
280
+ }
281
+ else {
282
+ architectureDesc = [
283
+ `- \`app.${jsxExt}\` defines the main application component containing your 3D content`,
284
+ `- Modify the content inside the \`<Canvas>\` component to change what is visible on screen`,
285
+ `- Static assets can be placed in the \`public\` folder`,
286
+ ];
287
+ }
288
+ files[`README.md`] = {
289
+ type: "text",
290
+ content: [
291
+ `# ${name}`,
292
+ `This project was generated with create-krispya`,
293
+ ...(codeSnippets["readme-start"] ?? []),
294
+ "\n",
295
+ `## Project Architecture`,
296
+ `This project uses [Vite](https://vitejs.dev/) as the bundler for fast development and optimized production builds.`,
297
+ ...architectureDesc,
298
+ "\n",
299
+ `## Libraries`,
300
+ `The following libraries are used - checkout the linked docs to learn more`,
301
+ ...(codeSnippets["readme-libraries"] ?? []).map((library) => `- ${library}`),
302
+ "\n",
303
+ codeSnippets["readme-tools"] && `## Tools`,
304
+ ...(codeSnippets["readme-tools"] ?? []).map((tool) => `- ${tool}`),
305
+ codeSnippets["readme-tools"] && `\n`,
306
+ `## Development Commands`,
307
+ ...(codeSnippets["readme-commands"] ?? []).map((command) => `- ${command}`),
308
+ ...(codeSnippets["readme-end"] ?? []),
309
+ ]
310
+ .filter(Boolean)
311
+ .join("\n"),
312
+ };
313
+ // Generate template-specific source files
314
+ if (isVanilla) {
315
+ // Vanilla template
316
+ const ext = language === "javascript" ? "js" : "ts";
317
+ files[`src/main.${ext}`] = { type: "text", content: ViteIndexContent };
318
+ files["src/style.css"] = { type: "text", content: ViteStyleContent };
319
+ const indexHtml = ViteHtmlContent.replace("$indexPath", `./src/main.${ext}`).replace("$title", name);
320
+ files["index.html"] = { type: "text", content: indexHtml };
321
+ }
322
+ else {
323
+ // React and R3F templates
324
+ files[`src/index.tsx`] = { type: "text", content: IndexContent };
325
+ const indexHtml = HtmlContent.replace("$indexPath", language === "javascript" ? "./src/index.jsx" : "./src/index.tsx").replace("$title", name);
326
+ files["index.html"] = { type: "text", content: indexHtml };
327
+ // Generate app.tsx
328
+ codeSnippets["dom-end"]?.reverse();
329
+ codeSnippets["global-end"]?.reverse();
330
+ codeSnippets["scene-end"]?.reverse();
331
+ let appCode;
332
+ if (isReact) {
333
+ // Simple React app without Canvas
334
+ appCode = [
335
+ ...(codeSnippets["import"] ?? []),
336
+ ...(codeSnippets["global-start"] ?? []),
337
+ `export function App() {`,
338
+ " return (",
339
+ ' <div style={{ padding: "2rem" }}>',
340
+ " <h1>Hello React!</h1>",
341
+ " <p>Edit src/app.tsx and save to see changes.</p>",
342
+ " </div>",
343
+ " )",
344
+ "}",
345
+ ...(codeSnippets["global-end"] ?? []),
346
+ ].join("\n");
347
+ }
348
+ else {
349
+ // R3F app with Canvas
350
+ appCode = [
351
+ ...(codeSnippets["import"] ?? []),
352
+ ...(codeSnippets["global-start"] ?? []),
353
+ `export function App() {`,
354
+ " return <>",
355
+ ...(codeSnippets["dom-start"] ?? []),
356
+ ...(codeSnippets["dom"] ?? []),
357
+ " <Canvas>",
358
+ ...(codeSnippets["scene-start"] ?? []),
359
+ ...(codeSnippets["scene"] ?? []),
360
+ ...(codeSnippets["scene-end"] ?? []),
361
+ " </Canvas>",
362
+ ...(codeSnippets["dom-end"] ?? []),
363
+ " </>",
364
+ "}",
365
+ ...(codeSnippets["global-end"] ?? []),
366
+ ].join("\n");
367
+ }
368
+ for (const { search, replace } of replacements) {
369
+ appCode = appCode.replace(search, replace);
370
+ }
371
+ files[`src/app.tsx`] = { type: "text", content: appCode };
372
+ }
373
+ if (codeSnippets["vscode-extension-suggestion"]?.length) {
374
+ // Deduplicate extension recommendations
375
+ const uniqueRecommendations = [...new Set(codeSnippets["vscode-extension-suggestion"])];
376
+ files[".vscode/extensions.json"] = {
377
+ type: "text",
378
+ content: JSON.stringify({
379
+ recommendations: uniqueRecommendations,
380
+ }, null, 2),
381
+ };
382
+ }
383
+ if (Object.keys(vscodeSettings).length > 0) {
384
+ files[".vscode/settings.json"] = {
385
+ type: "text",
386
+ content: JSON.stringify(vscodeSettings, null, "\t"),
387
+ };
388
+ }
389
+ if (language === "javascript") {
390
+ //TODO: transpile tsx? to jsx? files}
391
+ }
392
+ //TODO: execute prettier on ts(x), js(x), and json files``
393
+ return files;
394
+ }
@@ -0,0 +1,8 @@
1
+ import type { Generator } from "../index.js";
2
+ export type GenerateBiomeOptions = {
3
+ /** Whether biome is used as a linter */
4
+ linter?: boolean;
5
+ /** Whether biome is used as a formatter */
6
+ formatter?: boolean;
7
+ };
8
+ export declare function generateBiome(generator: Generator, options: GenerateBiomeOptions | undefined): void;
@@ -0,0 +1,80 @@
1
+ import { defaultFormatterConfig, defaultLinterConfig } from "../constants.js";
2
+ // Helper to convert level to Biome format
3
+ function toBiomeLevel(level) {
4
+ return level;
5
+ }
6
+ export function generateBiome(generator, options) {
7
+ if (options == null || (!options.linter && !options.formatter)) {
8
+ return;
9
+ }
10
+ const version = generator.versions.biome ?? "1.9.4";
11
+ generator.addDependency("@biomejs/biome", `^${version}`);
12
+ const { rules } = defaultLinterConfig;
13
+ // Build biome config based on roles
14
+ const biomeConfig = {
15
+ $schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
16
+ files: {
17
+ ignore: defaultLinterConfig.ignorePatterns,
18
+ },
19
+ };
20
+ if (options.linter) {
21
+ biomeConfig.linter = {
22
+ enabled: true,
23
+ rules: {
24
+ recommended: true,
25
+ correctness: {
26
+ noUnusedVariables: toBiomeLevel(rules.noUnusedVars.level),
27
+ },
28
+ },
29
+ };
30
+ }
31
+ else {
32
+ biomeConfig.linter = {
33
+ enabled: false,
34
+ };
35
+ }
36
+ if (options.formatter) {
37
+ // Translate common formatter settings to Biome format
38
+ biomeConfig.formatter = {
39
+ enabled: true,
40
+ lineWidth: defaultFormatterConfig.printWidth,
41
+ indentWidth: defaultFormatterConfig.tabWidth,
42
+ indentStyle: defaultFormatterConfig.useTabs ? "tab" : "space",
43
+ };
44
+ biomeConfig.javascript = {
45
+ formatter: {
46
+ semicolons: defaultFormatterConfig.semi ? "always" : "asNeeded",
47
+ quoteStyle: defaultFormatterConfig.singleQuote ? "single" : "double",
48
+ trailingCommas: defaultFormatterConfig.trailingComma,
49
+ bracketSpacing: defaultFormatterConfig.bracketSpacing,
50
+ arrowParentheses: defaultFormatterConfig.arrowParens === "always" ? "always" : "asNeeded",
51
+ },
52
+ };
53
+ }
54
+ else {
55
+ biomeConfig.formatter = {
56
+ enabled: false,
57
+ };
58
+ }
59
+ generator.addFile("biome.json", {
60
+ type: "text",
61
+ content: JSON.stringify(biomeConfig, null, 2),
62
+ });
63
+ if (options.linter) {
64
+ generator.addScript("lint", "biome lint .");
65
+ }
66
+ if (options.formatter) {
67
+ generator.addScript("format", "biome format --write .");
68
+ }
69
+ const roles = [];
70
+ if (options.linter)
71
+ roles.push("linter");
72
+ if (options.formatter)
73
+ roles.push("formatter");
74
+ generator.inject("readme-tools", `[Biome](https://biomejs.dev/) - Fast ${roles.join(" and ")} for JavaScript and TypeScript`);
75
+ generator.inject("vscode-extension-suggestion", "biomejs.biome");
76
+ generator.addVscodeSetting("biome.enabled", true);
77
+ if (options.formatter) {
78
+ generator.addVscodeSetting("editor.defaultFormatter", "biomejs.biome");
79
+ }
80
+ }
@@ -0,0 +1,3 @@
1
+ import type { Generator } from "../index.js";
2
+ export type GenerateDreiOptions = {} | boolean;
3
+ export declare function generateDrei(generator: Generator, options: GenerateDreiOptions | undefined): void;
@@ -0,0 +1,9 @@
1
+ export function generateDrei(generator, options) {
2
+ if (options == null) {
3
+ return;
4
+ }
5
+ generator.addDependency("@react-three/drei", "^10.0.0");
6
+ generator.inject("import", `import { Environment } from "@react-three/drei"`);
7
+ generator.inject("scene", '<Environment background preset="city" />');
8
+ generator.inject("readme-libraries", `[@react-three/drei](https://drei.docs.pmnd.rs/) - Useful helpers for @react-three/fiber`);
9
+ }
@@ -0,0 +1,3 @@
1
+ import { type Generator } from "../index.js";
2
+ export type GenerateEslintOptions = {} | boolean;
3
+ export declare function generateEslint(generator: Generator, options: GenerateEslintOptions | undefined): void;
@@ -0,0 +1,78 @@
1
+ import { defaultLinterConfig } from "../constants.js";
2
+ import { getBaseTemplate, getLanguageFromTemplate } from "../index.js";
3
+ // Helper to convert level to eslint format
4
+ function toEslintLevel(level) {
5
+ return level;
6
+ }
7
+ export function generateEslint(generator, options) {
8
+ if (options == null) {
9
+ return;
10
+ }
11
+ const version = generator.versions.eslint ?? "9.17.0";
12
+ generator.addDependency("eslint", `^${version}`);
13
+ // Add eslint flat config
14
+ const template = generator.options.template ?? "vanilla";
15
+ const baseTemplate = getBaseTemplate(template);
16
+ const isTypescript = getLanguageFromTemplate(template) === "typescript";
17
+ const isReact = baseTemplate === "react" || baseTemplate === "r3f";
18
+ const { rules } = defaultLinterConfig;
19
+ const imports = ['import js from "@eslint/js"'];
20
+ const configs = ["js.configs.recommended"];
21
+ if (isTypescript) {
22
+ generator.addDependency("typescript-eslint", "^8.18.0");
23
+ imports.push('import tseslint from "typescript-eslint"');
24
+ configs.push("...tseslint.configs.recommended");
25
+ }
26
+ if (isReact) {
27
+ generator.addDependency("eslint-plugin-react-hooks", "^5.1.0");
28
+ imports.push('import reactHooks from "eslint-plugin-react-hooks"');
29
+ }
30
+ // Build ignore patterns string
31
+ const ignoresArray = JSON.stringify(defaultLinterConfig.ignorePatterns);
32
+ // Build rules object - use @typescript-eslint/no-unused-vars for TS projects
33
+ const unusedVarsRule = isTypescript ? "@typescript-eslint/no-unused-vars" : "no-unused-vars";
34
+ const rulesConfig = {
35
+ [unusedVarsRule]: [
36
+ toEslintLevel(rules.noUnusedVars.level),
37
+ {
38
+ argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
39
+ varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
40
+ caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern,
41
+ },
42
+ ],
43
+ "no-unused-expressions": [
44
+ toEslintLevel(rules.noUnusedExpressions.level),
45
+ { allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit },
46
+ ],
47
+ };
48
+ const rulesString = JSON.stringify(rulesConfig, null, 4).replace(/\n/g, "\n ");
49
+ const configContent = [
50
+ ...imports,
51
+ "",
52
+ "export default [",
53
+ ` { ignores: ${ignoresArray} },`,
54
+ ` ${configs.join(",\n ")},`,
55
+ isReact
56
+ ? ` {
57
+ plugins: {
58
+ "react-hooks": reactHooks,
59
+ },
60
+ rules: reactHooks.configs.recommended.rules,
61
+ },`
62
+ : "",
63
+ ` {
64
+ rules: ${rulesString},
65
+ },`,
66
+ "]",
67
+ ]
68
+ .filter(Boolean)
69
+ .join("\n");
70
+ generator.addFile("eslint.config.js", {
71
+ type: "text",
72
+ content: configContent,
73
+ });
74
+ generator.addScript("lint", "eslint .");
75
+ generator.inject("readme-tools", "[ESLint](https://eslint.org/) - Linter for JavaScript and TypeScript");
76
+ generator.inject("vscode-extension-suggestion", "dbaeumer.vscode-eslint");
77
+ generator.addVscodeSetting("eslint.enable", true);
78
+ }
@@ -0,0 +1,8 @@
1
+ import type { Generator } from "../index.js";
2
+ export type GenerateFiberOptions = {
3
+ /**
4
+ * @default true
5
+ */
6
+ addExample?: boolean;
7
+ } | boolean;
8
+ export declare function generateFiber(generator: Generator, _options: GenerateFiberOptions | undefined): void;