create-krispya 0.1.0 → 0.3.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.
- package/dist/cli.cjs +550 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +531 -0
- package/dist/index.cjs +1688 -0
- package/dist/index.d.cts +148 -0
- package/dist/index.d.mts +148 -0
- package/dist/index.d.ts +89 -27
- package/dist/index.mjs +1676 -0
- package/package.json +6 -5
- package/dist/cli.js +0 -497
- package/dist/constants.d.ts +0 -33
- package/dist/constants.js +0 -110
- package/dist/index.js +0 -394
- package/dist/integrations/biome.d.ts +0 -8
- package/dist/integrations/biome.js +0 -80
- package/dist/integrations/drei.d.ts +0 -3
- package/dist/integrations/drei.js +0 -9
- package/dist/integrations/eslint.d.ts +0 -3
- package/dist/integrations/eslint.js +0 -78
- package/dist/integrations/fiber.d.ts +0 -8
- package/dist/integrations/fiber.js +0 -35
- package/dist/integrations/github-pages.d.ts +0 -3
- package/dist/integrations/github-pages.js +0 -58
- package/dist/integrations/handle.d.ts +0 -3
- package/dist/integrations/handle.js +0 -7
- package/dist/integrations/koota.d.ts +0 -8
- package/dist/integrations/koota.js +0 -7
- package/dist/integrations/leva.d.ts +0 -3
- package/dist/integrations/leva.js +0 -7
- package/dist/integrations/offscreen.d.ts +0 -3
- package/dist/integrations/offscreen.js +0 -12
- package/dist/integrations/oxfmt.d.ts +0 -3
- package/dist/integrations/oxfmt.js +0 -27
- package/dist/integrations/oxlint.d.ts +0 -3
- package/dist/integrations/oxlint.js +0 -52
- package/dist/integrations/postprocessing.d.ts +0 -3
- package/dist/integrations/postprocessing.js +0 -12
- package/dist/integrations/prettier.d.ts +0 -3
- package/dist/integrations/prettier.js +0 -28
- package/dist/integrations/rapier.d.ts +0 -3
- package/dist/integrations/rapier.js +0 -7
- package/dist/integrations/triplex.d.ts +0 -26
- package/dist/integrations/triplex.js +0 -159
- package/dist/integrations/uikit.d.ts +0 -3
- package/dist/integrations/uikit.js +0 -7
- package/dist/integrations/viverse.d.ts +0 -3
- package/dist/integrations/viverse.js +0 -74
- package/dist/integrations/xr.d.ts +0 -5
- package/dist/integrations/xr.js +0 -51
- package/dist/integrations/zustand.d.ts +0 -8
- package/dist/integrations/zustand.js +0 -7
- package/dist/lib/array.d.ts +0 -1
- package/dist/lib/array.js +0 -9
- package/dist/merge.d.ts +0 -1
- package/dist/merge.js +0 -26
- package/dist/utils.d.ts +0 -22
- package/dist/utils.js +0 -123
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1688 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const color = require('chalk');
|
|
4
|
+
|
|
5
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
6
|
+
|
|
7
|
+
const color__default = /*#__PURE__*/_interopDefaultCompat(color);
|
|
8
|
+
|
|
9
|
+
const HtmlContent = `<!DOCTYPE html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="UTF-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
14
|
+
<title>$title</title>
|
|
15
|
+
</head>
|
|
16
|
+
<body style="margin: 0; overscroll-behavior: none; user-select: none; touch-action: none;">
|
|
17
|
+
<script type="module" src="$indexPath"><\/script>
|
|
18
|
+
<div style="width: 100dvw; height: 100dvh; overflow: hidden;" id="root"></div>
|
|
19
|
+
</body>
|
|
20
|
+
</html>`;
|
|
21
|
+
const ViteHtmlContent = `<!DOCTYPE html>
|
|
22
|
+
<html lang="en">
|
|
23
|
+
<head>
|
|
24
|
+
<meta charset="UTF-8">
|
|
25
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
26
|
+
<title>$title</title>
|
|
27
|
+
</head>
|
|
28
|
+
<body>
|
|
29
|
+
<div id="app"></div>
|
|
30
|
+
<script type="module" src="$indexPath"><\/script>
|
|
31
|
+
</body>
|
|
32
|
+
</html>`;
|
|
33
|
+
const IndexContent = `import { StrictMode } from 'react'
|
|
34
|
+
import { createRoot } from 'react-dom/client'
|
|
35
|
+
import { App } from './app.js'
|
|
36
|
+
|
|
37
|
+
createRoot(document.getElementById('root')!).render(
|
|
38
|
+
<StrictMode>
|
|
39
|
+
<App />
|
|
40
|
+
</StrictMode>,
|
|
41
|
+
)`;
|
|
42
|
+
const ViteIndexContent = `import './style.css'
|
|
43
|
+
|
|
44
|
+
document.querySelector('#app')!.innerHTML = \`
|
|
45
|
+
<h1>Hello Vite!</h1>
|
|
46
|
+
<p>Edit src/main.ts and save to see HMR in action.</p>
|
|
47
|
+
\``;
|
|
48
|
+
const ViteStyleContent = `body {
|
|
49
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
50
|
+
margin: 0;
|
|
51
|
+
padding: 2rem;
|
|
52
|
+
min-height: 100vh;
|
|
53
|
+
background: #1a1a1a;
|
|
54
|
+
color: #fff;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
h1 {
|
|
58
|
+
color: #646cff;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
a {
|
|
62
|
+
color: #646cff;
|
|
63
|
+
}`;
|
|
64
|
+
const GitAttributes = [
|
|
65
|
+
"* text eol=lf",
|
|
66
|
+
"*.png binary",
|
|
67
|
+
"*.jpg binary",
|
|
68
|
+
"*.jpeg binary",
|
|
69
|
+
"*.gif binary",
|
|
70
|
+
"*.ico binary",
|
|
71
|
+
"*.mov binary",
|
|
72
|
+
"*.mp4 binary",
|
|
73
|
+
"*.mp3 binary",
|
|
74
|
+
"*.flv binary",
|
|
75
|
+
"*.fla binary",
|
|
76
|
+
"*.wav binary",
|
|
77
|
+
"*.swf binary",
|
|
78
|
+
"*.gz binary",
|
|
79
|
+
"*.zip binary",
|
|
80
|
+
"*.7z binary",
|
|
81
|
+
"*.ttf binary",
|
|
82
|
+
"*.eot binary",
|
|
83
|
+
"*.woff binary",
|
|
84
|
+
"*.pyc binary",
|
|
85
|
+
"*.pdf binary",
|
|
86
|
+
"*.glb binary",
|
|
87
|
+
"*.gltf binary"
|
|
88
|
+
].join("\n");
|
|
89
|
+
const defaultFormatterConfig = {
|
|
90
|
+
printWidth: 102,
|
|
91
|
+
tabWidth: 4,
|
|
92
|
+
useTabs: false,
|
|
93
|
+
semi: true,
|
|
94
|
+
singleQuote: true,
|
|
95
|
+
trailingComma: "es5",
|
|
96
|
+
bracketSpacing: true,
|
|
97
|
+
arrowParens: "always"
|
|
98
|
+
};
|
|
99
|
+
const defaultLinterConfig = {
|
|
100
|
+
ignorePatterns: ["dist"],
|
|
101
|
+
rules: {
|
|
102
|
+
noUnusedVars: {
|
|
103
|
+
level: "warn",
|
|
104
|
+
argsIgnorePattern: "^_",
|
|
105
|
+
varsIgnorePattern: "^_",
|
|
106
|
+
caughtErrorsIgnorePattern: "^_"
|
|
107
|
+
},
|
|
108
|
+
noUnusedExpressions: {
|
|
109
|
+
level: "warn",
|
|
110
|
+
allowShortCircuit: true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function toBiomeLevel(level) {
|
|
116
|
+
return level;
|
|
117
|
+
}
|
|
118
|
+
function generateBiome(generator, options) {
|
|
119
|
+
if (options == null || !options.linter && !options.formatter) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const version = generator.versions.biome ?? "1.9.4";
|
|
123
|
+
generator.addDevDependency("@biomejs/biome", `^${version}`);
|
|
124
|
+
const { rules } = defaultLinterConfig;
|
|
125
|
+
const biomeConfig = {
|
|
126
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
127
|
+
files: {
|
|
128
|
+
ignore: defaultLinterConfig.ignorePatterns
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
if (options.linter) {
|
|
132
|
+
biomeConfig.linter = {
|
|
133
|
+
enabled: true,
|
|
134
|
+
rules: {
|
|
135
|
+
recommended: true,
|
|
136
|
+
correctness: {
|
|
137
|
+
noUnusedVariables: toBiomeLevel(rules.noUnusedVars.level)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
} else {
|
|
142
|
+
biomeConfig.linter = {
|
|
143
|
+
enabled: false
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (options.formatter) {
|
|
147
|
+
biomeConfig.formatter = {
|
|
148
|
+
enabled: true,
|
|
149
|
+
lineWidth: defaultFormatterConfig.printWidth,
|
|
150
|
+
indentWidth: defaultFormatterConfig.tabWidth,
|
|
151
|
+
indentStyle: "space"
|
|
152
|
+
};
|
|
153
|
+
biomeConfig.javascript = {
|
|
154
|
+
formatter: {
|
|
155
|
+
semicolons: "always" ,
|
|
156
|
+
quoteStyle: "single" ,
|
|
157
|
+
trailingCommas: defaultFormatterConfig.trailingComma,
|
|
158
|
+
bracketSpacing: defaultFormatterConfig.bracketSpacing,
|
|
159
|
+
arrowParentheses: "always"
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
} else {
|
|
163
|
+
biomeConfig.formatter = {
|
|
164
|
+
enabled: false
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
generator.addFile("biome.json", {
|
|
168
|
+
type: "text",
|
|
169
|
+
content: JSON.stringify(biomeConfig, null, 2)
|
|
170
|
+
});
|
|
171
|
+
if (options.linter) {
|
|
172
|
+
generator.addScript("lint", "biome lint .");
|
|
173
|
+
}
|
|
174
|
+
if (options.formatter) {
|
|
175
|
+
generator.addScript("format", "biome format --write .");
|
|
176
|
+
}
|
|
177
|
+
const roles = [];
|
|
178
|
+
if (options.linter) roles.push("linter");
|
|
179
|
+
if (options.formatter) roles.push("formatter");
|
|
180
|
+
generator.inject(
|
|
181
|
+
"readme-tools",
|
|
182
|
+
`[Biome](https://biomejs.dev/) - Fast ${roles.join(" and ")} for JavaScript and TypeScript`
|
|
183
|
+
);
|
|
184
|
+
generator.inject("vscode-extension-suggestion", "biomejs.biome");
|
|
185
|
+
generator.addVscodeSetting("biome.enabled", true);
|
|
186
|
+
if (options.formatter) {
|
|
187
|
+
generator.addVscodeSetting("editor.defaultFormatter", "biomejs.biome");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function generateDrei(generator, options) {
|
|
192
|
+
if (options == null) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
generator.addDependency("@react-three/drei", "^10.0.0");
|
|
196
|
+
generator.inject("import", `import { Environment } from "@react-three/drei"`);
|
|
197
|
+
generator.inject("scene", '<Environment background preset="city" />');
|
|
198
|
+
generator.inject(
|
|
199
|
+
"readme-libraries",
|
|
200
|
+
`[@react-three/drei](https://drei.docs.pmnd.rs/) - Useful helpers for @react-three/fiber`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function toEslintLevel(level) {
|
|
205
|
+
return level;
|
|
206
|
+
}
|
|
207
|
+
function generateEslint(generator, options) {
|
|
208
|
+
const version = generator.versions.eslint ?? "9.17.0";
|
|
209
|
+
generator.addDevDependency("eslint", `^${version}`);
|
|
210
|
+
const template = generator.options.template ?? "vanilla";
|
|
211
|
+
const baseTemplate = getBaseTemplate(template);
|
|
212
|
+
const isTypescript = getLanguageFromTemplate(template) === "typescript";
|
|
213
|
+
const isReact = baseTemplate === "react" || baseTemplate === "r3f";
|
|
214
|
+
const { rules } = defaultLinterConfig;
|
|
215
|
+
const imports = ['import js from "@eslint/js"'];
|
|
216
|
+
const configs = ["js.configs.recommended"];
|
|
217
|
+
if (isTypescript) {
|
|
218
|
+
generator.addDevDependency("typescript-eslint", "^8.18.0");
|
|
219
|
+
imports.push('import tseslint from "typescript-eslint"');
|
|
220
|
+
configs.push("...tseslint.configs.recommended");
|
|
221
|
+
}
|
|
222
|
+
if (isReact) {
|
|
223
|
+
generator.addDevDependency("eslint-plugin-react-hooks", "^5.1.0");
|
|
224
|
+
imports.push('import reactHooks from "eslint-plugin-react-hooks"');
|
|
225
|
+
}
|
|
226
|
+
const ignoresArray = JSON.stringify(defaultLinterConfig.ignorePatterns);
|
|
227
|
+
const unusedVarsRule = isTypescript ? "@typescript-eslint/no-unused-vars" : "no-unused-vars";
|
|
228
|
+
const rulesConfig = {
|
|
229
|
+
[unusedVarsRule]: [
|
|
230
|
+
toEslintLevel(rules.noUnusedVars.level),
|
|
231
|
+
{
|
|
232
|
+
argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
|
|
233
|
+
varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
|
|
234
|
+
caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
"no-unused-expressions": [
|
|
238
|
+
toEslintLevel(rules.noUnusedExpressions.level),
|
|
239
|
+
{ allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
|
|
240
|
+
]
|
|
241
|
+
};
|
|
242
|
+
const rulesString = JSON.stringify(rulesConfig, null, 4).replace(/\n/g, "\n ");
|
|
243
|
+
const configContent = [
|
|
244
|
+
...imports,
|
|
245
|
+
"",
|
|
246
|
+
"export default [",
|
|
247
|
+
` { ignores: ${ignoresArray} },`,
|
|
248
|
+
` ${configs.join(",\n ")},`,
|
|
249
|
+
isReact ? ` {
|
|
250
|
+
plugins: {
|
|
251
|
+
"react-hooks": reactHooks,
|
|
252
|
+
},
|
|
253
|
+
rules: reactHooks.configs.recommended.rules,
|
|
254
|
+
},` : "",
|
|
255
|
+
` {
|
|
256
|
+
rules: ${rulesString},
|
|
257
|
+
},`,
|
|
258
|
+
"]"
|
|
259
|
+
].filter(Boolean).join("\n");
|
|
260
|
+
generator.addFile("eslint.config.js", {
|
|
261
|
+
type: "text",
|
|
262
|
+
content: configContent
|
|
263
|
+
});
|
|
264
|
+
generator.addScript("lint", "eslint .");
|
|
265
|
+
generator.inject(
|
|
266
|
+
"readme-tools",
|
|
267
|
+
"[ESLint](https://eslint.org/) - Linter for JavaScript and TypeScript"
|
|
268
|
+
);
|
|
269
|
+
generator.inject("vscode-extension-suggestion", "dbaeumer.vscode-eslint");
|
|
270
|
+
generator.addVscodeSetting("eslint.enable", true);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function generateFiber(generator, _options) {
|
|
274
|
+
generator.inject("import", `import { Box } from "./box.js"`);
|
|
275
|
+
generator.inject(
|
|
276
|
+
"scene",
|
|
277
|
+
[
|
|
278
|
+
`<ambientLight intensity={Math.PI / 2} />`,
|
|
279
|
+
`<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} decay={0} intensity={Math.PI} />`,
|
|
280
|
+
`<pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />`,
|
|
281
|
+
`<Box position={[-1.2, 0, 0]} />`,
|
|
282
|
+
`<Box position={[1.2, 0, 0]} />`
|
|
283
|
+
].join("\n")
|
|
284
|
+
);
|
|
285
|
+
generator.addFile("src/box.tsx", {
|
|
286
|
+
type: "text",
|
|
287
|
+
content: `import type { Mesh } from 'three'
|
|
288
|
+
import { useRef, useState } from 'react'
|
|
289
|
+
import { useFrame, ThreeElements } from '@react-three/fiber'
|
|
290
|
+
|
|
291
|
+
export function Box(props: ThreeElements['mesh']) {
|
|
292
|
+
const meshRef = useRef<Mesh>(null!)
|
|
293
|
+
const [hovered, setHover] = useState(false)
|
|
294
|
+
const [active, setActive] = useState(false)
|
|
295
|
+
useFrame((state, delta) => (meshRef.current.rotation.x += delta))
|
|
296
|
+
return (
|
|
297
|
+
<mesh
|
|
298
|
+
{...props}
|
|
299
|
+
ref={meshRef}
|
|
300
|
+
scale={active ? 1.5 : 1}
|
|
301
|
+
onClick={(event) => setActive(!active)}
|
|
302
|
+
onPointerOver={(event) => setHover(true)}
|
|
303
|
+
onPointerOut={(event) => setHover(false)}>
|
|
304
|
+
<boxGeometry args={[1, 1, 1]} />
|
|
305
|
+
<meshStandardMaterial color={hovered ? 'hotpink' : '#2f74c0'} />
|
|
306
|
+
</mesh>
|
|
307
|
+
)
|
|
308
|
+
}`
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function generateGithubPages(generator, options) {
|
|
313
|
+
if (options === false || (generator.options.packageManager ?? "npm") != "npm") {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
generator.addFile(".github/workflows/gh-pages.yml", {
|
|
317
|
+
type: "text",
|
|
318
|
+
content: `name: Deploy to Github Pages
|
|
319
|
+
|
|
320
|
+
on:
|
|
321
|
+
push:
|
|
322
|
+
branches:
|
|
323
|
+
- main
|
|
324
|
+
|
|
325
|
+
jobs:
|
|
326
|
+
build-and-deploy:
|
|
327
|
+
runs-on: ubuntu-latest
|
|
328
|
+
permissions:
|
|
329
|
+
pages: write
|
|
330
|
+
contents: read
|
|
331
|
+
id-token: write
|
|
332
|
+
|
|
333
|
+
environment:
|
|
334
|
+
name: github-pages
|
|
335
|
+
url: \${{ steps.deployment.outputs.page_url }}
|
|
336
|
+
|
|
337
|
+
steps:
|
|
338
|
+
- name: Checkout code
|
|
339
|
+
uses: actions/checkout@v3
|
|
340
|
+
|
|
341
|
+
- name: Setup Node
|
|
342
|
+
uses: actions/setup-node@v3
|
|
343
|
+
with:
|
|
344
|
+
node-version: 20
|
|
345
|
+
|
|
346
|
+
- name: Install dependencies
|
|
347
|
+
run: npm install
|
|
348
|
+
|
|
349
|
+
- name: Build project
|
|
350
|
+
run: npm run build
|
|
351
|
+
|
|
352
|
+
- name: Upload artifact
|
|
353
|
+
uses: actions/upload-pages-artifact@v3
|
|
354
|
+
with:
|
|
355
|
+
path: './dist'
|
|
356
|
+
|
|
357
|
+
- name: Deploy to GitHub Pages
|
|
358
|
+
id: deployment
|
|
359
|
+
uses: actions/deploy-pages@v4
|
|
360
|
+
`
|
|
361
|
+
});
|
|
362
|
+
generator.inject(
|
|
363
|
+
"readme-start",
|
|
364
|
+
`A github pages deployment action is configurd.`
|
|
365
|
+
);
|
|
366
|
+
if (generator.options.githubUserName != null && generator.options.githubRepoName != null) {
|
|
367
|
+
const address = `${generator.options.githubUserName}.github.io/${generator.options.githubRepoName}`;
|
|
368
|
+
generator.inject(
|
|
369
|
+
"readme-start",
|
|
370
|
+
`Your app will be publish at [${address}](https://${address}) once the github action is finished.
|
|
371
|
+
`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function generateHandle(generator, options) {
|
|
377
|
+
if (options == null) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
generator.addDependency("@react-three/handle", "^6.6.16");
|
|
381
|
+
generator.inject(
|
|
382
|
+
"readme-libraries",
|
|
383
|
+
`[@react-three/handle](https://pmndrs.github.io/xr/docs/handles/introduction) - interactive controls and handles for your 3D objects`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function generateKoota(generator, options) {
|
|
388
|
+
if (options == null) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
generator.addDependency("koota", "^0.4.0");
|
|
392
|
+
generator.inject(
|
|
393
|
+
"readme-libraries",
|
|
394
|
+
`[koota](https://github.com/pmndrs/koota) - ECS-based state management library optimized for real-time apps, games, and XR experiences`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function generateLeva(generator, options) {
|
|
399
|
+
if (options == null) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
generator.addDependency("leva", "^0.10.0");
|
|
403
|
+
generator.inject(
|
|
404
|
+
"readme-libraries",
|
|
405
|
+
`[leva](https://github.com/pmndrs/leva) - HTML GUI panel for React with lightweight, beautiful and extensible controls`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function generateOffscreen(generator, options) {
|
|
410
|
+
if (options == null) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (generator.options.xr != null) {
|
|
414
|
+
console.info(
|
|
415
|
+
color__default.blue("Info:"),
|
|
416
|
+
"@react-three/offscreen is disabled because it is not supported with XR"
|
|
417
|
+
);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
generator.addDependency("@react-three/offscreen", "^0.0.8");
|
|
421
|
+
generator.inject(
|
|
422
|
+
"readme-libraries",
|
|
423
|
+
`[@react-three/offscreen](https://github.com/pmndrs/offscreen) - Offload your scene to a worker thread for better performance`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function generateOxfmt(generator, options) {
|
|
428
|
+
const version = generator.versions.oxfmt ?? "0.1.0";
|
|
429
|
+
generator.addDevDependency("oxfmt", `^${version}`);
|
|
430
|
+
const oxfmtConfig = {
|
|
431
|
+
printWidth: defaultFormatterConfig.printWidth,
|
|
432
|
+
tabWidth: defaultFormatterConfig.tabWidth,
|
|
433
|
+
useTabs: defaultFormatterConfig.useTabs,
|
|
434
|
+
semi: defaultFormatterConfig.semi,
|
|
435
|
+
singleQuote: defaultFormatterConfig.singleQuote,
|
|
436
|
+
trailingComma: defaultFormatterConfig.trailingComma,
|
|
437
|
+
bracketSpacing: defaultFormatterConfig.bracketSpacing,
|
|
438
|
+
arrowParens: defaultFormatterConfig.arrowParens
|
|
439
|
+
};
|
|
440
|
+
generator.addFile(".oxfmt.json", {
|
|
441
|
+
type: "text",
|
|
442
|
+
content: JSON.stringify(oxfmtConfig, null, 2)
|
|
443
|
+
});
|
|
444
|
+
generator.addScript("format", "oxfmt --write .");
|
|
445
|
+
generator.inject(
|
|
446
|
+
"readme-tools",
|
|
447
|
+
"[Oxfmt](https://oxc.rs/docs/guide/usage/formatter) - Fast Prettier-compatible code formatter"
|
|
448
|
+
);
|
|
449
|
+
generator.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
|
|
450
|
+
generator.addVscodeSetting("editor.defaultFormatter", "oxc.oxc-vscode");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function toOxlintLevel(level) {
|
|
454
|
+
return level;
|
|
455
|
+
}
|
|
456
|
+
function generateOxlint(generator, options) {
|
|
457
|
+
const version = generator.versions.oxlint ?? "0.16.0";
|
|
458
|
+
generator.addDevDependency("oxlint", `^${version}`);
|
|
459
|
+
const { rules } = defaultLinterConfig;
|
|
460
|
+
const template = generator.options.template ?? "vanilla";
|
|
461
|
+
const baseTemplate = getBaseTemplate(template);
|
|
462
|
+
const isReact = baseTemplate === "react" || baseTemplate === "r3f";
|
|
463
|
+
const plugins = ["unicorn", "typescript", "oxc"];
|
|
464
|
+
if (isReact) {
|
|
465
|
+
plugins.push("react");
|
|
466
|
+
}
|
|
467
|
+
const oxlintConfig = {
|
|
468
|
+
$schema: "./node_modules/oxlint/configuration_schema.json",
|
|
469
|
+
plugins,
|
|
470
|
+
rules: {
|
|
471
|
+
"no-unused-vars": [
|
|
472
|
+
toOxlintLevel(rules.noUnusedVars.level),
|
|
473
|
+
{
|
|
474
|
+
argsIgnorePattern: rules.noUnusedVars.argsIgnorePattern,
|
|
475
|
+
varsIgnorePattern: rules.noUnusedVars.varsIgnorePattern,
|
|
476
|
+
caughtErrorsIgnorePattern: rules.noUnusedVars.caughtErrorsIgnorePattern
|
|
477
|
+
}
|
|
478
|
+
],
|
|
479
|
+
"no-useless-escape": "off",
|
|
480
|
+
"no-unused-expressions": [
|
|
481
|
+
toOxlintLevel(rules.noUnusedExpressions.level),
|
|
482
|
+
{ allowShortCircuit: rules.noUnusedExpressions.allowShortCircuit }
|
|
483
|
+
]
|
|
484
|
+
},
|
|
485
|
+
ignorePatterns: defaultLinterConfig.ignorePatterns
|
|
486
|
+
};
|
|
487
|
+
generator.addFile("oxlint.json", {
|
|
488
|
+
type: "text",
|
|
489
|
+
content: JSON.stringify(oxlintConfig, null, 2)
|
|
490
|
+
});
|
|
491
|
+
generator.addScript("lint", "oxlint");
|
|
492
|
+
generator.inject(
|
|
493
|
+
"readme-tools",
|
|
494
|
+
"[Oxlint](https://oxc.rs/docs/guide/usage/linter) - A fast linter for JavaScript and TypeScript"
|
|
495
|
+
);
|
|
496
|
+
generator.inject("vscode-extension-suggestion", "oxc.oxc-vscode");
|
|
497
|
+
generator.addVscodeSetting("oxc.enable", true);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function generatePostprocessing(generator, options) {
|
|
501
|
+
if (options == null) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (generator.options.xr != null) {
|
|
505
|
+
console.info(
|
|
506
|
+
color__default.blue("Info:"),
|
|
507
|
+
"@react-three/postprocessing is disabled because it is not supported with XR"
|
|
508
|
+
);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
generator.addDependency("@react-three/postprocessing", "^3.0.4");
|
|
512
|
+
generator.inject(
|
|
513
|
+
"readme-libraries",
|
|
514
|
+
`[@react-three/postprocessing](https://react-postprocessing.docs.pmnd.rs/) - Post-processing effects for @react-three/fiber`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function generatePrettier(generator, options) {
|
|
519
|
+
const version = generator.versions.prettier ?? "3.4.2";
|
|
520
|
+
generator.addDevDependency("prettier", `^${version}`);
|
|
521
|
+
const prettierConfig = {
|
|
522
|
+
$schema: "https://json.schemastore.org/prettierrc",
|
|
523
|
+
printWidth: defaultFormatterConfig.printWidth,
|
|
524
|
+
tabWidth: defaultFormatterConfig.tabWidth,
|
|
525
|
+
useTabs: defaultFormatterConfig.useTabs,
|
|
526
|
+
semi: defaultFormatterConfig.semi,
|
|
527
|
+
singleQuote: defaultFormatterConfig.singleQuote,
|
|
528
|
+
trailingComma: defaultFormatterConfig.trailingComma,
|
|
529
|
+
bracketSpacing: defaultFormatterConfig.bracketSpacing,
|
|
530
|
+
arrowParens: defaultFormatterConfig.arrowParens
|
|
531
|
+
};
|
|
532
|
+
generator.addFile(".prettierrc", {
|
|
533
|
+
type: "text",
|
|
534
|
+
content: JSON.stringify(prettierConfig, null, 2)
|
|
535
|
+
});
|
|
536
|
+
generator.addScript("format", "prettier --write .");
|
|
537
|
+
generator.inject("readme-tools", "[Prettier](https://prettier.io/) - Opinionated code formatter");
|
|
538
|
+
generator.inject("vscode-extension-suggestion", "esbenp.prettier-vscode");
|
|
539
|
+
generator.addVscodeSetting("editor.defaultFormatter", "esbenp.prettier-vscode");
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function generateRapier(generator, options) {
|
|
543
|
+
if (options == null) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
generator.addDependency("@react-three/rapier", "^2.1.0");
|
|
547
|
+
generator.inject(
|
|
548
|
+
"readme-libraries",
|
|
549
|
+
`[@react-three/rapier](https://github.com/pmndrs/react-three-rapier) - Physics based on Rapier for your @react-three/fiber scene`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function generateUikit(generator, options) {
|
|
554
|
+
if (options == null) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
generator.addDependency("@react-three/uikit", "^0.8.15");
|
|
558
|
+
generator.inject(
|
|
559
|
+
"readme-libraries",
|
|
560
|
+
`[@react-three/uikit](https://pmndrs.github.io/uikit/docs/) - UI primitives for React Three Fiber`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function generateXr(generator, options) {
|
|
565
|
+
if (options == null || options === false) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (options === true) {
|
|
569
|
+
options = {};
|
|
570
|
+
}
|
|
571
|
+
generator.addDependency("@react-three/xr", "^6.6.16");
|
|
572
|
+
generator.addDependency("@vitejs/plugin-basic-ssl", "^2.0.0");
|
|
573
|
+
generator.inject("import", "import { XR, createXRStore } from '@react-three/xr'");
|
|
574
|
+
generator.inject(
|
|
575
|
+
`global-start`,
|
|
576
|
+
`const store = createXRStore(${JSON.stringify(options.storeOptions ?? {})})`
|
|
577
|
+
);
|
|
578
|
+
generator.inject("scene-start", "<XR store={store}>");
|
|
579
|
+
generator.inject("scene-end", "</XR>");
|
|
580
|
+
generator.inject("vite-config-import", "import basicSsl from '@vitejs/plugin-basic-ssl'");
|
|
581
|
+
generator.configureVite({
|
|
582
|
+
server: {
|
|
583
|
+
host: true
|
|
584
|
+
},
|
|
585
|
+
plugins: ["$raw:basicSsl()"]
|
|
586
|
+
});
|
|
587
|
+
generator.inject(
|
|
588
|
+
"dom-start",
|
|
589
|
+
`<div style={{
|
|
590
|
+
display: "flex",
|
|
591
|
+
flexDirection: "row",
|
|
592
|
+
gap: "1rem",
|
|
593
|
+
position: 'absolute',
|
|
594
|
+
zIndex: 10000,
|
|
595
|
+
background: 'black',
|
|
596
|
+
borderRadius: '0.5rem',
|
|
597
|
+
border: 'none',
|
|
598
|
+
fontWeight: 'bold',
|
|
599
|
+
color: 'white',
|
|
600
|
+
cursor: 'pointer',
|
|
601
|
+
fontSize: '1.5rem',
|
|
602
|
+
bottom: '1rem',
|
|
603
|
+
left: '50%',
|
|
604
|
+
boxShadow: '0px 0px 20px rgba(0,0,0,1)',
|
|
605
|
+
transform: 'translate(-50%, 0)',
|
|
606
|
+
}}><button
|
|
607
|
+
style={{ cursor: "pointer", padding: '1rem 2rem', fontSize: "1rem", background: "none", color: "white", border: "none" }}
|
|
608
|
+
onClick={() => store.enterAR()}
|
|
609
|
+
>
|
|
610
|
+
Enter AR
|
|
611
|
+
</button>
|
|
612
|
+
<button
|
|
613
|
+
style={{ cursor: "pointer", padding: '1rem 2rem', fontSize: "1rem", background: "none", color: "white", border: "none" }}
|
|
614
|
+
onClick={() => store.enterVR()}
|
|
615
|
+
>
|
|
616
|
+
Enter VR
|
|
617
|
+
</button></div>`
|
|
618
|
+
);
|
|
619
|
+
generator.inject(
|
|
620
|
+
"readme-libraries",
|
|
621
|
+
`[@react-three/xr](https://pmndrs.github.io/xr/docs/) - VR/AR support for @react-three/fiber`
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function generateZustand(generator, options) {
|
|
626
|
+
if (options == null) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
generator.addDependency("zustand", "^5.0.3");
|
|
630
|
+
generator.inject(
|
|
631
|
+
"readme-libraries",
|
|
632
|
+
`[zustand](https://zustand.docs.pmnd.rs/) - small, fast and scalable state-management solution`
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function unique(...array) {
|
|
637
|
+
const set = /* @__PURE__ */ new Set();
|
|
638
|
+
for (const arr of array) {
|
|
639
|
+
for (const item of arr) {
|
|
640
|
+
set.add(item);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return Array.from(set);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function generateProvidersModule(generator) {
|
|
647
|
+
const canvasProviders = [];
|
|
648
|
+
const globalProviders = [];
|
|
649
|
+
const providerDefs = {
|
|
650
|
+
uikit: {
|
|
651
|
+
type: "layout-effect",
|
|
652
|
+
props: [
|
|
653
|
+
{
|
|
654
|
+
declaredPropDefaultValue: '"light"',
|
|
655
|
+
declaredPropName: "colorMode",
|
|
656
|
+
propName: "colorMode",
|
|
657
|
+
propValue: "colorMode",
|
|
658
|
+
declaredPropType: '"light" | "dark"'
|
|
659
|
+
}
|
|
660
|
+
],
|
|
661
|
+
code: `
|
|
662
|
+
setPreferredColorScheme(colorMode);
|
|
663
|
+
`,
|
|
664
|
+
import: 'import { setPreferredColorScheme } from "@react-three/uikit"'
|
|
665
|
+
},
|
|
666
|
+
rapier: {
|
|
667
|
+
component: "Physics",
|
|
668
|
+
type: "wrapped-jsx",
|
|
669
|
+
import: 'import { Physics } from "@react-three/rapier";',
|
|
670
|
+
props: [
|
|
671
|
+
{
|
|
672
|
+
declaredPropDefaultValue: false,
|
|
673
|
+
declaredPropName: "physicsEnabled",
|
|
674
|
+
propName: "paused",
|
|
675
|
+
propValue: "!physicsEnabled",
|
|
676
|
+
declaredPropType: "boolean"
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
declaredPropDefaultValue: true,
|
|
680
|
+
declaredPropName: "debugPhysics",
|
|
681
|
+
propName: "debug",
|
|
682
|
+
propValue: "debugPhysics",
|
|
683
|
+
declaredPropType: "boolean"
|
|
684
|
+
}
|
|
685
|
+
]
|
|
686
|
+
},
|
|
687
|
+
postprocessing: {
|
|
688
|
+
type: "inline-jsx",
|
|
689
|
+
code: `
|
|
690
|
+
<EffectComposer enabled={postProcessingEnabled}>
|
|
691
|
+
<DepthOfField
|
|
692
|
+
focusDistance={0}
|
|
693
|
+
focalLength={0.02}
|
|
694
|
+
bokehScale={2}
|
|
695
|
+
height={480}
|
|
696
|
+
/>
|
|
697
|
+
<Bloom luminanceThreshold={0} luminanceSmoothing={0.9} height={300} />
|
|
698
|
+
</EffectComposer>
|
|
699
|
+
`,
|
|
700
|
+
import: 'import { Bloom, DepthOfField, EffectComposer } from "@react-three/postprocessing";',
|
|
701
|
+
props: [
|
|
702
|
+
{
|
|
703
|
+
declaredPropDefaultValue: true,
|
|
704
|
+
declaredPropName: "postProcessingEnabled",
|
|
705
|
+
propName: "enabled",
|
|
706
|
+
propValue: "postProcessingEnabled",
|
|
707
|
+
declaredPropType: "boolean"
|
|
708
|
+
}
|
|
709
|
+
]
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
if (generator.options.rapier) {
|
|
713
|
+
canvasProviders.push("rapier");
|
|
714
|
+
}
|
|
715
|
+
if (!!generator.options.postprocessing && !generator.options.xr) {
|
|
716
|
+
canvasProviders.push("postprocessing");
|
|
717
|
+
}
|
|
718
|
+
if (generator.options.uikit) {
|
|
719
|
+
globalProviders.push("uikit");
|
|
720
|
+
}
|
|
721
|
+
function generateProviderFunction(name, { jsdoc, providers }) {
|
|
722
|
+
const resolvedProviders = providers.map((provider) => providerDefs[provider]);
|
|
723
|
+
const providerProps = resolvedProviders.flatMap((provider) => provider.props || []);
|
|
724
|
+
const providerImports = resolvedProviders.flatMap((provider) => provider.import);
|
|
725
|
+
const wrappedComponents = resolvedProviders.filter(
|
|
726
|
+
(provider) => provider.type === "wrapped-jsx"
|
|
727
|
+
);
|
|
728
|
+
const inlineComponents = resolvedProviders.filter((provider) => provider.type === "inline-jsx");
|
|
729
|
+
const layoutEffects = resolvedProviders.filter((provider) => provider.type === "layout-effect");
|
|
730
|
+
const declaredProps = providerProps.map((prop) => `${prop.declaredPropName} = ${prop.declaredPropDefaultValue}`).join(", ");
|
|
731
|
+
const declaredTypes = providerProps.map((prop) => `${prop.declaredPropName}?: ${prop.declaredPropType}`).join("; ");
|
|
732
|
+
const reactImports = ["type ReactNode"];
|
|
733
|
+
if (layoutEffects.length) {
|
|
734
|
+
reactImports.push("useLayoutEffect");
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
reactImports,
|
|
738
|
+
imports: providerImports,
|
|
739
|
+
code: `
|
|
740
|
+
/**
|
|
741
|
+
${jsdoc.split("\n").map((line) => ` * ${line}`).join("\n")}
|
|
742
|
+
*/
|
|
743
|
+
export function ${name}({ children, ${declaredProps} }: { children: ReactNode; ${declaredTypes} }) {
|
|
744
|
+
${layoutEffects.length ? `
|
|
745
|
+
useLayoutEffect(() => {
|
|
746
|
+
${layoutEffects.map((effect) => effect.code).join("\n")}
|
|
747
|
+
}, [${layoutEffects.map((effect) => effect.props?.[0]?.propValue)}]);
|
|
748
|
+
` : ""}
|
|
749
|
+
return (
|
|
750
|
+
<>
|
|
751
|
+
${inlineComponents.map((provider) => provider.code)}
|
|
752
|
+
${wrappedComponents.reduce((acc, provider) => {
|
|
753
|
+
const props = provider.props?.map((prop) => `${prop.propName}={${prop.propValue}}`).join(" ");
|
|
754
|
+
return `<${provider.component} ${props}>${acc}</${provider.component}>`;
|
|
755
|
+
}, "{children}")}
|
|
756
|
+
</>
|
|
757
|
+
);
|
|
758
|
+
}`
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
const global = generateProviderFunction("GlobalProvider", {
|
|
762
|
+
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",
|
|
763
|
+
providers: globalProviders
|
|
764
|
+
});
|
|
765
|
+
const canvas = generateProviderFunction("CanvasProvider", {
|
|
766
|
+
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",
|
|
767
|
+
providers: canvasProviders
|
|
768
|
+
});
|
|
769
|
+
return `
|
|
770
|
+
import { ${unique(global.reactImports, canvas.reactImports).sort().join(", ")} } from "react";
|
|
771
|
+
${unique(global.imports, canvas.imports).sort().join("\n")}
|
|
772
|
+
|
|
773
|
+
${global.code}
|
|
774
|
+
${canvas.code}
|
|
775
|
+
`;
|
|
776
|
+
}
|
|
777
|
+
function generateTriplex(generator, options) {
|
|
778
|
+
if (options == null) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
generator.inject("vscode-extension-suggestion", "trytriplex.triplex-vsce");
|
|
782
|
+
generator.inject(
|
|
783
|
+
"readme-tools",
|
|
784
|
+
`[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).`
|
|
785
|
+
);
|
|
786
|
+
generator.addFile(".triplex/providers.tsx", {
|
|
787
|
+
content: generateProvidersModule(generator),
|
|
788
|
+
type: "text"
|
|
789
|
+
});
|
|
790
|
+
generator.addFile(".triplex/config.json", {
|
|
791
|
+
content: JSON.stringify(
|
|
792
|
+
{
|
|
793
|
+
$schema: "https://triplex.dev/config.schema.json",
|
|
794
|
+
provider: "./providers.tsx"
|
|
795
|
+
},
|
|
796
|
+
null,
|
|
797
|
+
2
|
|
798
|
+
),
|
|
799
|
+
type: "text"
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function generateTsdown(generator) {
|
|
804
|
+
generator.addDevDependency("tsdown", "^0.12.0");
|
|
805
|
+
const template = generator.options.template ?? "vanilla";
|
|
806
|
+
const baseTemplate = getBaseTemplate(template);
|
|
807
|
+
const language = getLanguageFromTemplate(template);
|
|
808
|
+
const isReact = baseTemplate === "react" || baseTemplate === "r3f";
|
|
809
|
+
const ext = language === "typescript" ? "ts" : "js";
|
|
810
|
+
const configLines = [
|
|
811
|
+
`import { defineConfig } from "tsdown"`,
|
|
812
|
+
``,
|
|
813
|
+
`export default defineConfig({`,
|
|
814
|
+
` entry: ["./src/index.${ext}${isReact ? "x" : ""}"],`,
|
|
815
|
+
` format: ["esm", "cjs"],`,
|
|
816
|
+
` dts: ${language === "typescript"},`,
|
|
817
|
+
` clean: true,`
|
|
818
|
+
];
|
|
819
|
+
if (isReact) {
|
|
820
|
+
configLines.push(` esbuild: {`);
|
|
821
|
+
configLines.push(` jsx: "automatic",`);
|
|
822
|
+
configLines.push(` },`);
|
|
823
|
+
}
|
|
824
|
+
configLines.push(`})`);
|
|
825
|
+
generator.addFile(`tsdown.config.${ext}`, {
|
|
826
|
+
type: "text",
|
|
827
|
+
content: configLines.join("\n")
|
|
828
|
+
});
|
|
829
|
+
generator.addScript("build", "tsdown");
|
|
830
|
+
generator.inject(
|
|
831
|
+
"readme-libraries",
|
|
832
|
+
"[tsdown](https://github.com/nicepkg/tsdown) - Fast TypeScript bundler powered by esbuild"
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function generateUnbuild(generator) {
|
|
837
|
+
generator.addDevDependency("unbuild", "^3.5.0");
|
|
838
|
+
const template = generator.options.template ?? "vanilla";
|
|
839
|
+
const baseTemplate = getBaseTemplate(template);
|
|
840
|
+
const language = getLanguageFromTemplate(template);
|
|
841
|
+
const isReact = baseTemplate === "react" || baseTemplate === "r3f";
|
|
842
|
+
const ext = language === "typescript" ? "ts" : "js";
|
|
843
|
+
const buildConfigLines = [
|
|
844
|
+
`import { defineBuildConfig } from "unbuild"`,
|
|
845
|
+
``,
|
|
846
|
+
`export default defineBuildConfig({`,
|
|
847
|
+
` entries: ["./src/index"],`,
|
|
848
|
+
` declaration: ${language === "typescript"},`,
|
|
849
|
+
` clean: true,`,
|
|
850
|
+
` rollup: {`,
|
|
851
|
+
` emitCJS: true,`
|
|
852
|
+
];
|
|
853
|
+
if (isReact) {
|
|
854
|
+
buildConfigLines.push(` esbuild: {`);
|
|
855
|
+
buildConfigLines.push(` jsx: "automatic",`);
|
|
856
|
+
buildConfigLines.push(` },`);
|
|
857
|
+
}
|
|
858
|
+
buildConfigLines.push(` },`);
|
|
859
|
+
buildConfigLines.push(`})`);
|
|
860
|
+
generator.addFile(`build.config.${ext}`, {
|
|
861
|
+
type: "text",
|
|
862
|
+
content: buildConfigLines.join("\n")
|
|
863
|
+
});
|
|
864
|
+
generator.addScript("build", "unbuild");
|
|
865
|
+
generator.inject(
|
|
866
|
+
"readme-libraries",
|
|
867
|
+
"[unbuild](https://github.com/unjs/unbuild) - Unified JavaScript build system"
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function generateVitest(generator) {
|
|
872
|
+
const version = generator.versions.vitest ?? "4.0.0";
|
|
873
|
+
generator.addDevDependency("vitest", `^${version}`);
|
|
874
|
+
const template = generator.options.template ?? "vanilla";
|
|
875
|
+
const baseTemplate = getBaseTemplate(template);
|
|
876
|
+
const isReact = baseTemplate === "react" || baseTemplate === "r3f";
|
|
877
|
+
if (isReact) {
|
|
878
|
+
generator.addDevDependency("@testing-library/react", "^16.2.0");
|
|
879
|
+
generator.addDevDependency("@testing-library/dom", "^10.4.0");
|
|
880
|
+
generator.addDevDependency("jsdom", "^26.0.0");
|
|
881
|
+
}
|
|
882
|
+
if (isReact) {
|
|
883
|
+
generator.configureVite({ test: { environment: "jsdom" } });
|
|
884
|
+
}
|
|
885
|
+
generator.addScript("test", "vitest");
|
|
886
|
+
generator.inject(
|
|
887
|
+
"readme-tools",
|
|
888
|
+
"[Vitest](https://vitest.dev/) - Fast unit test framework powered by Vite"
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function merge(target, modification) {
|
|
893
|
+
if (modification == null) {
|
|
894
|
+
throw new Error(`Cannot merge "${modification}" modification into target "${target}"`);
|
|
895
|
+
}
|
|
896
|
+
if (target == null) {
|
|
897
|
+
return modification;
|
|
898
|
+
}
|
|
899
|
+
if (Array.isArray(target)) {
|
|
900
|
+
if (!Array.isArray(modification)) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
`Cannot merge non-array modification "${modification}" into array target "${target}"`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
return [...target, ...modification];
|
|
906
|
+
}
|
|
907
|
+
if (typeof target === "object") {
|
|
908
|
+
if (typeof modification != "object") {
|
|
909
|
+
throw new Error(
|
|
910
|
+
`Cannot merge non-object modification "${modification}" into object target "${target}"`
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
const result = { ...target };
|
|
914
|
+
for (const modificationKey in modification) {
|
|
915
|
+
result[modificationKey] = merge(target[modificationKey], modification[modificationKey]);
|
|
916
|
+
}
|
|
917
|
+
return result;
|
|
918
|
+
}
|
|
919
|
+
console.warn(`target "${target}" is overwritten with modification "${modification}"`);
|
|
920
|
+
return modification;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function generateViverse(generator, options) {
|
|
924
|
+
if (options == null || (generator.options.packageManager ?? "npm") != "npm") {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
generator.addFile(".github/workflows/viverse.yml", {
|
|
928
|
+
type: "text",
|
|
929
|
+
content: `name: Deploy to Viverse
|
|
930
|
+
|
|
931
|
+
on:
|
|
932
|
+
push:
|
|
933
|
+
branches:
|
|
934
|
+
- main
|
|
935
|
+
workflow_dispatch:
|
|
936
|
+
|
|
937
|
+
jobs:
|
|
938
|
+
check-secrets:
|
|
939
|
+
runs-on: ubuntu-latest
|
|
940
|
+
outputs:
|
|
941
|
+
secrets-available: \${{ steps.check.outputs.secrets-available }}
|
|
942
|
+
steps:
|
|
943
|
+
- id: check
|
|
944
|
+
run: |
|
|
945
|
+
if [[ -n "\${{ secrets.VIVERSE_EMAIL }}" && -n "\${{ secrets.VIVERSE_PASSWORD }}" ]]; then
|
|
946
|
+
echo "secrets-available=true" >> $GITHUB_OUTPUT
|
|
947
|
+
else
|
|
948
|
+
echo "secrets-available=false" >> $GITHUB_OUTPUT
|
|
949
|
+
fi
|
|
950
|
+
|
|
951
|
+
build-and-deploy:
|
|
952
|
+
runs-on: ubuntu-latest
|
|
953
|
+
needs: check-secrets
|
|
954
|
+
# Only run if secrets are present
|
|
955
|
+
if: needs.check-secrets.outputs.secrets-available == 'true'
|
|
956
|
+
permissions:
|
|
957
|
+
contents: read
|
|
958
|
+
|
|
959
|
+
steps:
|
|
960
|
+
- name: Checkout code
|
|
961
|
+
uses: actions/checkout@v3
|
|
962
|
+
|
|
963
|
+
- name: Setup Node
|
|
964
|
+
uses: actions/setup-node@v3
|
|
965
|
+
with:
|
|
966
|
+
node-version: 22
|
|
967
|
+
|
|
968
|
+
- name: Install dependencies
|
|
969
|
+
run: npm install
|
|
970
|
+
|
|
971
|
+
- name: Build project
|
|
972
|
+
run: npm run build
|
|
973
|
+
|
|
974
|
+
- name: Viverse Login
|
|
975
|
+
run: npx viverse-cli auth login -e \${{ secrets.VIVERSE_EMAIL }} -p \${{ secrets.VIVERSE_PASSWORD }}
|
|
976
|
+
|
|
977
|
+
- name: Deploy to Viverse
|
|
978
|
+
run: npx viverse-cli app publish ./dist --auto-create-app --name ${generator.options.name}
|
|
979
|
+
|
|
980
|
+
`
|
|
981
|
+
});
|
|
982
|
+
generator.addDependency("@viverse/cli", "^0.9.5-beta.8");
|
|
983
|
+
generator.inject(
|
|
984
|
+
"readme-start",
|
|
985
|
+
`A GitHub CI/CD workflow for publishing to Viverse is configured.
|
|
986
|
+
|
|
987
|
+
To use publish to viverse via the CI/CD workflow:
|
|
988
|
+
1. Set \`VIVERSE_EMAIL\` and \`VIVERSE_PASSWORD\` secrets in your repository settings under \`Secrets and Variables\` > \`Actions\` > \`New repository secret\`
|
|
989
|
+
2. Manually trigger the "Deploy to Viverse" workflow or push to the main branch
|
|
990
|
+
|
|
991
|
+
**Manual CLI Upload:**
|
|
992
|
+
You can also upload your project manually using the Viverse CLI:
|
|
993
|
+
\`\`\`bash
|
|
994
|
+
viverse-cli auth login -e <email> -p <password>
|
|
995
|
+
npm run build
|
|
996
|
+
viverse-cli app publish ./dist --auto-create-app --name ${generator.options.name}
|
|
997
|
+
\`\`\`
|
|
998
|
+
`
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
async function getLatestNpmVersion(packageName, fallback) {
|
|
1003
|
+
try {
|
|
1004
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
1005
|
+
const data = await response.json();
|
|
1006
|
+
return data.version;
|
|
1007
|
+
} catch {
|
|
1008
|
+
return fallback;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
async function getLatestPnpmVersion() {
|
|
1012
|
+
return getLatestNpmVersion("pnpm", "10.11.0");
|
|
1013
|
+
}
|
|
1014
|
+
async function getLatestNodeVersion() {
|
|
1015
|
+
try {
|
|
1016
|
+
const response = await fetch("https://nodejs.org/dist/index.json");
|
|
1017
|
+
const data = await response.json();
|
|
1018
|
+
const ltsVersion = data.find((v) => v.lts);
|
|
1019
|
+
if (ltsVersion) {
|
|
1020
|
+
return ltsVersion.version.replace(/^v/, "");
|
|
1021
|
+
}
|
|
1022
|
+
return "22.0.0";
|
|
1023
|
+
} catch {
|
|
1024
|
+
return "22.0.0";
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function generateRandomName() {
|
|
1028
|
+
const adjectives = [
|
|
1029
|
+
"red",
|
|
1030
|
+
"blue",
|
|
1031
|
+
"green",
|
|
1032
|
+
"yellow",
|
|
1033
|
+
"purple",
|
|
1034
|
+
"orange",
|
|
1035
|
+
"pink",
|
|
1036
|
+
"black",
|
|
1037
|
+
"white",
|
|
1038
|
+
"tiny",
|
|
1039
|
+
"big",
|
|
1040
|
+
"small",
|
|
1041
|
+
"large",
|
|
1042
|
+
"huge",
|
|
1043
|
+
"giant",
|
|
1044
|
+
"mini",
|
|
1045
|
+
"mega",
|
|
1046
|
+
"super",
|
|
1047
|
+
"happy",
|
|
1048
|
+
"sad",
|
|
1049
|
+
"angry",
|
|
1050
|
+
"calm",
|
|
1051
|
+
"quiet",
|
|
1052
|
+
"loud",
|
|
1053
|
+
"silent",
|
|
1054
|
+
"noisy",
|
|
1055
|
+
"shiny",
|
|
1056
|
+
"dull",
|
|
1057
|
+
"bright",
|
|
1058
|
+
"dark",
|
|
1059
|
+
"fuzzy",
|
|
1060
|
+
"smooth",
|
|
1061
|
+
"rough",
|
|
1062
|
+
"soft"
|
|
1063
|
+
];
|
|
1064
|
+
const nouns = [
|
|
1065
|
+
"apple",
|
|
1066
|
+
"banana",
|
|
1067
|
+
"cherry",
|
|
1068
|
+
"date",
|
|
1069
|
+
"elderberry",
|
|
1070
|
+
"fig",
|
|
1071
|
+
"grape",
|
|
1072
|
+
"honeydew",
|
|
1073
|
+
"cat",
|
|
1074
|
+
"dog",
|
|
1075
|
+
"elephant",
|
|
1076
|
+
"fox",
|
|
1077
|
+
"giraffe",
|
|
1078
|
+
"horse",
|
|
1079
|
+
"iguana",
|
|
1080
|
+
"jaguar",
|
|
1081
|
+
"mountain",
|
|
1082
|
+
"river",
|
|
1083
|
+
"ocean",
|
|
1084
|
+
"desert",
|
|
1085
|
+
"forest",
|
|
1086
|
+
"jungle",
|
|
1087
|
+
"meadow",
|
|
1088
|
+
"valley",
|
|
1089
|
+
"star",
|
|
1090
|
+
"moon",
|
|
1091
|
+
"sun",
|
|
1092
|
+
"planet",
|
|
1093
|
+
"comet",
|
|
1094
|
+
"asteroid",
|
|
1095
|
+
"galaxy",
|
|
1096
|
+
"universe"
|
|
1097
|
+
];
|
|
1098
|
+
const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)];
|
|
1099
|
+
const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
|
|
1100
|
+
return `${randomAdjective}-${randomNoun}`;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function getLanguageFromTemplate(template) {
|
|
1104
|
+
return template.endsWith("-js") ? "javascript" : "typescript";
|
|
1105
|
+
}
|
|
1106
|
+
function getBaseTemplate(template) {
|
|
1107
|
+
return template.replace("-js", "");
|
|
1108
|
+
}
|
|
1109
|
+
function generate(options) {
|
|
1110
|
+
const clonedOptions = structuredClone(options);
|
|
1111
|
+
const template = clonedOptions.template ?? "vanilla";
|
|
1112
|
+
const baseTemplate = getBaseTemplate(template);
|
|
1113
|
+
const language = getLanguageFromTemplate(template);
|
|
1114
|
+
const isVanilla = baseTemplate === "vanilla";
|
|
1115
|
+
const isReact = baseTemplate === "react";
|
|
1116
|
+
const isR3f = baseTemplate === "r3f";
|
|
1117
|
+
const isLibrary = clonedOptions.projectType === "library";
|
|
1118
|
+
const libraryBundler = clonedOptions.libraryBundler ?? "unbuild";
|
|
1119
|
+
const files = {
|
|
1120
|
+
...clonedOptions.files
|
|
1121
|
+
};
|
|
1122
|
+
const replacements = clonedOptions.replacements ?? [];
|
|
1123
|
+
const versions = clonedOptions.versions ?? {};
|
|
1124
|
+
const dependencies = {
|
|
1125
|
+
...clonedOptions.dependencies
|
|
1126
|
+
};
|
|
1127
|
+
const devDependencies = {};
|
|
1128
|
+
const peerDependencies = {};
|
|
1129
|
+
if (!isLibrary) {
|
|
1130
|
+
devDependencies.vite = versions.vite ? `^${versions.vite}` : "^6.3.4";
|
|
1131
|
+
}
|
|
1132
|
+
if (isReact || isR3f) {
|
|
1133
|
+
if (isLibrary) {
|
|
1134
|
+
peerDependencies["react"] = "^18.0.0 || ^19.0.0";
|
|
1135
|
+
peerDependencies["react-dom"] = "^18.0.0 || ^19.0.0";
|
|
1136
|
+
} else {
|
|
1137
|
+
dependencies["react"] = "^19.0.0";
|
|
1138
|
+
dependencies["react-dom"] = "^19.0.0";
|
|
1139
|
+
devDependencies["@vitejs/plugin-react"] = "^4.4.1";
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (isR3f) {
|
|
1143
|
+
if (isLibrary) {
|
|
1144
|
+
peerDependencies["three"] = ">=0.150.0";
|
|
1145
|
+
peerDependencies["@react-three/fiber"] = "^8.0.0 || ^9.0.0";
|
|
1146
|
+
} else {
|
|
1147
|
+
dependencies["three"] = "~0.175.0";
|
|
1148
|
+
dependencies["@react-three/fiber"] = "^9.0.0";
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (language === "typescript") {
|
|
1152
|
+
const tsConfigNode = {
|
|
1153
|
+
$schema: "https://json.schemastore.org/tsconfig",
|
|
1154
|
+
compilerOptions: {
|
|
1155
|
+
target: "ESNext",
|
|
1156
|
+
module: "ESNext",
|
|
1157
|
+
moduleResolution: "bundler",
|
|
1158
|
+
lib: ["ESNext"],
|
|
1159
|
+
esModuleInterop: true,
|
|
1160
|
+
allowSyntheticDefaultImports: true,
|
|
1161
|
+
strict: true,
|
|
1162
|
+
skipLibCheck: true,
|
|
1163
|
+
composite: true,
|
|
1164
|
+
rewriteRelativeImportExtensions: true,
|
|
1165
|
+
erasableSyntaxOnly: true
|
|
1166
|
+
},
|
|
1167
|
+
include: ["src", "tests"]
|
|
1168
|
+
};
|
|
1169
|
+
files["tsconfig.node.json"] = {
|
|
1170
|
+
type: "text",
|
|
1171
|
+
content: JSON.stringify(tsConfigNode, null, 2)
|
|
1172
|
+
};
|
|
1173
|
+
const tsConfig = {
|
|
1174
|
+
$schema: "https://json.schemastore.org/tsconfig",
|
|
1175
|
+
extends: "./tsconfig.node.json",
|
|
1176
|
+
compilerOptions: {},
|
|
1177
|
+
include: ["*.config.ts"],
|
|
1178
|
+
references: [{ path: "./tsconfig.node.json" }]
|
|
1179
|
+
};
|
|
1180
|
+
if (isReact || isR3f) {
|
|
1181
|
+
tsConfig.compilerOptions.jsx = "react-jsx";
|
|
1182
|
+
devDependencies["@types/react"] = "^19.0.0";
|
|
1183
|
+
devDependencies["@types/react-dom"] = "^19.0.0";
|
|
1184
|
+
}
|
|
1185
|
+
if (isR3f) {
|
|
1186
|
+
devDependencies["@types/three"] = "~0.175.0";
|
|
1187
|
+
}
|
|
1188
|
+
files["tsconfig.json"] = {
|
|
1189
|
+
type: "text",
|
|
1190
|
+
content: JSON.stringify(tsConfig, null, 2)
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
const codeSnippets = {};
|
|
1194
|
+
const vscodeSettings = {};
|
|
1195
|
+
const scripts = isLibrary ? {} : {
|
|
1196
|
+
dev: "vite",
|
|
1197
|
+
build: "vite build"
|
|
1198
|
+
};
|
|
1199
|
+
if (!isLibrary && (isReact || isR3f)) {
|
|
1200
|
+
codeSnippets["vite-config-import"] = [
|
|
1201
|
+
"import react from '@vitejs/plugin-react'"
|
|
1202
|
+
];
|
|
1203
|
+
}
|
|
1204
|
+
if (!isLibrary && isR3f) {
|
|
1205
|
+
codeSnippets["import"] = [`import { Canvas } from "@react-three/fiber"`];
|
|
1206
|
+
}
|
|
1207
|
+
const defaultName = isVanilla ? "vanilla-app" : isReact ? "react-app" : "react-three-app";
|
|
1208
|
+
const name = clonedOptions.name ?? defaultName;
|
|
1209
|
+
let viteConfig = {
|
|
1210
|
+
base: "./"
|
|
1211
|
+
};
|
|
1212
|
+
if (!isLibrary && (isReact || isR3f)) {
|
|
1213
|
+
viteConfig.plugins = ["$raw:react()"];
|
|
1214
|
+
}
|
|
1215
|
+
if (!isLibrary && isR3f) {
|
|
1216
|
+
viteConfig.resolve = { dedupe: ["three"] };
|
|
1217
|
+
}
|
|
1218
|
+
const generator = {
|
|
1219
|
+
options: clonedOptions,
|
|
1220
|
+
versions,
|
|
1221
|
+
addDependency(name2, semver) {
|
|
1222
|
+
dependencies[name2];
|
|
1223
|
+
dependencies[name2] = semver;
|
|
1224
|
+
},
|
|
1225
|
+
addDevDependency(name2, semver) {
|
|
1226
|
+
devDependencies[name2];
|
|
1227
|
+
devDependencies[name2] = semver;
|
|
1228
|
+
},
|
|
1229
|
+
addFile(path, content) {
|
|
1230
|
+
files[path] = content;
|
|
1231
|
+
},
|
|
1232
|
+
addScript(name2, command) {
|
|
1233
|
+
scripts[name2] = command;
|
|
1234
|
+
},
|
|
1235
|
+
inject(location, code) {
|
|
1236
|
+
let entries = codeSnippets[location];
|
|
1237
|
+
if (entries == null) {
|
|
1238
|
+
codeSnippets[location] = entries = [];
|
|
1239
|
+
}
|
|
1240
|
+
entries.push(code);
|
|
1241
|
+
},
|
|
1242
|
+
replace(search, replace) {
|
|
1243
|
+
replacements.push({ search, replace });
|
|
1244
|
+
},
|
|
1245
|
+
configureVite(config) {
|
|
1246
|
+
viteConfig = merge(viteConfig, config);
|
|
1247
|
+
},
|
|
1248
|
+
addVscodeSetting(key, value) {
|
|
1249
|
+
vscodeSettings[key] = value;
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
if (isR3f) {
|
|
1253
|
+
generateDrei(generator, clonedOptions.drei);
|
|
1254
|
+
generateHandle(generator, clonedOptions.handle);
|
|
1255
|
+
generateKoota(generator, clonedOptions.koota);
|
|
1256
|
+
generateLeva(generator, clonedOptions.leva);
|
|
1257
|
+
generateOffscreen(generator, clonedOptions.offscreen);
|
|
1258
|
+
generatePostprocessing(generator, clonedOptions.postprocessing);
|
|
1259
|
+
generateRapier(generator, clonedOptions.rapier);
|
|
1260
|
+
generateUikit(generator, clonedOptions.uikit);
|
|
1261
|
+
generateXr(generator, clonedOptions.xr);
|
|
1262
|
+
generateZustand(generator, clonedOptions.zustand);
|
|
1263
|
+
generateFiber(generator, clonedOptions.fiber);
|
|
1264
|
+
generateTriplex(generator, clonedOptions.triplex);
|
|
1265
|
+
generateViverse(generator, clonedOptions.viverse);
|
|
1266
|
+
}
|
|
1267
|
+
if (!isLibrary) {
|
|
1268
|
+
generateGithubPages(generator, clonedOptions.githubPages);
|
|
1269
|
+
}
|
|
1270
|
+
if (isLibrary) {
|
|
1271
|
+
if (libraryBundler === "unbuild") {
|
|
1272
|
+
generateUnbuild(generator);
|
|
1273
|
+
} else if (libraryBundler === "tsdown") {
|
|
1274
|
+
generateTsdown(generator);
|
|
1275
|
+
}
|
|
1276
|
+
const packageManager2 = clonedOptions.packageManager ?? "pnpm";
|
|
1277
|
+
generator.addScript(
|
|
1278
|
+
"release",
|
|
1279
|
+
`${packageManager2} run build && ${packageManager2} publish`
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
generateVitest(generator);
|
|
1283
|
+
const linter = clonedOptions.linter;
|
|
1284
|
+
const formatter = clonedOptions.formatter;
|
|
1285
|
+
if (linter === "eslint") {
|
|
1286
|
+
generateEslint(generator);
|
|
1287
|
+
generator.addVscodeSetting("biome.enabled", false);
|
|
1288
|
+
generator.addVscodeSetting("oxc.enable", false);
|
|
1289
|
+
} else if (linter === "oxlint") {
|
|
1290
|
+
generateOxlint(generator);
|
|
1291
|
+
generator.addVscodeSetting("eslint.enable", false);
|
|
1292
|
+
generator.addVscodeSetting("biome.enabled", false);
|
|
1293
|
+
} else if (linter === "biome") {
|
|
1294
|
+
generateBiome(generator, {
|
|
1295
|
+
linter: true,
|
|
1296
|
+
formatter: formatter === "biome"
|
|
1297
|
+
});
|
|
1298
|
+
generator.addVscodeSetting("eslint.enable", false);
|
|
1299
|
+
generator.addVscodeSetting("oxc.enable", false);
|
|
1300
|
+
}
|
|
1301
|
+
if (formatter === "prettier") {
|
|
1302
|
+
generatePrettier(generator);
|
|
1303
|
+
} else if (formatter === "oxfmt") {
|
|
1304
|
+
generateOxfmt(generator);
|
|
1305
|
+
} else if (formatter === "biome" && linter !== "biome") {
|
|
1306
|
+
generateBiome(generator, { linter: false, formatter: true });
|
|
1307
|
+
generator.addVscodeSetting("eslint.enable", false);
|
|
1308
|
+
generator.addVscodeSetting("oxc.enable", false);
|
|
1309
|
+
}
|
|
1310
|
+
for (const { code, location } of clonedOptions.injections ?? []) {
|
|
1311
|
+
generator.inject(location, code);
|
|
1312
|
+
}
|
|
1313
|
+
if (!isLibrary) {
|
|
1314
|
+
const viteConfigContent = [
|
|
1315
|
+
`import { defineConfig } from 'vite'`,
|
|
1316
|
+
...codeSnippets["vite-config-import"] ?? [],
|
|
1317
|
+
`export default defineConfig(${JSON.stringify(viteConfig).replace(
|
|
1318
|
+
/"\$raw:([^"]+)"/g,
|
|
1319
|
+
(_, raw) => raw
|
|
1320
|
+
)})`
|
|
1321
|
+
].join("\n");
|
|
1322
|
+
files["vite.config.ts"] = { type: "text", content: viteConfigContent };
|
|
1323
|
+
}
|
|
1324
|
+
const packageManager = options.packageManager ?? "pnpm";
|
|
1325
|
+
const isPnpm = packageManager === "pnpm";
|
|
1326
|
+
const ext = language === "typescript" ? "ts" : "js";
|
|
1327
|
+
const jsxExt = language === "typescript" ? "tsx" : "jsx";
|
|
1328
|
+
const packageJson = {
|
|
1329
|
+
name,
|
|
1330
|
+
type: "module"
|
|
1331
|
+
};
|
|
1332
|
+
if (isLibrary) {
|
|
1333
|
+
packageJson.main = "./dist/index.mjs";
|
|
1334
|
+
packageJson.module = "./dist/index.mjs";
|
|
1335
|
+
if (language === "typescript") {
|
|
1336
|
+
packageJson.types = "./dist/index.d.ts";
|
|
1337
|
+
}
|
|
1338
|
+
packageJson.exports = {
|
|
1339
|
+
".": {
|
|
1340
|
+
...language === "typescript" && { types: "./dist/index.d.ts" },
|
|
1341
|
+
import: "./dist/index.mjs",
|
|
1342
|
+
require: "./dist/index.cjs"
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
packageJson.files = ["dist"];
|
|
1346
|
+
}
|
|
1347
|
+
packageJson.scripts = scripts;
|
|
1348
|
+
packageJson.dependencies = dependencies;
|
|
1349
|
+
if (Object.keys(devDependencies).length > 0) {
|
|
1350
|
+
packageJson.devDependencies = devDependencies;
|
|
1351
|
+
}
|
|
1352
|
+
if (isLibrary && Object.keys(peerDependencies).length > 0) {
|
|
1353
|
+
packageJson.peerDependencies = peerDependencies;
|
|
1354
|
+
}
|
|
1355
|
+
const engines = {};
|
|
1356
|
+
if (isPnpm) {
|
|
1357
|
+
const pnpmVersion = options.pnpmVersion ?? "10.11.0";
|
|
1358
|
+
const majorVersion = pnpmVersion.split(".")[0];
|
|
1359
|
+
engines.pnpm = `>=${majorVersion}.0.0`;
|
|
1360
|
+
packageJson.packageManager = `pnpm@${pnpmVersion}`;
|
|
1361
|
+
}
|
|
1362
|
+
if (options.nodeVersion) {
|
|
1363
|
+
const majorVersion = options.nodeVersion.split(".")[0];
|
|
1364
|
+
engines.node = `>=${majorVersion}.0.0`;
|
|
1365
|
+
}
|
|
1366
|
+
if (Object.keys(engines).length > 0) {
|
|
1367
|
+
packageJson.engines = engines;
|
|
1368
|
+
}
|
|
1369
|
+
files["package.json"] = {
|
|
1370
|
+
type: "text",
|
|
1371
|
+
content: JSON.stringify(packageJson, null, 2)
|
|
1372
|
+
};
|
|
1373
|
+
if (isPnpm) {
|
|
1374
|
+
const manageVersions = options.pnpmManageVersions ?? true;
|
|
1375
|
+
const workspaceLines = [];
|
|
1376
|
+
if (manageVersions) {
|
|
1377
|
+
workspaceLines.push("manage-package-manager-versions: true", "");
|
|
1378
|
+
}
|
|
1379
|
+
workspaceLines.push("onlyBuiltDependencies:", " - esbuild");
|
|
1380
|
+
files["pnpm-workspace.yaml"] = {
|
|
1381
|
+
type: "text",
|
|
1382
|
+
content: workspaceLines.join("\n")
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
files[".gitignore"] = {
|
|
1386
|
+
type: "text",
|
|
1387
|
+
content: ["node_modules", "dist", "*.tsbuildinfo"].join("\n")
|
|
1388
|
+
};
|
|
1389
|
+
files[".gitattributes"] = { type: "text", content: GitAttributes };
|
|
1390
|
+
codeSnippets["readme-libraries"] ??= [];
|
|
1391
|
+
codeSnippets["readme-commands"] ??= [];
|
|
1392
|
+
if (isLibrary) ; else if (isVanilla) {
|
|
1393
|
+
codeSnippets["readme-libraries"].unshift(
|
|
1394
|
+
`[Vite](https://vitejs.dev/) - Next generation frontend tooling`
|
|
1395
|
+
);
|
|
1396
|
+
} else if (isReact) {
|
|
1397
|
+
codeSnippets["readme-libraries"].unshift(
|
|
1398
|
+
`[React](https://react.dev/) - A JavaScript library for building user interfaces`,
|
|
1399
|
+
`[Vite](https://vitejs.dev/) - Next generation frontend tooling`
|
|
1400
|
+
);
|
|
1401
|
+
} else {
|
|
1402
|
+
codeSnippets["readme-libraries"].unshift(
|
|
1403
|
+
`[React](https://react.dev/) - A JavaScript library for building user interfaces`,
|
|
1404
|
+
`[Three.js](https://threejs.org/) - JavaScript 3D library`,
|
|
1405
|
+
`[@react-three/fiber](https://docs.pmnd.rs/react-three-fiber) - lets you create Three.js scenes using React components`
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
if (isLibrary) {
|
|
1409
|
+
codeSnippets["readme-commands"].unshift(
|
|
1410
|
+
`\`${packageManager} install\` to install the dependencies`,
|
|
1411
|
+
`\`${packageManager} run build\` to build the library into the \`dist\` folder`,
|
|
1412
|
+
`\`${packageManager} run test\` to run the tests`,
|
|
1413
|
+
`\`${packageManager} run release\` to build and publish to npm`
|
|
1414
|
+
);
|
|
1415
|
+
} else {
|
|
1416
|
+
codeSnippets["readme-commands"].unshift(
|
|
1417
|
+
`\`${packageManager} install\` to install the dependencies`,
|
|
1418
|
+
`\`${packageManager} run dev\` to run the development server and preview the app with live updates`,
|
|
1419
|
+
`\`${packageManager} run build\` to build the app into the \`dist\` folder`,
|
|
1420
|
+
`\`${packageManager} run test\` to run the tests`
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
let architectureDesc;
|
|
1424
|
+
if (isLibrary) {
|
|
1425
|
+
architectureDesc = [
|
|
1426
|
+
`- \`src/index.${isReact || isR3f ? jsxExt : ext}\` is the main entry point for your library exports`,
|
|
1427
|
+
`- Add your library code in the \`src\` folder`,
|
|
1428
|
+
`- \`tests/\` contains your test files`
|
|
1429
|
+
];
|
|
1430
|
+
} else if (isVanilla) {
|
|
1431
|
+
architectureDesc = [
|
|
1432
|
+
`- \`src/main.${ext}\` is the entry point for your application`,
|
|
1433
|
+
`- \`tests/\` contains your test files`,
|
|
1434
|
+
`- Static assets can be placed in the \`public\` folder`
|
|
1435
|
+
];
|
|
1436
|
+
} else if (isReact) {
|
|
1437
|
+
architectureDesc = [
|
|
1438
|
+
`- \`src/app.${jsxExt}\` defines the main application component`,
|
|
1439
|
+
`- \`src/index.${jsxExt}\` renders the React app into the DOM`,
|
|
1440
|
+
`- \`tests/\` contains your test files`,
|
|
1441
|
+
`- Static assets can be placed in the \`public\` folder`
|
|
1442
|
+
];
|
|
1443
|
+
} else {
|
|
1444
|
+
architectureDesc = [
|
|
1445
|
+
`- \`app.${jsxExt}\` defines the main application component containing your 3D content`,
|
|
1446
|
+
`- Modify the content inside the \`<Canvas>\` component to change what is visible on screen`,
|
|
1447
|
+
`- \`tests/\` contains your test files`,
|
|
1448
|
+
`- Static assets can be placed in the \`public\` folder`
|
|
1449
|
+
];
|
|
1450
|
+
}
|
|
1451
|
+
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.`;
|
|
1452
|
+
files[`README.md`] = {
|
|
1453
|
+
type: "text",
|
|
1454
|
+
content: [
|
|
1455
|
+
`# ${name}`,
|
|
1456
|
+
`This ${isLibrary ? "library" : "project"} was generated with create-krispya`,
|
|
1457
|
+
...codeSnippets["readme-start"] ?? [],
|
|
1458
|
+
"\n",
|
|
1459
|
+
`## Project Architecture`,
|
|
1460
|
+
bundlerDescription,
|
|
1461
|
+
...architectureDesc,
|
|
1462
|
+
"\n",
|
|
1463
|
+
`## Libraries`,
|
|
1464
|
+
`The following libraries are used - checkout the linked docs to learn more`,
|
|
1465
|
+
...(codeSnippets["readme-libraries"] ?? []).map(
|
|
1466
|
+
(library) => `- ${library}`
|
|
1467
|
+
),
|
|
1468
|
+
"\n",
|
|
1469
|
+
codeSnippets["readme-tools"] && `## Tools`,
|
|
1470
|
+
...(codeSnippets["readme-tools"] ?? []).map((tool) => `- ${tool}`),
|
|
1471
|
+
codeSnippets["readme-tools"] && `
|
|
1472
|
+
`,
|
|
1473
|
+
`## Development Commands`,
|
|
1474
|
+
...(codeSnippets["readme-commands"] ?? []).map(
|
|
1475
|
+
(command) => `- ${command}`
|
|
1476
|
+
),
|
|
1477
|
+
...codeSnippets["readme-end"] ?? []
|
|
1478
|
+
].filter(Boolean).join("\n")
|
|
1479
|
+
};
|
|
1480
|
+
if (isLibrary) {
|
|
1481
|
+
const libExt = isReact || isR3f ? jsxExt : ext;
|
|
1482
|
+
let libContent;
|
|
1483
|
+
if (isVanilla) {
|
|
1484
|
+
libContent = [
|
|
1485
|
+
`// Library entry point`,
|
|
1486
|
+
`export function hello(name: string = "world"): string {`,
|
|
1487
|
+
` return \`Hello, \${name}!\``,
|
|
1488
|
+
`}`
|
|
1489
|
+
].join("\n");
|
|
1490
|
+
} else if (isReact) {
|
|
1491
|
+
libContent = [
|
|
1492
|
+
`// Library entry point`,
|
|
1493
|
+
`export function MyComponent({ message = "Hello from library!" }: { message?: string }) {`,
|
|
1494
|
+
` return <div>{message}</div>`,
|
|
1495
|
+
`}`
|
|
1496
|
+
].join("\n");
|
|
1497
|
+
} else {
|
|
1498
|
+
libContent = [
|
|
1499
|
+
`// Library entry point`,
|
|
1500
|
+
`export function MyMesh({ color = "orange" }: { color?: string }) {`,
|
|
1501
|
+
` return (`,
|
|
1502
|
+
` <mesh>`,
|
|
1503
|
+
` <boxGeometry />`,
|
|
1504
|
+
` <meshStandardMaterial color={color} />`,
|
|
1505
|
+
` </mesh>`,
|
|
1506
|
+
` )`,
|
|
1507
|
+
`}`
|
|
1508
|
+
].join("\n");
|
|
1509
|
+
}
|
|
1510
|
+
files[`src/index.${libExt}`] = { type: "text", content: libContent };
|
|
1511
|
+
} else if (isVanilla) {
|
|
1512
|
+
files[`src/main.${ext}`] = { type: "text", content: ViteIndexContent };
|
|
1513
|
+
files["src/style.css"] = { type: "text", content: ViteStyleContent };
|
|
1514
|
+
const indexHtml = ViteHtmlContent.replace(
|
|
1515
|
+
"$indexPath",
|
|
1516
|
+
`./src/main.${ext}`
|
|
1517
|
+
).replace("$title", name);
|
|
1518
|
+
files["index.html"] = { type: "text", content: indexHtml };
|
|
1519
|
+
} else {
|
|
1520
|
+
files[`src/index.tsx`] = { type: "text", content: IndexContent };
|
|
1521
|
+
const indexHtml = HtmlContent.replace(
|
|
1522
|
+
"$indexPath",
|
|
1523
|
+
language === "javascript" ? "./src/index.jsx" : "./src/index.tsx"
|
|
1524
|
+
).replace("$title", name);
|
|
1525
|
+
files["index.html"] = { type: "text", content: indexHtml };
|
|
1526
|
+
codeSnippets["dom-end"]?.reverse();
|
|
1527
|
+
codeSnippets["global-end"]?.reverse();
|
|
1528
|
+
codeSnippets["scene-end"]?.reverse();
|
|
1529
|
+
let appCode;
|
|
1530
|
+
if (isReact) {
|
|
1531
|
+
appCode = [
|
|
1532
|
+
...codeSnippets["import"] ?? [],
|
|
1533
|
+
...codeSnippets["global-start"] ?? [],
|
|
1534
|
+
`export function App() {`,
|
|
1535
|
+
" return (",
|
|
1536
|
+
' <div style={{ padding: "2rem" }}>',
|
|
1537
|
+
" <h1>Hello React!</h1>",
|
|
1538
|
+
" <p>Edit src/app.tsx and save to see changes.</p>",
|
|
1539
|
+
" </div>",
|
|
1540
|
+
" )",
|
|
1541
|
+
"}",
|
|
1542
|
+
...codeSnippets["global-end"] ?? []
|
|
1543
|
+
].join("\n");
|
|
1544
|
+
} else {
|
|
1545
|
+
appCode = [
|
|
1546
|
+
...codeSnippets["import"] ?? [],
|
|
1547
|
+
...codeSnippets["global-start"] ?? [],
|
|
1548
|
+
`export function App() {`,
|
|
1549
|
+
" return <>",
|
|
1550
|
+
...codeSnippets["dom-start"] ?? [],
|
|
1551
|
+
...codeSnippets["dom"] ?? [],
|
|
1552
|
+
" <Canvas>",
|
|
1553
|
+
...codeSnippets["scene-start"] ?? [],
|
|
1554
|
+
...codeSnippets["scene"] ?? [],
|
|
1555
|
+
...codeSnippets["scene-end"] ?? [],
|
|
1556
|
+
" </Canvas>",
|
|
1557
|
+
...codeSnippets["dom-end"] ?? [],
|
|
1558
|
+
" </>",
|
|
1559
|
+
"}",
|
|
1560
|
+
...codeSnippets["global-end"] ?? []
|
|
1561
|
+
].join("\n");
|
|
1562
|
+
}
|
|
1563
|
+
for (const { search, replace } of replacements) {
|
|
1564
|
+
appCode = appCode.replace(search, replace);
|
|
1565
|
+
}
|
|
1566
|
+
files[`src/app.tsx`] = { type: "text", content: appCode };
|
|
1567
|
+
}
|
|
1568
|
+
if (isLibrary) {
|
|
1569
|
+
const testExt = isReact || isR3f ? jsxExt : ext;
|
|
1570
|
+
let testContent;
|
|
1571
|
+
if (isVanilla) {
|
|
1572
|
+
testContent = [
|
|
1573
|
+
`import { describe, it, expect } from "vitest"`,
|
|
1574
|
+
`import { hello } from "../src/index.js"`,
|
|
1575
|
+
``,
|
|
1576
|
+
`describe("hello", () => {`,
|
|
1577
|
+
` it("returns greeting with default name", () => {`,
|
|
1578
|
+
` expect(hello()).toBe("Hello, world!")`,
|
|
1579
|
+
` })`,
|
|
1580
|
+
``,
|
|
1581
|
+
` it("returns greeting with custom name", () => {`,
|
|
1582
|
+
` expect(hello("vitest")).toBe("Hello, vitest!")`,
|
|
1583
|
+
` })`,
|
|
1584
|
+
`})`
|
|
1585
|
+
].join("\n");
|
|
1586
|
+
} else if (isReact) {
|
|
1587
|
+
testContent = [
|
|
1588
|
+
`import { describe, it, expect } from "vitest"`,
|
|
1589
|
+
`import { render, screen } from "@testing-library/react"`,
|
|
1590
|
+
`import { MyComponent } from "../src/index.js"`,
|
|
1591
|
+
``,
|
|
1592
|
+
`describe("MyComponent", () => {`,
|
|
1593
|
+
` it("renders with default message", () => {`,
|
|
1594
|
+
` render(<MyComponent />)`,
|
|
1595
|
+
` expect(screen.getByText("Hello from library!")).toBeDefined()`,
|
|
1596
|
+
` })`,
|
|
1597
|
+
``,
|
|
1598
|
+
` it("renders with custom message", () => {`,
|
|
1599
|
+
` render(<MyComponent message="Custom message" />)`,
|
|
1600
|
+
` expect(screen.getByText("Custom message")).toBeDefined()`,
|
|
1601
|
+
` })`,
|
|
1602
|
+
`})`
|
|
1603
|
+
].join("\n");
|
|
1604
|
+
} else {
|
|
1605
|
+
testContent = [
|
|
1606
|
+
`import { describe, it, expect } from "vitest"`,
|
|
1607
|
+
`import { MyMesh } from "../src/index.js"`,
|
|
1608
|
+
``,
|
|
1609
|
+
`describe("MyMesh", () => {`,
|
|
1610
|
+
` it("is defined", () => {`,
|
|
1611
|
+
` expect(MyMesh).toBeDefined()`,
|
|
1612
|
+
` })`,
|
|
1613
|
+
`})`
|
|
1614
|
+
].join("\n");
|
|
1615
|
+
}
|
|
1616
|
+
files[`tests/index.test.${testExt}`] = {
|
|
1617
|
+
type: "text",
|
|
1618
|
+
content: testContent
|
|
1619
|
+
};
|
|
1620
|
+
} else if (isVanilla) {
|
|
1621
|
+
const testContent = [
|
|
1622
|
+
`import { describe, it, expect } from "vitest"`,
|
|
1623
|
+
``,
|
|
1624
|
+
`describe("example", () => {`,
|
|
1625
|
+
` it("works", () => {`,
|
|
1626
|
+
` expect(1 + 1).toBe(2)`,
|
|
1627
|
+
` })`,
|
|
1628
|
+
`})`
|
|
1629
|
+
].join("\n");
|
|
1630
|
+
files[`tests/main.test.${ext}`] = { type: "text", content: testContent };
|
|
1631
|
+
} else if (isReact) {
|
|
1632
|
+
const testContent = [
|
|
1633
|
+
`import { describe, it, expect } from "vitest"`,
|
|
1634
|
+
`import { render, screen } from "@testing-library/react"`,
|
|
1635
|
+
`import { App } from "../src/app.js"`,
|
|
1636
|
+
``,
|
|
1637
|
+
`describe("App", () => {`,
|
|
1638
|
+
` it("renders heading", () => {`,
|
|
1639
|
+
` render(<App />)`,
|
|
1640
|
+
` expect(screen.getByText("Hello React!")).toBeDefined()`,
|
|
1641
|
+
` })`,
|
|
1642
|
+
`})`
|
|
1643
|
+
].join("\n");
|
|
1644
|
+
files[`tests/app.test.${jsxExt}`] = { type: "text", content: testContent };
|
|
1645
|
+
} else {
|
|
1646
|
+
const testContent = [
|
|
1647
|
+
`import { describe, it, expect } from "vitest"`,
|
|
1648
|
+
`import { App } from "../src/app.js"`,
|
|
1649
|
+
``,
|
|
1650
|
+
`describe("App", () => {`,
|
|
1651
|
+
` it("is defined", () => {`,
|
|
1652
|
+
` expect(App).toBeDefined()`,
|
|
1653
|
+
` })`,
|
|
1654
|
+
`})`
|
|
1655
|
+
].join("\n");
|
|
1656
|
+
files[`tests/app.test.${jsxExt}`] = { type: "text", content: testContent };
|
|
1657
|
+
}
|
|
1658
|
+
if (codeSnippets["vscode-extension-suggestion"]?.length) {
|
|
1659
|
+
const uniqueRecommendations = [
|
|
1660
|
+
...new Set(codeSnippets["vscode-extension-suggestion"])
|
|
1661
|
+
];
|
|
1662
|
+
files[".vscode/extensions.json"] = {
|
|
1663
|
+
type: "text",
|
|
1664
|
+
content: JSON.stringify(
|
|
1665
|
+
{
|
|
1666
|
+
recommendations: uniqueRecommendations
|
|
1667
|
+
},
|
|
1668
|
+
null,
|
|
1669
|
+
2
|
|
1670
|
+
)
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
if (Object.keys(vscodeSettings).length > 0) {
|
|
1674
|
+
files[".vscode/settings.json"] = {
|
|
1675
|
+
type: "text",
|
|
1676
|
+
content: JSON.stringify(vscodeSettings, null, " ")
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
return files;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
exports.generate = generate;
|
|
1683
|
+
exports.generateRandomName = generateRandomName;
|
|
1684
|
+
exports.getBaseTemplate = getBaseTemplate;
|
|
1685
|
+
exports.getLanguageFromTemplate = getLanguageFromTemplate;
|
|
1686
|
+
exports.getLatestNodeVersion = getLatestNodeVersion;
|
|
1687
|
+
exports.getLatestNpmVersion = getLatestNpmVersion;
|
|
1688
|
+
exports.getLatestPnpmVersion = getLatestPnpmVersion;
|