reset-framework-cli 1.2.2 → 1.2.4
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/LICENSE +20 -20
- package/README.md +25 -25
- package/package.json +7 -7
- package/src/commands/build.js +144 -144
- package/src/commands/dev.js +195 -195
- package/src/commands/doctor.js +140 -140
- package/src/commands/init.js +946 -946
- package/src/commands/package.js +68 -68
- package/src/index.js +195 -195
- package/src/lib/backend.js +123 -123
- package/src/lib/context.js +66 -66
- package/src/lib/framework.js +57 -57
- package/src/lib/logger.js +11 -11
- package/src/lib/output.js +283 -283
- package/src/lib/process.js +303 -303
- package/src/lib/project.js +893 -866
- package/src/lib/toolchain.js +62 -62
- package/src/lib/ui.js +244 -244
- package/templates/basic/README.md +15 -15
- package/templates/basic/frontend/README.md +73 -73
- package/templates/basic/frontend/eslint.config.js +23 -23
- package/templates/basic/frontend/index.html +13 -13
- package/templates/basic/frontend/package.json +31 -31
- package/templates/basic/frontend/public/icons.svg +24 -24
- package/templates/basic/frontend/src/App.css +216 -216
- package/templates/basic/frontend/src/App.tsx +77 -77
- package/templates/basic/frontend/src/assets/vite.svg +1 -1
- package/templates/basic/frontend/src/index.css +111 -111
- package/templates/basic/frontend/src/lib/reset.ts +1 -1
- package/templates/basic/frontend/src/main.tsx +10 -10
- package/templates/basic/frontend/tsconfig.app.json +28 -28
- package/templates/basic/frontend/tsconfig.json +7 -7
- package/templates/basic/frontend/tsconfig.node.json +26 -26
- package/templates/basic/frontend/vite.config.ts +16 -16
- package/templates/basic/reset.config.json +58 -58
package/src/commands/init.js
CHANGED
|
@@ -1,946 +1,946 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from "node:fs"
|
|
2
|
-
import { cp, mkdir, readFile, readdir, writeFile } from "node:fs/promises"
|
|
3
|
-
import path from "node:path"
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
captureCommandOutput,
|
|
7
|
-
getInstallCommandArgs,
|
|
8
|
-
resolvePackageManagerCommand,
|
|
9
|
-
runCommand
|
|
10
|
-
} from "../lib/process.js"
|
|
11
|
-
import {
|
|
12
|
-
makeAppMetadata,
|
|
13
|
-
resolveBackendDependencySpec,
|
|
14
|
-
resolveCliDependencySpec,
|
|
15
|
-
resolveFrameworkPaths,
|
|
16
|
-
resolveSdkDependencySpec
|
|
17
|
-
} from "../lib/project.js"
|
|
18
|
-
import {
|
|
19
|
-
createProgress,
|
|
20
|
-
isInteractiveSession,
|
|
21
|
-
printBanner,
|
|
22
|
-
printKeyValueTable,
|
|
23
|
-
printSection,
|
|
24
|
-
printStatusTable,
|
|
25
|
-
promptConfirm,
|
|
26
|
-
promptSelect,
|
|
27
|
-
promptText,
|
|
28
|
-
withPromptSession
|
|
29
|
-
} from "../lib/ui.js"
|
|
30
|
-
|
|
31
|
-
const sdkRuntimeReexport = `export * from '@reset-framework/sdk'
|
|
32
|
-
`
|
|
33
|
-
|
|
34
|
-
const backendTsconfig = `{
|
|
35
|
-
"compilerOptions": {
|
|
36
|
-
"target": "ES2023",
|
|
37
|
-
"module": "ESNext",
|
|
38
|
-
"moduleResolution": "bundler",
|
|
39
|
-
"strict": true,
|
|
40
|
-
"skipLibCheck": true,
|
|
41
|
-
"verbatimModuleSyntax": true,
|
|
42
|
-
"noEmit": true
|
|
43
|
-
},
|
|
44
|
-
"include": ["src"]
|
|
45
|
-
}
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
-
const backendStarterSource = `import { defineBackend } from '@reset-framework/backend'
|
|
49
|
-
|
|
50
|
-
export default defineBackend(({ handle, process }) => {
|
|
51
|
-
handle('backend.ping', async () => {
|
|
52
|
-
return {
|
|
53
|
-
ok: true,
|
|
54
|
-
runtime: globalThis.process.release?.name ?? 'node',
|
|
55
|
-
version: globalThis.process.version
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
handle('backend.execVersion', async () => {
|
|
60
|
-
return process.run({
|
|
61
|
-
command: globalThis.process.execPath,
|
|
62
|
-
args: ['--version']
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
`
|
|
67
|
-
|
|
68
|
-
const standaloneViteConfig = `import { defineConfig } from 'vite'
|
|
69
|
-
import react from '@vitejs/plugin-react'
|
|
70
|
-
|
|
71
|
-
export default defineConfig({
|
|
72
|
-
base: './',
|
|
73
|
-
plugins: [react()],
|
|
74
|
-
})
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
const tailwindViteConfig = `import { defineConfig } from 'vite'
|
|
78
|
-
import react from '@vitejs/plugin-react'
|
|
79
|
-
import tailwindcss from '@tailwindcss/vite'
|
|
80
|
-
|
|
81
|
-
export default defineConfig({
|
|
82
|
-
base: './',
|
|
83
|
-
plugins: [react(), tailwindcss()],
|
|
84
|
-
})
|
|
85
|
-
`
|
|
86
|
-
|
|
87
|
-
const standaloneTsconfigApp = `{
|
|
88
|
-
"compilerOptions": {
|
|
89
|
-
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
90
|
-
"target": "ES2023",
|
|
91
|
-
"useDefineForClassFields": true,
|
|
92
|
-
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
93
|
-
"module": "ESNext",
|
|
94
|
-
"types": ["vite/client"],
|
|
95
|
-
"skipLibCheck": true,
|
|
96
|
-
"moduleResolution": "bundler",
|
|
97
|
-
"allowImportingTsExtensions": true,
|
|
98
|
-
"verbatimModuleSyntax": true,
|
|
99
|
-
"moduleDetection": "force",
|
|
100
|
-
"noEmit": true,
|
|
101
|
-
"jsx": "react-jsx",
|
|
102
|
-
"strict": true,
|
|
103
|
-
"noUnusedLocals": true,
|
|
104
|
-
"noUnusedParameters": true,
|
|
105
|
-
"erasableSyntaxOnly": true,
|
|
106
|
-
"noFallthroughCasesInSwitch": true,
|
|
107
|
-
"noUncheckedSideEffectImports": true
|
|
108
|
-
},
|
|
109
|
-
"include": ["src"]
|
|
110
|
-
}
|
|
111
|
-
`
|
|
112
|
-
|
|
113
|
-
const tailwindIndexCss = `@import "tailwindcss";
|
|
114
|
-
|
|
115
|
-
:root {
|
|
116
|
-
color-scheme: dark;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
body {
|
|
120
|
-
margin: 0;
|
|
121
|
-
font-family: "Avenir Next", "Segoe UI", sans-serif;
|
|
122
|
-
background: #111111;
|
|
123
|
-
}
|
|
124
|
-
`
|
|
125
|
-
|
|
126
|
-
const tailwindAppTsx = `import { useEffect, useState } from 'react'
|
|
127
|
-
import { reset } from './lib/reset'
|
|
128
|
-
|
|
129
|
-
function App() {
|
|
130
|
-
const [count, setCount] = useState(0)
|
|
131
|
-
const [appName, setAppName] = useState('Reset App')
|
|
132
|
-
const [appVersion, setAppVersion] = useState('0.0.0')
|
|
133
|
-
const [platform, setPlatform] = useState('unknown')
|
|
134
|
-
const [bridgeVersion, setBridgeVersion] = useState('1')
|
|
135
|
-
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
let cancelled = false
|
|
138
|
-
|
|
139
|
-
async function loadRuntimeInfo() {
|
|
140
|
-
try {
|
|
141
|
-
const [appInfo, runtimeInfo] = await Promise.all([
|
|
142
|
-
reset.app.getInfo(),
|
|
143
|
-
reset.runtime.getInfo(),
|
|
144
|
-
])
|
|
145
|
-
|
|
146
|
-
if (cancelled) {
|
|
147
|
-
return
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
setAppName(appInfo.name)
|
|
151
|
-
setAppVersion(appInfo.version)
|
|
152
|
-
setPlatform(runtimeInfo.platform)
|
|
153
|
-
setBridgeVersion(runtimeInfo.bridgeVersion)
|
|
154
|
-
} catch (error) {
|
|
155
|
-
console.error('Failed to load runtime info', error)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
loadRuntimeInfo()
|
|
160
|
-
|
|
161
|
-
return () => {
|
|
162
|
-
cancelled = true
|
|
163
|
-
}
|
|
164
|
-
}, [])
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<main className="min-h-screen bg-stone-950 text-stone-100">
|
|
168
|
-
<div className="mx-auto flex min-h-screen max-w-6xl flex-col px-6 py-12 lg:px-10">
|
|
169
|
-
<div className="inline-flex w-fit items-center rounded-full border border-white/10 bg-white/5 px-4 py-2 text-sm tracking-[0.18em] text-stone-300 uppercase">
|
|
170
|
-
{appName} v{appVersion} on {platform}
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<div className="mt-12 grid gap-12 lg:grid-cols-[minmax(0,1.4fr)_minmax(20rem,0.8fr)] lg:items-end">
|
|
174
|
-
<section>
|
|
175
|
-
<p className="text-sm font-medium uppercase tracking-[0.32em] text-amber-300">
|
|
176
|
-
Reset Framework
|
|
177
|
-
</p>
|
|
178
|
-
<h1 className="mt-5 max-w-4xl text-5xl font-medium leading-[0.95] tracking-[-0.06em] text-white sm:text-6xl lg:text-7xl">
|
|
179
|
-
Tailwind-ready desktop apps with a native runtime underneath.
|
|
180
|
-
</h1>
|
|
181
|
-
<p className="mt-6 max-w-2xl text-lg leading-8 text-stone-300">
|
|
182
|
-
Edit the frontend, call native commands through the runtime bridge, and keep the
|
|
183
|
-
host layer out of the way while you build the actual product.
|
|
184
|
-
</p>
|
|
185
|
-
</section>
|
|
186
|
-
|
|
187
|
-
<section className="rounded-[2rem] border border-white/10 bg-white/5 p-6 shadow-2xl shadow-black/20 backdrop-blur">
|
|
188
|
-
<div className="flex items-center justify-between text-sm text-stone-400">
|
|
189
|
-
<span>Runtime bridge</span>
|
|
190
|
-
<span>bridge v{bridgeVersion}</span>
|
|
191
|
-
</div>
|
|
192
|
-
<div className="mt-6 rounded-2xl border border-white/10 bg-black/30 p-5">
|
|
193
|
-
<div className="text-xs uppercase tracking-[0.24em] text-stone-500">
|
|
194
|
-
Counter
|
|
195
|
-
</div>
|
|
196
|
-
<div className="mt-4 text-4xl font-semibold text-white">{count}</div>
|
|
197
|
-
<button
|
|
198
|
-
className="mt-6 inline-flex items-center rounded-full bg-amber-300 px-5 py-3 text-sm font-semibold text-stone-950 transition hover:bg-amber-200"
|
|
199
|
-
onClick={() => setCount((value) => value + 1)}
|
|
200
|
-
>
|
|
201
|
-
Increment
|
|
202
|
-
</button>
|
|
203
|
-
</div>
|
|
204
|
-
</section>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
</main>
|
|
208
|
-
)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export default App
|
|
212
|
-
`
|
|
213
|
-
|
|
214
|
-
export const description = "Scaffold a starter app with an interactive project setup"
|
|
215
|
-
|
|
216
|
-
function listTemplates(templatesDir) {
|
|
217
|
-
return readdirSync(templatesDir, { withFileTypes: true })
|
|
218
|
-
.filter((entry) => entry.isDirectory())
|
|
219
|
-
.map((entry) => entry.name)
|
|
220
|
-
.sort()
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function getRootGitignore(frontendDir) {
|
|
224
|
-
if (frontendDir === ".") {
|
|
225
|
-
return `.DS_Store
|
|
226
|
-
Thumbs.db
|
|
227
|
-
.idea/
|
|
228
|
-
.vs/
|
|
229
|
-
.vscode/
|
|
230
|
-
.cache/
|
|
231
|
-
.reset/
|
|
232
|
-
.turbo/
|
|
233
|
-
build/
|
|
234
|
-
coverage/
|
|
235
|
-
dist/
|
|
236
|
-
node_modules/
|
|
237
|
-
npm-debug.log*
|
|
238
|
-
yarn-debug.log*
|
|
239
|
-
yarn-error.log*
|
|
240
|
-
.pnpm-debug.log*
|
|
241
|
-
*.log
|
|
242
|
-
*.zip
|
|
243
|
-
*.tgz
|
|
244
|
-
*.tsbuildinfo
|
|
245
|
-
*.dSYM/
|
|
246
|
-
.env
|
|
247
|
-
.env.*
|
|
248
|
-
.env.local
|
|
249
|
-
.env.*.local
|
|
250
|
-
.eslintcache
|
|
251
|
-
bun.lock
|
|
252
|
-
bun.lockb
|
|
253
|
-
compile_commands.json
|
|
254
|
-
CMakeUserPresets.json
|
|
255
|
-
CMakeCache.txt
|
|
256
|
-
CMakeFiles/
|
|
257
|
-
cmake_install.cmake
|
|
258
|
-
install_manifest.txt
|
|
259
|
-
cmake-build-*/
|
|
260
|
-
Testing/
|
|
261
|
-
scripts/local/
|
|
262
|
-
`
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return `.DS_Store
|
|
266
|
-
Thumbs.db
|
|
267
|
-
.idea/
|
|
268
|
-
.vs/
|
|
269
|
-
.vscode/
|
|
270
|
-
.cache/
|
|
271
|
-
.reset/
|
|
272
|
-
.turbo/
|
|
273
|
-
build/
|
|
274
|
-
coverage/
|
|
275
|
-
node_modules/
|
|
276
|
-
${frontendDir}/node_modules/
|
|
277
|
-
${frontendDir}/dist/
|
|
278
|
-
npm-debug.log*
|
|
279
|
-
yarn-debug.log*
|
|
280
|
-
yarn-error.log*
|
|
281
|
-
.pnpm-debug.log*
|
|
282
|
-
*.log
|
|
283
|
-
*.zip
|
|
284
|
-
*.tgz
|
|
285
|
-
*.tsbuildinfo
|
|
286
|
-
*.dSYM/
|
|
287
|
-
.env
|
|
288
|
-
.env.*
|
|
289
|
-
.env.local
|
|
290
|
-
.env.*.local
|
|
291
|
-
.eslintcache
|
|
292
|
-
bun.lock
|
|
293
|
-
bun.lockb
|
|
294
|
-
compile_commands.json
|
|
295
|
-
CMakeUserPresets.json
|
|
296
|
-
CMakeCache.txt
|
|
297
|
-
CMakeFiles/
|
|
298
|
-
cmake_install.cmake
|
|
299
|
-
install_manifest.txt
|
|
300
|
-
cmake-build-*/
|
|
301
|
-
Testing/
|
|
302
|
-
scripts/local/
|
|
303
|
-
`
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function resolveDefaultFrontendDir(context) {
|
|
307
|
-
if (typeof context.flags["frontend-dir"] === "string") {
|
|
308
|
-
return context.flags["frontend-dir"]
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (context.flags.flat) {
|
|
312
|
-
return "."
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return "frontend"
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function resolveDefaultStyling(context) {
|
|
319
|
-
if (context.flags.tailwind) {
|
|
320
|
-
return "tailwindcss"
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (context.flags["no-tailwind"]) {
|
|
324
|
-
return "css"
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return "css"
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function resolvePackageManagerOption(context) {
|
|
331
|
-
if (typeof context.flags["package-manager"] === "string") {
|
|
332
|
-
return context.flags["package-manager"]
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return "npm"
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function describeInstallCommand(packageManager) {
|
|
339
|
-
return [packageManager, ...getInstallCommandArgs(packageManager)].join(" ")
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function createFrontendScriptCommand(packageManager, frontendDir, scriptName) {
|
|
343
|
-
const frontendPath = JSON.stringify(frontendDir.replace(/\\/g, "/"))
|
|
344
|
-
|
|
345
|
-
if (packageManager === "npm") {
|
|
346
|
-
return `npm --workspace ${frontendPath} run ${scriptName} --`
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (packageManager === "pnpm") {
|
|
350
|
-
return `pnpm --dir ${frontendPath} run ${scriptName} --`
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (packageManager === "yarn") {
|
|
354
|
-
return `yarn --cwd ${frontendPath} run ${scriptName}`
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (packageManager === "bun") {
|
|
358
|
-
return `bun --cwd ${frontendPath} run ${scriptName}`
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
throw new Error(`Unsupported package manager: ${packageManager}`)
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
async function ensureWritableTarget(targetDir, force) {
|
|
365
|
-
if (!existsSync(targetDir)) {
|
|
366
|
-
return
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const entries = await readdir(targetDir)
|
|
370
|
-
|
|
371
|
-
if (entries.length > 0 && !force) {
|
|
372
|
-
throw new Error(`Target directory is not empty: ${targetDir}. Use --force to continue.`)
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
async function copyFrontendTemplate(sourceFrontendDir, targetFrontendDir, options) {
|
|
377
|
-
const { force, frontendDir } = options
|
|
378
|
-
const flatLayout = frontendDir === "."
|
|
379
|
-
|
|
380
|
-
if (!flatLayout) {
|
|
381
|
-
await cp(sourceFrontendDir, targetFrontendDir, {
|
|
382
|
-
recursive: true,
|
|
383
|
-
force,
|
|
384
|
-
filter(source) {
|
|
385
|
-
const baseName = path.basename(source)
|
|
386
|
-
return (
|
|
387
|
-
baseName !== "node_modules" &&
|
|
388
|
-
baseName !== "dist" &&
|
|
389
|
-
baseName !== "bun.lockb" &&
|
|
390
|
-
baseName !== "reset.config.json"
|
|
391
|
-
)
|
|
392
|
-
}
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
return
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
await mkdir(targetFrontendDir, { recursive: true })
|
|
399
|
-
|
|
400
|
-
const entries = await readdir(sourceFrontendDir, { withFileTypes: true })
|
|
401
|
-
for (const entry of entries) {
|
|
402
|
-
if (["node_modules", "dist", "bun.lockb", "reset.config.json", ".gitignore", "README.md"].includes(entry.name)) {
|
|
403
|
-
continue
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
await cp(
|
|
407
|
-
path.join(sourceFrontendDir, entry.name),
|
|
408
|
-
path.join(targetFrontendDir, entry.name),
|
|
409
|
-
{ recursive: true, force }
|
|
410
|
-
)
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
async function applyFrontendOverrides(frontendDir, options) {
|
|
415
|
-
await writeFile(path.join(frontendDir, "src", "lib", "reset.ts"), sdkRuntimeReexport, "utf8")
|
|
416
|
-
await writeFile(path.join(frontendDir, "tsconfig.app.json"), standaloneTsconfigApp, "utf8")
|
|
417
|
-
|
|
418
|
-
const packageJsonPath = path.join(frontendDir, "package.json")
|
|
419
|
-
const frontendPackage = JSON.parse(await readFile(packageJsonPath, "utf8"))
|
|
420
|
-
frontendPackage.dependencies = {
|
|
421
|
-
...frontendPackage.dependencies,
|
|
422
|
-
"@reset-framework/sdk": options.sdkDependencySpec
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (options.styling === "tailwindcss") {
|
|
426
|
-
frontendPackage.devDependencies = {
|
|
427
|
-
...frontendPackage.devDependencies,
|
|
428
|
-
"@tailwindcss/vite": "latest",
|
|
429
|
-
tailwindcss: "latest"
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
await writeFile(packageJsonPath, JSON.stringify(frontendPackage, null, 2) + "\n", "utf8")
|
|
433
|
-
await writeFile(path.join(frontendDir, "vite.config.ts"), tailwindViteConfig, "utf8")
|
|
434
|
-
await writeFile(path.join(frontendDir, "src", "index.css"), tailwindIndexCss, "utf8")
|
|
435
|
-
await writeFile(path.join(frontendDir, "src", "App.tsx"), tailwindAppTsx, "utf8")
|
|
436
|
-
return
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
await writeFile(packageJsonPath, JSON.stringify(frontendPackage, null, 2) + "\n", "utf8")
|
|
440
|
-
await writeFile(path.join(frontendDir, "vite.config.ts"), standaloneViteConfig, "utf8")
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function createRootPackageJson(options) {
|
|
444
|
-
const packageManagerVersion = options.packageManagerVersion
|
|
445
|
-
? `${options.packageManager}@${options.packageManagerVersion}`
|
|
446
|
-
: undefined
|
|
447
|
-
|
|
448
|
-
const base = {
|
|
449
|
-
name: options.appName,
|
|
450
|
-
private: true,
|
|
451
|
-
version: "0.1.0",
|
|
452
|
-
description: `${options.productName} desktop app`,
|
|
453
|
-
scripts: {
|
|
454
|
-
dev: "reset-framework-cli dev",
|
|
455
|
-
build: "reset-framework-cli build",
|
|
456
|
-
package: "reset-framework-cli package",
|
|
457
|
-
doctor: "reset-framework-cli doctor"
|
|
458
|
-
},
|
|
459
|
-
devDependencies: {
|
|
460
|
-
"reset-framework-cli": options.cliDependencySpec
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (packageManagerVersion) {
|
|
465
|
-
base.packageManager = packageManagerVersion
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (options.frontendDir !== ".") {
|
|
469
|
-
base.workspaces = [options.frontendDir, "backend"]
|
|
470
|
-
base.scripts["dev:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "dev")
|
|
471
|
-
base.scripts["build:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "build")
|
|
472
|
-
base.scripts["lint:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "lint")
|
|
473
|
-
base.scripts["preview:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "preview")
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return base
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function createRootReadme(options) {
|
|
480
|
-
const run = `${options.packageManager} run`
|
|
481
|
-
const webPath = options.frontendDir === "." ? "project root" : `${options.frontendDir}/`
|
|
482
|
-
|
|
483
|
-
return `# ${options.productName}
|
|
484
|
-
|
|
485
|
-
Generated with \`reset-framework-cli\`.
|
|
486
|
-
|
|
487
|
-
## Commands
|
|
488
|
-
|
|
489
|
-
- \`${run} dev\`: start the frontend dev server and native desktop runtime
|
|
490
|
-
- \`${run} build\`: build the frontend and stage the desktop app bundle
|
|
491
|
-
- \`${run} package\`: archive the built desktop app
|
|
492
|
-
- \`${run} doctor\`: inspect the project and framework installation
|
|
493
|
-
|
|
494
|
-
## Web-only commands
|
|
495
|
-
|
|
496
|
-
${options.frontendDir === "." ? `- \`${run} dev:web\`: run the Vite frontend only
|
|
497
|
-
- \`${run} build:web\`: build the frontend only` : `- \`${run} dev:web\`: run the frontend workspace only
|
|
498
|
-
- \`${run} build:web\`: build the frontend workspace only`}
|
|
499
|
-
|
|
500
|
-
## Project files
|
|
501
|
-
|
|
502
|
-
- \`reset.config.json\`: desktop app metadata and runtime configuration
|
|
503
|
-
- \`${webPath}\`: web frontend source
|
|
504
|
-
- \`backend/\`: optional TypeScript sidecar backend
|
|
505
|
-
- \`.reset/\`: generated build output and native runtime cache
|
|
506
|
-
`
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function createBackendPackageJson(options) {
|
|
510
|
-
return {
|
|
511
|
-
name: `${options.appName}-backend`,
|
|
512
|
-
private: true,
|
|
513
|
-
version: "0.1.0",
|
|
514
|
-
type: "module",
|
|
515
|
-
dependencies: {
|
|
516
|
-
"@reset-framework/backend": options.backendDependencySpec
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
async function writeBackendFiles(options) {
|
|
522
|
-
const backendDir = path.join(options.targetDir, "backend")
|
|
523
|
-
const backendSrcDir = path.join(backendDir, "src")
|
|
524
|
-
|
|
525
|
-
await mkdir(backendSrcDir, { recursive: true })
|
|
526
|
-
await writeFile(
|
|
527
|
-
path.join(backendDir, "package.json"),
|
|
528
|
-
JSON.stringify(createBackendPackageJson(options), null, 2) + "\n",
|
|
529
|
-
"utf8"
|
|
530
|
-
)
|
|
531
|
-
await writeFile(path.join(backendDir, "tsconfig.json"), backendTsconfig, "utf8")
|
|
532
|
-
await writeFile(path.join(backendSrcDir, "index.ts"), backendStarterSource, "utf8")
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
async function writeRootPackageFiles(options) {
|
|
536
|
-
await writeFile(
|
|
537
|
-
path.join(options.targetDir, "README.md"),
|
|
538
|
-
createRootReadme(options),
|
|
539
|
-
"utf8"
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
if (options.frontendDir !== ".") {
|
|
543
|
-
await writeFile(
|
|
544
|
-
path.join(options.targetDir, "package.json"),
|
|
545
|
-
JSON.stringify(createRootPackageJson(options), null, 2) + "\n",
|
|
546
|
-
"utf8"
|
|
547
|
-
)
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
async function writeProjectFiles(options) {
|
|
552
|
-
const templateConfig = JSON.parse(await readFile(options.templateConfigPath, "utf8"))
|
|
553
|
-
|
|
554
|
-
await writeFile(
|
|
555
|
-
path.join(options.targetDir, "reset.config.json"),
|
|
556
|
-
JSON.stringify(
|
|
557
|
-
{
|
|
558
|
-
...templateConfig,
|
|
559
|
-
name: options.appName,
|
|
560
|
-
productName: options.productName,
|
|
561
|
-
appId: options.appId,
|
|
562
|
-
project: {
|
|
563
|
-
frontendDir: options.frontendDir,
|
|
564
|
-
styling: options.styling
|
|
565
|
-
},
|
|
566
|
-
security: {
|
|
567
|
-
permissions: Array.from(
|
|
568
|
-
new Set([
|
|
569
|
-
...(Array.isArray(templateConfig.security?.permissions)
|
|
570
|
-
? templateConfig.security.permissions
|
|
571
|
-
: []),
|
|
572
|
-
"backend.*"
|
|
573
|
-
])
|
|
574
|
-
)
|
|
575
|
-
},
|
|
576
|
-
window: {
|
|
577
|
-
...templateConfig.window,
|
|
578
|
-
title: options.productName
|
|
579
|
-
}
|
|
580
|
-
},
|
|
581
|
-
null,
|
|
582
|
-
2
|
|
583
|
-
) + "\n",
|
|
584
|
-
"utf8"
|
|
585
|
-
)
|
|
586
|
-
|
|
587
|
-
await writeFile(
|
|
588
|
-
path.join(options.targetDir, ".gitignore"),
|
|
589
|
-
getRootGitignore(options.frontendDir),
|
|
590
|
-
"utf8"
|
|
591
|
-
)
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
async function resolvePackageManagerVersion(packageManager) {
|
|
595
|
-
if (packageManager !== "npm" && packageManager !== "pnpm" && packageManager !== "yarn" && packageManager !== "bun") {
|
|
596
|
-
return null
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
try {
|
|
600
|
-
const command = resolvePackageManagerCommand(packageManager)
|
|
601
|
-
const result = await captureCommandOutput(command, ["--version"])
|
|
602
|
-
return typeof result === "string" && result.trim() !== "" ? result.trim() : null
|
|
603
|
-
} catch {
|
|
604
|
-
return null
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
async function collectCreateAppOptions(context, templates) {
|
|
609
|
-
const initialTarget = context.args[0] ?? "my-app"
|
|
610
|
-
const defaultTargetDir = path.resolve(context.cwd, initialTarget)
|
|
611
|
-
const defaultMetadata = makeAppMetadata(path.basename(defaultTargetDir))
|
|
612
|
-
const frameworkPaths = resolveFrameworkPaths()
|
|
613
|
-
|
|
614
|
-
const defaults = {
|
|
615
|
-
targetDir: defaultTargetDir,
|
|
616
|
-
templateName: typeof context.flags.template === "string" ? context.flags.template : "basic",
|
|
617
|
-
appName: defaultMetadata.name,
|
|
618
|
-
productName:
|
|
619
|
-
typeof context.flags["product-name"] === "string"
|
|
620
|
-
? context.flags["product-name"]
|
|
621
|
-
: defaultMetadata.productName,
|
|
622
|
-
appId:
|
|
623
|
-
typeof context.flags["app-id"] === "string"
|
|
624
|
-
? context.flags["app-id"]
|
|
625
|
-
: defaultMetadata.appId,
|
|
626
|
-
frontendDir: resolveDefaultFrontendDir(context),
|
|
627
|
-
styling: resolveDefaultStyling(context)
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
const interactive = isInteractiveSession() && !context.flags.yes && !context.flags["no-prompt"]
|
|
631
|
-
|
|
632
|
-
if (!interactive) {
|
|
633
|
-
if (!context.args[0]) {
|
|
634
|
-
throw new Error("Missing target directory. Use: reset-framework-cli create-app <directory>")
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
return {
|
|
638
|
-
...defaults,
|
|
639
|
-
templateDir: path.join(frameworkPaths.templatesDir, defaults.templateName),
|
|
640
|
-
templateConfigPath: path.join(frameworkPaths.templatesDir, defaults.templateName, "reset.config.json"),
|
|
641
|
-
sourceFrontendDir: path.join(frameworkPaths.templatesDir, defaults.templateName, "frontend"),
|
|
642
|
-
backendDependencySpec: resolveBackendDependencySpec(frameworkPaths),
|
|
643
|
-
cliDependencySpec: resolveCliDependencySpec(frameworkPaths),
|
|
644
|
-
sdkDependencySpec: resolveSdkDependencySpec(frameworkPaths),
|
|
645
|
-
packageManager: resolvePackageManagerOption(context),
|
|
646
|
-
packageManagerVersion: await resolvePackageManagerVersion(resolvePackageManagerOption(context)),
|
|
647
|
-
installDependencies: !context.flags["no-install"],
|
|
648
|
-
force: Boolean(context.flags.force)
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
const selected = await withPromptSession(async (rl) => {
|
|
653
|
-
const targetInput = await promptText(rl, {
|
|
654
|
-
label: "Project directory",
|
|
655
|
-
defaultValue: path.basename(defaults.targetDir),
|
|
656
|
-
validate(value) {
|
|
657
|
-
return value.trim() !== "" || "Enter a directory name."
|
|
658
|
-
}
|
|
659
|
-
})
|
|
660
|
-
|
|
661
|
-
const targetDir = path.resolve(context.cwd, targetInput)
|
|
662
|
-
const metadata = makeAppMetadata(path.basename(targetDir))
|
|
663
|
-
|
|
664
|
-
const templateName =
|
|
665
|
-
templates.length === 1
|
|
666
|
-
? templates[0]
|
|
667
|
-
: await promptSelect(rl, {
|
|
668
|
-
label: "Template",
|
|
669
|
-
defaultValue: defaults.templateName,
|
|
670
|
-
choices: templates.map((template) => ({
|
|
671
|
-
value: template,
|
|
672
|
-
label: template,
|
|
673
|
-
description: "Starter project scaffold"
|
|
674
|
-
}))
|
|
675
|
-
})
|
|
676
|
-
|
|
677
|
-
const productName = await promptText(rl, {
|
|
678
|
-
label: "Product name",
|
|
679
|
-
defaultValue: defaults.productName || metadata.productName,
|
|
680
|
-
validate(value) {
|
|
681
|
-
return value.trim() !== "" || "Enter a product name."
|
|
682
|
-
}
|
|
683
|
-
})
|
|
684
|
-
|
|
685
|
-
const appId = await promptText(rl, {
|
|
686
|
-
label: "App ID",
|
|
687
|
-
defaultValue: defaults.appId || metadata.appId,
|
|
688
|
-
validate(value) {
|
|
689
|
-
return value.includes(".") || "Use a reverse-domain identifier such as com.example.my-app."
|
|
690
|
-
}
|
|
691
|
-
})
|
|
692
|
-
|
|
693
|
-
const frontendDir = await promptSelect(rl, {
|
|
694
|
-
label: "Frontend layout",
|
|
695
|
-
defaultValue: defaults.frontendDir === "." ? "." : "frontend",
|
|
696
|
-
choices: [
|
|
697
|
-
{
|
|
698
|
-
value: "frontend",
|
|
699
|
-
label: "Use a frontend/ subdirectory",
|
|
700
|
-
description: "Keeps the web app separate from config, output, and packaging files."
|
|
701
|
-
},
|
|
702
|
-
{
|
|
703
|
-
value: ".",
|
|
704
|
-
label: "Keep the frontend at project root",
|
|
705
|
-
description: "Flatter project layout with package.json and src/ directly in the app root."
|
|
706
|
-
}
|
|
707
|
-
]
|
|
708
|
-
})
|
|
709
|
-
|
|
710
|
-
const styling = await promptSelect(rl, {
|
|
711
|
-
label: "Styling setup",
|
|
712
|
-
defaultValue: defaults.styling,
|
|
713
|
-
choices: [
|
|
714
|
-
{
|
|
715
|
-
value: "css",
|
|
716
|
-
label: "Vanilla CSS starter",
|
|
717
|
-
description: "Use the existing CSS-based starter with no extra styling dependency."
|
|
718
|
-
},
|
|
719
|
-
{
|
|
720
|
-
value: "tailwindcss",
|
|
721
|
-
label: "Tailwind CSS starter",
|
|
722
|
-
description: "Configure Tailwind CSS and swap the starter screen to utility classes."
|
|
723
|
-
}
|
|
724
|
-
]
|
|
725
|
-
})
|
|
726
|
-
|
|
727
|
-
const installDependencies = await promptConfirm(rl, {
|
|
728
|
-
label: "Install frontend dependencies after scaffolding",
|
|
729
|
-
defaultValue: true
|
|
730
|
-
})
|
|
731
|
-
|
|
732
|
-
const packageManager = await promptSelect(rl, {
|
|
733
|
-
label: "Package manager",
|
|
734
|
-
defaultValue: resolvePackageManagerOption(context),
|
|
735
|
-
choices: [
|
|
736
|
-
{
|
|
737
|
-
value: "npm",
|
|
738
|
-
label: "npm",
|
|
739
|
-
description: "Wide compatibility and predictable installs."
|
|
740
|
-
},
|
|
741
|
-
{
|
|
742
|
-
value: "bun",
|
|
743
|
-
label: "bun",
|
|
744
|
-
description: "Fast installs and lets bun run the root desktop scripts."
|
|
745
|
-
},
|
|
746
|
-
{
|
|
747
|
-
value: "pnpm",
|
|
748
|
-
label: "pnpm",
|
|
749
|
-
description: "Strict dependency graph with workspace support."
|
|
750
|
-
},
|
|
751
|
-
{
|
|
752
|
-
value: "yarn",
|
|
753
|
-
label: "yarn",
|
|
754
|
-
description: "Classic workspace-based JavaScript package workflow."
|
|
755
|
-
}
|
|
756
|
-
]
|
|
757
|
-
})
|
|
758
|
-
|
|
759
|
-
printSection("Summary")
|
|
760
|
-
printKeyValueTable([
|
|
761
|
-
["Directory", targetDir],
|
|
762
|
-
["Template", templateName],
|
|
763
|
-
["Product", productName],
|
|
764
|
-
["App ID", appId],
|
|
765
|
-
["Frontend", frontendDir === "." ? "project root" : `${frontendDir}/`],
|
|
766
|
-
["Styling", styling],
|
|
767
|
-
["Package manager", packageManager],
|
|
768
|
-
["Install", installDependencies ? "yes" : "no"]
|
|
769
|
-
])
|
|
770
|
-
console.log("")
|
|
771
|
-
|
|
772
|
-
const confirmed = await promptConfirm(rl, {
|
|
773
|
-
label: "Create this project",
|
|
774
|
-
defaultValue: true
|
|
775
|
-
})
|
|
776
|
-
|
|
777
|
-
if (!confirmed) {
|
|
778
|
-
throw new Error("Create app cancelled.")
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
return {
|
|
782
|
-
targetDir,
|
|
783
|
-
templateName,
|
|
784
|
-
appName: metadata.name,
|
|
785
|
-
productName,
|
|
786
|
-
appId,
|
|
787
|
-
frontendDir,
|
|
788
|
-
styling,
|
|
789
|
-
installDependencies,
|
|
790
|
-
packageManager
|
|
791
|
-
}
|
|
792
|
-
})
|
|
793
|
-
|
|
794
|
-
return {
|
|
795
|
-
...selected,
|
|
796
|
-
templateDir: path.join(frameworkPaths.templatesDir, selected.templateName),
|
|
797
|
-
templateConfigPath: path.join(frameworkPaths.templatesDir, selected.templateName, "reset.config.json"),
|
|
798
|
-
sourceFrontendDir: path.join(frameworkPaths.templatesDir, selected.templateName, "frontend"),
|
|
799
|
-
backendDependencySpec: resolveBackendDependencySpec(frameworkPaths),
|
|
800
|
-
cliDependencySpec: resolveCliDependencySpec(frameworkPaths),
|
|
801
|
-
sdkDependencySpec: resolveSdkDependencySpec(frameworkPaths),
|
|
802
|
-
packageManagerVersion: await resolvePackageManagerVersion(selected.packageManager),
|
|
803
|
-
force: Boolean(context.flags.force)
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
export async function run(context) {
|
|
808
|
-
const frameworkPaths = resolveFrameworkPaths()
|
|
809
|
-
const templates = listTemplates(frameworkPaths.templatesDir)
|
|
810
|
-
const formatTargetForShell = (targetDir) => {
|
|
811
|
-
const relative = path.relative(context.cwd, targetDir)
|
|
812
|
-
if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))) {
|
|
813
|
-
return relative || "."
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
return targetDir
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
printBanner("reset-framework-cli create-app", description)
|
|
820
|
-
const options = await collectCreateAppOptions(context, templates)
|
|
821
|
-
|
|
822
|
-
if (!templates.includes(options.templateName)) {
|
|
823
|
-
throw new Error(`Unknown template: ${options.templateName}`)
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
const targetFrontendDir =
|
|
827
|
-
options.frontendDir === "."
|
|
828
|
-
? options.targetDir
|
|
829
|
-
: path.join(options.targetDir, options.frontendDir)
|
|
830
|
-
|
|
831
|
-
await ensureWritableTarget(options.targetDir, options.force)
|
|
832
|
-
|
|
833
|
-
printSection("Project")
|
|
834
|
-
printKeyValueTable([
|
|
835
|
-
["Directory", options.targetDir],
|
|
836
|
-
["Template", options.templateName],
|
|
837
|
-
["Product", options.productName],
|
|
838
|
-
["App ID", options.appId],
|
|
839
|
-
["Frontend", options.frontendDir === "." ? "project root" : `${options.frontendDir}/`],
|
|
840
|
-
["Styling", options.styling],
|
|
841
|
-
["Package manager", options.packageManager],
|
|
842
|
-
["Install", options.installDependencies ? describeInstallCommand(options.packageManager) : "skipped"]
|
|
843
|
-
])
|
|
844
|
-
|
|
845
|
-
if (Boolean(context.flags["dry-run"])) {
|
|
846
|
-
console.log("")
|
|
847
|
-
printSection("Dry run")
|
|
848
|
-
printStatusTable([
|
|
849
|
-
["plan", "Copy starter", `${options.sourceFrontendDir} -> ${targetFrontendDir}`],
|
|
850
|
-
["plan", "Write README", path.join(options.targetDir, "README.md")],
|
|
851
|
-
["plan", "Write config", path.join(options.targetDir, "reset.config.json")],
|
|
852
|
-
["plan", "Write ignore file", path.join(options.targetDir, ".gitignore")],
|
|
853
|
-
["plan", "Write app package", path.join(options.targetDir, "package.json")],
|
|
854
|
-
["plan", "Write backend package", path.join(options.targetDir, "backend", "package.json")],
|
|
855
|
-
["plan", "Write backend entry", path.join(options.targetDir, "backend", "src", "index.ts")],
|
|
856
|
-
["plan", "Write SDK bridge", path.join(targetFrontendDir, "src", "lib", "reset.ts")],
|
|
857
|
-
["plan", "Write Vite config", path.join(targetFrontendDir, "vite.config.ts")],
|
|
858
|
-
["plan", "Write TS config", path.join(targetFrontendDir, "tsconfig.app.json")]
|
|
859
|
-
])
|
|
860
|
-
if (options.styling === "tailwindcss") {
|
|
861
|
-
printStatusTable([
|
|
862
|
-
["plan", "Patch package.json", path.join(targetFrontendDir, "package.json")],
|
|
863
|
-
["plan", "Write styles", path.join(targetFrontendDir, "src", "index.css")],
|
|
864
|
-
["plan", "Write starter screen", path.join(targetFrontendDir, "src", "App.tsx")]
|
|
865
|
-
])
|
|
866
|
-
}
|
|
867
|
-
if (options.installDependencies) {
|
|
868
|
-
printStatusTable([
|
|
869
|
-
["plan", "Install dependencies", `${describeInstallCommand(options.packageManager)} in ${options.targetDir}`]
|
|
870
|
-
])
|
|
871
|
-
}
|
|
872
|
-
return
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
console.log("")
|
|
876
|
-
const progress = createProgress(options.installDependencies ? 7 : 6, "Scaffold")
|
|
877
|
-
|
|
878
|
-
await mkdir(options.targetDir, { recursive: true })
|
|
879
|
-
progress.tick("Prepared project directory")
|
|
880
|
-
|
|
881
|
-
await copyFrontendTemplate(options.sourceFrontendDir, targetFrontendDir, options)
|
|
882
|
-
progress.tick("Copied frontend starter")
|
|
883
|
-
|
|
884
|
-
await writeProjectFiles(options)
|
|
885
|
-
await writeRootPackageFiles(options)
|
|
886
|
-
progress.tick("Wrote project files")
|
|
887
|
-
|
|
888
|
-
await applyFrontendOverrides(targetFrontendDir, options)
|
|
889
|
-
progress.tick("Applied frontend package wiring")
|
|
890
|
-
|
|
891
|
-
await writeBackendFiles(options)
|
|
892
|
-
progress.tick("Wrote backend starter")
|
|
893
|
-
|
|
894
|
-
const packageJsonPath = path.join(targetFrontendDir, "package.json")
|
|
895
|
-
const frontendPackage = JSON.parse(await readFile(packageJsonPath, "utf8"))
|
|
896
|
-
|
|
897
|
-
if (options.frontendDir === ".") {
|
|
898
|
-
const currentScripts = { ...(frontendPackage.scripts ?? {}) }
|
|
899
|
-
frontendPackage.name = options.appName
|
|
900
|
-
frontendPackage.private = true
|
|
901
|
-
frontendPackage.version = "0.1.0"
|
|
902
|
-
frontendPackage.description = `${options.productName} desktop app`
|
|
903
|
-
frontendPackage.workspaces = ["backend"]
|
|
904
|
-
frontendPackage.devDependencies = {
|
|
905
|
-
...frontendPackage.devDependencies,
|
|
906
|
-
"reset-framework-cli": options.cliDependencySpec
|
|
907
|
-
}
|
|
908
|
-
frontendPackage.scripts = {
|
|
909
|
-
dev: "reset-framework-cli dev",
|
|
910
|
-
"dev:web": currentScripts.dev ?? "vite",
|
|
911
|
-
build: "reset-framework-cli build",
|
|
912
|
-
"build:web": currentScripts.build ?? "tsc -b && vite build",
|
|
913
|
-
package: "reset-framework-cli package",
|
|
914
|
-
doctor: "reset-framework-cli doctor",
|
|
915
|
-
lint: currentScripts.lint ?? "eslint .",
|
|
916
|
-
preview: currentScripts.preview ?? "vite preview"
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
if (options.packageManagerVersion) {
|
|
920
|
-
frontendPackage.packageManager = `${options.packageManager}@${options.packageManagerVersion}`
|
|
921
|
-
}
|
|
922
|
-
} else {
|
|
923
|
-
frontendPackage.name = `${options.appName}-frontend`
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
await writeFile(packageJsonPath, JSON.stringify(frontendPackage, null, 2) + "\n", "utf8")
|
|
927
|
-
progress.tick("Finalized starter metadata")
|
|
928
|
-
|
|
929
|
-
if (options.installDependencies) {
|
|
930
|
-
await runCommand(
|
|
931
|
-
resolvePackageManagerCommand(options.packageManager),
|
|
932
|
-
getInstallCommandArgs(options.packageManager),
|
|
933
|
-
{
|
|
934
|
-
cwd: options.targetDir
|
|
935
|
-
}
|
|
936
|
-
)
|
|
937
|
-
progress.tick("Installed frontend dependencies")
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
console.log("")
|
|
941
|
-
printSection("Result")
|
|
942
|
-
printStatusTable([
|
|
943
|
-
["done", "Project", options.targetDir],
|
|
944
|
-
["done", "Next step", `cd ${formatTargetForShell(options.targetDir)} && ${options.packageManager} run dev`]
|
|
945
|
-
])
|
|
946
|
-
}
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs"
|
|
2
|
+
import { cp, mkdir, readFile, readdir, writeFile } from "node:fs/promises"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
captureCommandOutput,
|
|
7
|
+
getInstallCommandArgs,
|
|
8
|
+
resolvePackageManagerCommand,
|
|
9
|
+
runCommand
|
|
10
|
+
} from "../lib/process.js"
|
|
11
|
+
import {
|
|
12
|
+
makeAppMetadata,
|
|
13
|
+
resolveBackendDependencySpec,
|
|
14
|
+
resolveCliDependencySpec,
|
|
15
|
+
resolveFrameworkPaths,
|
|
16
|
+
resolveSdkDependencySpec
|
|
17
|
+
} from "../lib/project.js"
|
|
18
|
+
import {
|
|
19
|
+
createProgress,
|
|
20
|
+
isInteractiveSession,
|
|
21
|
+
printBanner,
|
|
22
|
+
printKeyValueTable,
|
|
23
|
+
printSection,
|
|
24
|
+
printStatusTable,
|
|
25
|
+
promptConfirm,
|
|
26
|
+
promptSelect,
|
|
27
|
+
promptText,
|
|
28
|
+
withPromptSession
|
|
29
|
+
} from "../lib/ui.js"
|
|
30
|
+
|
|
31
|
+
const sdkRuntimeReexport = `export * from '@reset-framework/sdk'
|
|
32
|
+
`
|
|
33
|
+
|
|
34
|
+
const backendTsconfig = `{
|
|
35
|
+
"compilerOptions": {
|
|
36
|
+
"target": "ES2023",
|
|
37
|
+
"module": "ESNext",
|
|
38
|
+
"moduleResolution": "bundler",
|
|
39
|
+
"strict": true,
|
|
40
|
+
"skipLibCheck": true,
|
|
41
|
+
"verbatimModuleSyntax": true,
|
|
42
|
+
"noEmit": true
|
|
43
|
+
},
|
|
44
|
+
"include": ["src"]
|
|
45
|
+
}
|
|
46
|
+
`
|
|
47
|
+
|
|
48
|
+
const backendStarterSource = `import { defineBackend } from '@reset-framework/backend'
|
|
49
|
+
|
|
50
|
+
export default defineBackend(({ handle, process }) => {
|
|
51
|
+
handle('backend.ping', async () => {
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
runtime: globalThis.process.release?.name ?? 'node',
|
|
55
|
+
version: globalThis.process.version
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
handle('backend.execVersion', async () => {
|
|
60
|
+
return process.run({
|
|
61
|
+
command: globalThis.process.execPath,
|
|
62
|
+
args: ['--version']
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
`
|
|
67
|
+
|
|
68
|
+
const standaloneViteConfig = `import { defineConfig } from 'vite'
|
|
69
|
+
import react from '@vitejs/plugin-react'
|
|
70
|
+
|
|
71
|
+
export default defineConfig({
|
|
72
|
+
base: './',
|
|
73
|
+
plugins: [react()],
|
|
74
|
+
})
|
|
75
|
+
`
|
|
76
|
+
|
|
77
|
+
const tailwindViteConfig = `import { defineConfig } from 'vite'
|
|
78
|
+
import react from '@vitejs/plugin-react'
|
|
79
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
80
|
+
|
|
81
|
+
export default defineConfig({
|
|
82
|
+
base: './',
|
|
83
|
+
plugins: [react(), tailwindcss()],
|
|
84
|
+
})
|
|
85
|
+
`
|
|
86
|
+
|
|
87
|
+
const standaloneTsconfigApp = `{
|
|
88
|
+
"compilerOptions": {
|
|
89
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
90
|
+
"target": "ES2023",
|
|
91
|
+
"useDefineForClassFields": true,
|
|
92
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
93
|
+
"module": "ESNext",
|
|
94
|
+
"types": ["vite/client"],
|
|
95
|
+
"skipLibCheck": true,
|
|
96
|
+
"moduleResolution": "bundler",
|
|
97
|
+
"allowImportingTsExtensions": true,
|
|
98
|
+
"verbatimModuleSyntax": true,
|
|
99
|
+
"moduleDetection": "force",
|
|
100
|
+
"noEmit": true,
|
|
101
|
+
"jsx": "react-jsx",
|
|
102
|
+
"strict": true,
|
|
103
|
+
"noUnusedLocals": true,
|
|
104
|
+
"noUnusedParameters": true,
|
|
105
|
+
"erasableSyntaxOnly": true,
|
|
106
|
+
"noFallthroughCasesInSwitch": true,
|
|
107
|
+
"noUncheckedSideEffectImports": true
|
|
108
|
+
},
|
|
109
|
+
"include": ["src"]
|
|
110
|
+
}
|
|
111
|
+
`
|
|
112
|
+
|
|
113
|
+
const tailwindIndexCss = `@import "tailwindcss";
|
|
114
|
+
|
|
115
|
+
:root {
|
|
116
|
+
color-scheme: dark;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
body {
|
|
120
|
+
margin: 0;
|
|
121
|
+
font-family: "Avenir Next", "Segoe UI", sans-serif;
|
|
122
|
+
background: #111111;
|
|
123
|
+
}
|
|
124
|
+
`
|
|
125
|
+
|
|
126
|
+
const tailwindAppTsx = `import { useEffect, useState } from 'react'
|
|
127
|
+
import { reset } from './lib/reset'
|
|
128
|
+
|
|
129
|
+
function App() {
|
|
130
|
+
const [count, setCount] = useState(0)
|
|
131
|
+
const [appName, setAppName] = useState('Reset App')
|
|
132
|
+
const [appVersion, setAppVersion] = useState('0.0.0')
|
|
133
|
+
const [platform, setPlatform] = useState('unknown')
|
|
134
|
+
const [bridgeVersion, setBridgeVersion] = useState('1')
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
let cancelled = false
|
|
138
|
+
|
|
139
|
+
async function loadRuntimeInfo() {
|
|
140
|
+
try {
|
|
141
|
+
const [appInfo, runtimeInfo] = await Promise.all([
|
|
142
|
+
reset.app.getInfo(),
|
|
143
|
+
reset.runtime.getInfo(),
|
|
144
|
+
])
|
|
145
|
+
|
|
146
|
+
if (cancelled) {
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setAppName(appInfo.name)
|
|
151
|
+
setAppVersion(appInfo.version)
|
|
152
|
+
setPlatform(runtimeInfo.platform)
|
|
153
|
+
setBridgeVersion(runtimeInfo.bridgeVersion)
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('Failed to load runtime info', error)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
loadRuntimeInfo()
|
|
160
|
+
|
|
161
|
+
return () => {
|
|
162
|
+
cancelled = true
|
|
163
|
+
}
|
|
164
|
+
}, [])
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<main className="min-h-screen bg-stone-950 text-stone-100">
|
|
168
|
+
<div className="mx-auto flex min-h-screen max-w-6xl flex-col px-6 py-12 lg:px-10">
|
|
169
|
+
<div className="inline-flex w-fit items-center rounded-full border border-white/10 bg-white/5 px-4 py-2 text-sm tracking-[0.18em] text-stone-300 uppercase">
|
|
170
|
+
{appName} v{appVersion} on {platform}
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<div className="mt-12 grid gap-12 lg:grid-cols-[minmax(0,1.4fr)_minmax(20rem,0.8fr)] lg:items-end">
|
|
174
|
+
<section>
|
|
175
|
+
<p className="text-sm font-medium uppercase tracking-[0.32em] text-amber-300">
|
|
176
|
+
Reset Framework
|
|
177
|
+
</p>
|
|
178
|
+
<h1 className="mt-5 max-w-4xl text-5xl font-medium leading-[0.95] tracking-[-0.06em] text-white sm:text-6xl lg:text-7xl">
|
|
179
|
+
Tailwind-ready desktop apps with a native runtime underneath.
|
|
180
|
+
</h1>
|
|
181
|
+
<p className="mt-6 max-w-2xl text-lg leading-8 text-stone-300">
|
|
182
|
+
Edit the frontend, call native commands through the runtime bridge, and keep the
|
|
183
|
+
host layer out of the way while you build the actual product.
|
|
184
|
+
</p>
|
|
185
|
+
</section>
|
|
186
|
+
|
|
187
|
+
<section className="rounded-[2rem] border border-white/10 bg-white/5 p-6 shadow-2xl shadow-black/20 backdrop-blur">
|
|
188
|
+
<div className="flex items-center justify-between text-sm text-stone-400">
|
|
189
|
+
<span>Runtime bridge</span>
|
|
190
|
+
<span>bridge v{bridgeVersion}</span>
|
|
191
|
+
</div>
|
|
192
|
+
<div className="mt-6 rounded-2xl border border-white/10 bg-black/30 p-5">
|
|
193
|
+
<div className="text-xs uppercase tracking-[0.24em] text-stone-500">
|
|
194
|
+
Counter
|
|
195
|
+
</div>
|
|
196
|
+
<div className="mt-4 text-4xl font-semibold text-white">{count}</div>
|
|
197
|
+
<button
|
|
198
|
+
className="mt-6 inline-flex items-center rounded-full bg-amber-300 px-5 py-3 text-sm font-semibold text-stone-950 transition hover:bg-amber-200"
|
|
199
|
+
onClick={() => setCount((value) => value + 1)}
|
|
200
|
+
>
|
|
201
|
+
Increment
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
</section>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</main>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export default App
|
|
212
|
+
`
|
|
213
|
+
|
|
214
|
+
export const description = "Scaffold a starter app with an interactive project setup"
|
|
215
|
+
|
|
216
|
+
function listTemplates(templatesDir) {
|
|
217
|
+
return readdirSync(templatesDir, { withFileTypes: true })
|
|
218
|
+
.filter((entry) => entry.isDirectory())
|
|
219
|
+
.map((entry) => entry.name)
|
|
220
|
+
.sort()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function getRootGitignore(frontendDir) {
|
|
224
|
+
if (frontendDir === ".") {
|
|
225
|
+
return `.DS_Store
|
|
226
|
+
Thumbs.db
|
|
227
|
+
.idea/
|
|
228
|
+
.vs/
|
|
229
|
+
.vscode/
|
|
230
|
+
.cache/
|
|
231
|
+
.reset/
|
|
232
|
+
.turbo/
|
|
233
|
+
build/
|
|
234
|
+
coverage/
|
|
235
|
+
dist/
|
|
236
|
+
node_modules/
|
|
237
|
+
npm-debug.log*
|
|
238
|
+
yarn-debug.log*
|
|
239
|
+
yarn-error.log*
|
|
240
|
+
.pnpm-debug.log*
|
|
241
|
+
*.log
|
|
242
|
+
*.zip
|
|
243
|
+
*.tgz
|
|
244
|
+
*.tsbuildinfo
|
|
245
|
+
*.dSYM/
|
|
246
|
+
.env
|
|
247
|
+
.env.*
|
|
248
|
+
.env.local
|
|
249
|
+
.env.*.local
|
|
250
|
+
.eslintcache
|
|
251
|
+
bun.lock
|
|
252
|
+
bun.lockb
|
|
253
|
+
compile_commands.json
|
|
254
|
+
CMakeUserPresets.json
|
|
255
|
+
CMakeCache.txt
|
|
256
|
+
CMakeFiles/
|
|
257
|
+
cmake_install.cmake
|
|
258
|
+
install_manifest.txt
|
|
259
|
+
cmake-build-*/
|
|
260
|
+
Testing/
|
|
261
|
+
scripts/local/
|
|
262
|
+
`
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return `.DS_Store
|
|
266
|
+
Thumbs.db
|
|
267
|
+
.idea/
|
|
268
|
+
.vs/
|
|
269
|
+
.vscode/
|
|
270
|
+
.cache/
|
|
271
|
+
.reset/
|
|
272
|
+
.turbo/
|
|
273
|
+
build/
|
|
274
|
+
coverage/
|
|
275
|
+
node_modules/
|
|
276
|
+
${frontendDir}/node_modules/
|
|
277
|
+
${frontendDir}/dist/
|
|
278
|
+
npm-debug.log*
|
|
279
|
+
yarn-debug.log*
|
|
280
|
+
yarn-error.log*
|
|
281
|
+
.pnpm-debug.log*
|
|
282
|
+
*.log
|
|
283
|
+
*.zip
|
|
284
|
+
*.tgz
|
|
285
|
+
*.tsbuildinfo
|
|
286
|
+
*.dSYM/
|
|
287
|
+
.env
|
|
288
|
+
.env.*
|
|
289
|
+
.env.local
|
|
290
|
+
.env.*.local
|
|
291
|
+
.eslintcache
|
|
292
|
+
bun.lock
|
|
293
|
+
bun.lockb
|
|
294
|
+
compile_commands.json
|
|
295
|
+
CMakeUserPresets.json
|
|
296
|
+
CMakeCache.txt
|
|
297
|
+
CMakeFiles/
|
|
298
|
+
cmake_install.cmake
|
|
299
|
+
install_manifest.txt
|
|
300
|
+
cmake-build-*/
|
|
301
|
+
Testing/
|
|
302
|
+
scripts/local/
|
|
303
|
+
`
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function resolveDefaultFrontendDir(context) {
|
|
307
|
+
if (typeof context.flags["frontend-dir"] === "string") {
|
|
308
|
+
return context.flags["frontend-dir"]
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (context.flags.flat) {
|
|
312
|
+
return "."
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return "frontend"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function resolveDefaultStyling(context) {
|
|
319
|
+
if (context.flags.tailwind) {
|
|
320
|
+
return "tailwindcss"
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (context.flags["no-tailwind"]) {
|
|
324
|
+
return "css"
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return "css"
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function resolvePackageManagerOption(context) {
|
|
331
|
+
if (typeof context.flags["package-manager"] === "string") {
|
|
332
|
+
return context.flags["package-manager"]
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return "npm"
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function describeInstallCommand(packageManager) {
|
|
339
|
+
return [packageManager, ...getInstallCommandArgs(packageManager)].join(" ")
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function createFrontendScriptCommand(packageManager, frontendDir, scriptName) {
|
|
343
|
+
const frontendPath = JSON.stringify(frontendDir.replace(/\\/g, "/"))
|
|
344
|
+
|
|
345
|
+
if (packageManager === "npm") {
|
|
346
|
+
return `npm --workspace ${frontendPath} run ${scriptName} --`
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (packageManager === "pnpm") {
|
|
350
|
+
return `pnpm --dir ${frontendPath} run ${scriptName} --`
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (packageManager === "yarn") {
|
|
354
|
+
return `yarn --cwd ${frontendPath} run ${scriptName}`
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (packageManager === "bun") {
|
|
358
|
+
return `bun --cwd ${frontendPath} run ${scriptName}`
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
throw new Error(`Unsupported package manager: ${packageManager}`)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function ensureWritableTarget(targetDir, force) {
|
|
365
|
+
if (!existsSync(targetDir)) {
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const entries = await readdir(targetDir)
|
|
370
|
+
|
|
371
|
+
if (entries.length > 0 && !force) {
|
|
372
|
+
throw new Error(`Target directory is not empty: ${targetDir}. Use --force to continue.`)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function copyFrontendTemplate(sourceFrontendDir, targetFrontendDir, options) {
|
|
377
|
+
const { force, frontendDir } = options
|
|
378
|
+
const flatLayout = frontendDir === "."
|
|
379
|
+
|
|
380
|
+
if (!flatLayout) {
|
|
381
|
+
await cp(sourceFrontendDir, targetFrontendDir, {
|
|
382
|
+
recursive: true,
|
|
383
|
+
force,
|
|
384
|
+
filter(source) {
|
|
385
|
+
const baseName = path.basename(source)
|
|
386
|
+
return (
|
|
387
|
+
baseName !== "node_modules" &&
|
|
388
|
+
baseName !== "dist" &&
|
|
389
|
+
baseName !== "bun.lockb" &&
|
|
390
|
+
baseName !== "reset.config.json"
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
await mkdir(targetFrontendDir, { recursive: true })
|
|
399
|
+
|
|
400
|
+
const entries = await readdir(sourceFrontendDir, { withFileTypes: true })
|
|
401
|
+
for (const entry of entries) {
|
|
402
|
+
if (["node_modules", "dist", "bun.lockb", "reset.config.json", ".gitignore", "README.md"].includes(entry.name)) {
|
|
403
|
+
continue
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
await cp(
|
|
407
|
+
path.join(sourceFrontendDir, entry.name),
|
|
408
|
+
path.join(targetFrontendDir, entry.name),
|
|
409
|
+
{ recursive: true, force }
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function applyFrontendOverrides(frontendDir, options) {
|
|
415
|
+
await writeFile(path.join(frontendDir, "src", "lib", "reset.ts"), sdkRuntimeReexport, "utf8")
|
|
416
|
+
await writeFile(path.join(frontendDir, "tsconfig.app.json"), standaloneTsconfigApp, "utf8")
|
|
417
|
+
|
|
418
|
+
const packageJsonPath = path.join(frontendDir, "package.json")
|
|
419
|
+
const frontendPackage = JSON.parse(await readFile(packageJsonPath, "utf8"))
|
|
420
|
+
frontendPackage.dependencies = {
|
|
421
|
+
...frontendPackage.dependencies,
|
|
422
|
+
"@reset-framework/sdk": options.sdkDependencySpec
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (options.styling === "tailwindcss") {
|
|
426
|
+
frontendPackage.devDependencies = {
|
|
427
|
+
...frontendPackage.devDependencies,
|
|
428
|
+
"@tailwindcss/vite": "latest",
|
|
429
|
+
tailwindcss: "latest"
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
await writeFile(packageJsonPath, JSON.stringify(frontendPackage, null, 2) + "\n", "utf8")
|
|
433
|
+
await writeFile(path.join(frontendDir, "vite.config.ts"), tailwindViteConfig, "utf8")
|
|
434
|
+
await writeFile(path.join(frontendDir, "src", "index.css"), tailwindIndexCss, "utf8")
|
|
435
|
+
await writeFile(path.join(frontendDir, "src", "App.tsx"), tailwindAppTsx, "utf8")
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
await writeFile(packageJsonPath, JSON.stringify(frontendPackage, null, 2) + "\n", "utf8")
|
|
440
|
+
await writeFile(path.join(frontendDir, "vite.config.ts"), standaloneViteConfig, "utf8")
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function createRootPackageJson(options) {
|
|
444
|
+
const packageManagerVersion = options.packageManagerVersion
|
|
445
|
+
? `${options.packageManager}@${options.packageManagerVersion}`
|
|
446
|
+
: undefined
|
|
447
|
+
|
|
448
|
+
const base = {
|
|
449
|
+
name: options.appName,
|
|
450
|
+
private: true,
|
|
451
|
+
version: "0.1.0",
|
|
452
|
+
description: `${options.productName} desktop app`,
|
|
453
|
+
scripts: {
|
|
454
|
+
dev: "reset-framework-cli dev",
|
|
455
|
+
build: "reset-framework-cli build",
|
|
456
|
+
package: "reset-framework-cli package",
|
|
457
|
+
doctor: "reset-framework-cli doctor"
|
|
458
|
+
},
|
|
459
|
+
devDependencies: {
|
|
460
|
+
"reset-framework-cli": options.cliDependencySpec
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (packageManagerVersion) {
|
|
465
|
+
base.packageManager = packageManagerVersion
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (options.frontendDir !== ".") {
|
|
469
|
+
base.workspaces = [options.frontendDir, "backend"]
|
|
470
|
+
base.scripts["dev:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "dev")
|
|
471
|
+
base.scripts["build:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "build")
|
|
472
|
+
base.scripts["lint:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "lint")
|
|
473
|
+
base.scripts["preview:web"] = createFrontendScriptCommand(options.packageManager, options.frontendDir, "preview")
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return base
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function createRootReadme(options) {
|
|
480
|
+
const run = `${options.packageManager} run`
|
|
481
|
+
const webPath = options.frontendDir === "." ? "project root" : `${options.frontendDir}/`
|
|
482
|
+
|
|
483
|
+
return `# ${options.productName}
|
|
484
|
+
|
|
485
|
+
Generated with \`reset-framework-cli\`.
|
|
486
|
+
|
|
487
|
+
## Commands
|
|
488
|
+
|
|
489
|
+
- \`${run} dev\`: start the frontend dev server and native desktop runtime
|
|
490
|
+
- \`${run} build\`: build the frontend and stage the desktop app bundle
|
|
491
|
+
- \`${run} package\`: archive the built desktop app
|
|
492
|
+
- \`${run} doctor\`: inspect the project and framework installation
|
|
493
|
+
|
|
494
|
+
## Web-only commands
|
|
495
|
+
|
|
496
|
+
${options.frontendDir === "." ? `- \`${run} dev:web\`: run the Vite frontend only
|
|
497
|
+
- \`${run} build:web\`: build the frontend only` : `- \`${run} dev:web\`: run the frontend workspace only
|
|
498
|
+
- \`${run} build:web\`: build the frontend workspace only`}
|
|
499
|
+
|
|
500
|
+
## Project files
|
|
501
|
+
|
|
502
|
+
- \`reset.config.json\`: desktop app metadata and runtime configuration
|
|
503
|
+
- \`${webPath}\`: web frontend source
|
|
504
|
+
- \`backend/\`: optional TypeScript sidecar backend
|
|
505
|
+
- \`.reset/\`: generated build output and native runtime cache
|
|
506
|
+
`
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function createBackendPackageJson(options) {
|
|
510
|
+
return {
|
|
511
|
+
name: `${options.appName}-backend`,
|
|
512
|
+
private: true,
|
|
513
|
+
version: "0.1.0",
|
|
514
|
+
type: "module",
|
|
515
|
+
dependencies: {
|
|
516
|
+
"@reset-framework/backend": options.backendDependencySpec
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function writeBackendFiles(options) {
|
|
522
|
+
const backendDir = path.join(options.targetDir, "backend")
|
|
523
|
+
const backendSrcDir = path.join(backendDir, "src")
|
|
524
|
+
|
|
525
|
+
await mkdir(backendSrcDir, { recursive: true })
|
|
526
|
+
await writeFile(
|
|
527
|
+
path.join(backendDir, "package.json"),
|
|
528
|
+
JSON.stringify(createBackendPackageJson(options), null, 2) + "\n",
|
|
529
|
+
"utf8"
|
|
530
|
+
)
|
|
531
|
+
await writeFile(path.join(backendDir, "tsconfig.json"), backendTsconfig, "utf8")
|
|
532
|
+
await writeFile(path.join(backendSrcDir, "index.ts"), backendStarterSource, "utf8")
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async function writeRootPackageFiles(options) {
|
|
536
|
+
await writeFile(
|
|
537
|
+
path.join(options.targetDir, "README.md"),
|
|
538
|
+
createRootReadme(options),
|
|
539
|
+
"utf8"
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
if (options.frontendDir !== ".") {
|
|
543
|
+
await writeFile(
|
|
544
|
+
path.join(options.targetDir, "package.json"),
|
|
545
|
+
JSON.stringify(createRootPackageJson(options), null, 2) + "\n",
|
|
546
|
+
"utf8"
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function writeProjectFiles(options) {
|
|
552
|
+
const templateConfig = JSON.parse(await readFile(options.templateConfigPath, "utf8"))
|
|
553
|
+
|
|
554
|
+
await writeFile(
|
|
555
|
+
path.join(options.targetDir, "reset.config.json"),
|
|
556
|
+
JSON.stringify(
|
|
557
|
+
{
|
|
558
|
+
...templateConfig,
|
|
559
|
+
name: options.appName,
|
|
560
|
+
productName: options.productName,
|
|
561
|
+
appId: options.appId,
|
|
562
|
+
project: {
|
|
563
|
+
frontendDir: options.frontendDir,
|
|
564
|
+
styling: options.styling
|
|
565
|
+
},
|
|
566
|
+
security: {
|
|
567
|
+
permissions: Array.from(
|
|
568
|
+
new Set([
|
|
569
|
+
...(Array.isArray(templateConfig.security?.permissions)
|
|
570
|
+
? templateConfig.security.permissions
|
|
571
|
+
: []),
|
|
572
|
+
"backend.*"
|
|
573
|
+
])
|
|
574
|
+
)
|
|
575
|
+
},
|
|
576
|
+
window: {
|
|
577
|
+
...templateConfig.window,
|
|
578
|
+
title: options.productName
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
null,
|
|
582
|
+
2
|
|
583
|
+
) + "\n",
|
|
584
|
+
"utf8"
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
await writeFile(
|
|
588
|
+
path.join(options.targetDir, ".gitignore"),
|
|
589
|
+
getRootGitignore(options.frontendDir),
|
|
590
|
+
"utf8"
|
|
591
|
+
)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async function resolvePackageManagerVersion(packageManager) {
|
|
595
|
+
if (packageManager !== "npm" && packageManager !== "pnpm" && packageManager !== "yarn" && packageManager !== "bun") {
|
|
596
|
+
return null
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
const command = resolvePackageManagerCommand(packageManager)
|
|
601
|
+
const result = await captureCommandOutput(command, ["--version"])
|
|
602
|
+
return typeof result === "string" && result.trim() !== "" ? result.trim() : null
|
|
603
|
+
} catch {
|
|
604
|
+
return null
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function collectCreateAppOptions(context, templates) {
|
|
609
|
+
const initialTarget = context.args[0] ?? "my-app"
|
|
610
|
+
const defaultTargetDir = path.resolve(context.cwd, initialTarget)
|
|
611
|
+
const defaultMetadata = makeAppMetadata(path.basename(defaultTargetDir))
|
|
612
|
+
const frameworkPaths = resolveFrameworkPaths()
|
|
613
|
+
|
|
614
|
+
const defaults = {
|
|
615
|
+
targetDir: defaultTargetDir,
|
|
616
|
+
templateName: typeof context.flags.template === "string" ? context.flags.template : "basic",
|
|
617
|
+
appName: defaultMetadata.name,
|
|
618
|
+
productName:
|
|
619
|
+
typeof context.flags["product-name"] === "string"
|
|
620
|
+
? context.flags["product-name"]
|
|
621
|
+
: defaultMetadata.productName,
|
|
622
|
+
appId:
|
|
623
|
+
typeof context.flags["app-id"] === "string"
|
|
624
|
+
? context.flags["app-id"]
|
|
625
|
+
: defaultMetadata.appId,
|
|
626
|
+
frontendDir: resolveDefaultFrontendDir(context),
|
|
627
|
+
styling: resolveDefaultStyling(context)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const interactive = isInteractiveSession() && !context.flags.yes && !context.flags["no-prompt"]
|
|
631
|
+
|
|
632
|
+
if (!interactive) {
|
|
633
|
+
if (!context.args[0]) {
|
|
634
|
+
throw new Error("Missing target directory. Use: reset-framework-cli create-app <directory>")
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
...defaults,
|
|
639
|
+
templateDir: path.join(frameworkPaths.templatesDir, defaults.templateName),
|
|
640
|
+
templateConfigPath: path.join(frameworkPaths.templatesDir, defaults.templateName, "reset.config.json"),
|
|
641
|
+
sourceFrontendDir: path.join(frameworkPaths.templatesDir, defaults.templateName, "frontend"),
|
|
642
|
+
backendDependencySpec: resolveBackendDependencySpec(frameworkPaths),
|
|
643
|
+
cliDependencySpec: resolveCliDependencySpec(frameworkPaths),
|
|
644
|
+
sdkDependencySpec: resolveSdkDependencySpec(frameworkPaths),
|
|
645
|
+
packageManager: resolvePackageManagerOption(context),
|
|
646
|
+
packageManagerVersion: await resolvePackageManagerVersion(resolvePackageManagerOption(context)),
|
|
647
|
+
installDependencies: !context.flags["no-install"],
|
|
648
|
+
force: Boolean(context.flags.force)
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const selected = await withPromptSession(async (rl) => {
|
|
653
|
+
const targetInput = await promptText(rl, {
|
|
654
|
+
label: "Project directory",
|
|
655
|
+
defaultValue: path.basename(defaults.targetDir),
|
|
656
|
+
validate(value) {
|
|
657
|
+
return value.trim() !== "" || "Enter a directory name."
|
|
658
|
+
}
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
const targetDir = path.resolve(context.cwd, targetInput)
|
|
662
|
+
const metadata = makeAppMetadata(path.basename(targetDir))
|
|
663
|
+
|
|
664
|
+
const templateName =
|
|
665
|
+
templates.length === 1
|
|
666
|
+
? templates[0]
|
|
667
|
+
: await promptSelect(rl, {
|
|
668
|
+
label: "Template",
|
|
669
|
+
defaultValue: defaults.templateName,
|
|
670
|
+
choices: templates.map((template) => ({
|
|
671
|
+
value: template,
|
|
672
|
+
label: template,
|
|
673
|
+
description: "Starter project scaffold"
|
|
674
|
+
}))
|
|
675
|
+
})
|
|
676
|
+
|
|
677
|
+
const productName = await promptText(rl, {
|
|
678
|
+
label: "Product name",
|
|
679
|
+
defaultValue: defaults.productName || metadata.productName,
|
|
680
|
+
validate(value) {
|
|
681
|
+
return value.trim() !== "" || "Enter a product name."
|
|
682
|
+
}
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
const appId = await promptText(rl, {
|
|
686
|
+
label: "App ID",
|
|
687
|
+
defaultValue: defaults.appId || metadata.appId,
|
|
688
|
+
validate(value) {
|
|
689
|
+
return value.includes(".") || "Use a reverse-domain identifier such as com.example.my-app."
|
|
690
|
+
}
|
|
691
|
+
})
|
|
692
|
+
|
|
693
|
+
const frontendDir = await promptSelect(rl, {
|
|
694
|
+
label: "Frontend layout",
|
|
695
|
+
defaultValue: defaults.frontendDir === "." ? "." : "frontend",
|
|
696
|
+
choices: [
|
|
697
|
+
{
|
|
698
|
+
value: "frontend",
|
|
699
|
+
label: "Use a frontend/ subdirectory",
|
|
700
|
+
description: "Keeps the web app separate from config, output, and packaging files."
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
value: ".",
|
|
704
|
+
label: "Keep the frontend at project root",
|
|
705
|
+
description: "Flatter project layout with package.json and src/ directly in the app root."
|
|
706
|
+
}
|
|
707
|
+
]
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
const styling = await promptSelect(rl, {
|
|
711
|
+
label: "Styling setup",
|
|
712
|
+
defaultValue: defaults.styling,
|
|
713
|
+
choices: [
|
|
714
|
+
{
|
|
715
|
+
value: "css",
|
|
716
|
+
label: "Vanilla CSS starter",
|
|
717
|
+
description: "Use the existing CSS-based starter with no extra styling dependency."
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
value: "tailwindcss",
|
|
721
|
+
label: "Tailwind CSS starter",
|
|
722
|
+
description: "Configure Tailwind CSS and swap the starter screen to utility classes."
|
|
723
|
+
}
|
|
724
|
+
]
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
const installDependencies = await promptConfirm(rl, {
|
|
728
|
+
label: "Install frontend dependencies after scaffolding",
|
|
729
|
+
defaultValue: true
|
|
730
|
+
})
|
|
731
|
+
|
|
732
|
+
const packageManager = await promptSelect(rl, {
|
|
733
|
+
label: "Package manager",
|
|
734
|
+
defaultValue: resolvePackageManagerOption(context),
|
|
735
|
+
choices: [
|
|
736
|
+
{
|
|
737
|
+
value: "npm",
|
|
738
|
+
label: "npm",
|
|
739
|
+
description: "Wide compatibility and predictable installs."
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
value: "bun",
|
|
743
|
+
label: "bun",
|
|
744
|
+
description: "Fast installs and lets bun run the root desktop scripts."
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
value: "pnpm",
|
|
748
|
+
label: "pnpm",
|
|
749
|
+
description: "Strict dependency graph with workspace support."
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
value: "yarn",
|
|
753
|
+
label: "yarn",
|
|
754
|
+
description: "Classic workspace-based JavaScript package workflow."
|
|
755
|
+
}
|
|
756
|
+
]
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
printSection("Summary")
|
|
760
|
+
printKeyValueTable([
|
|
761
|
+
["Directory", targetDir],
|
|
762
|
+
["Template", templateName],
|
|
763
|
+
["Product", productName],
|
|
764
|
+
["App ID", appId],
|
|
765
|
+
["Frontend", frontendDir === "." ? "project root" : `${frontendDir}/`],
|
|
766
|
+
["Styling", styling],
|
|
767
|
+
["Package manager", packageManager],
|
|
768
|
+
["Install", installDependencies ? "yes" : "no"]
|
|
769
|
+
])
|
|
770
|
+
console.log("")
|
|
771
|
+
|
|
772
|
+
const confirmed = await promptConfirm(rl, {
|
|
773
|
+
label: "Create this project",
|
|
774
|
+
defaultValue: true
|
|
775
|
+
})
|
|
776
|
+
|
|
777
|
+
if (!confirmed) {
|
|
778
|
+
throw new Error("Create app cancelled.")
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
return {
|
|
782
|
+
targetDir,
|
|
783
|
+
templateName,
|
|
784
|
+
appName: metadata.name,
|
|
785
|
+
productName,
|
|
786
|
+
appId,
|
|
787
|
+
frontendDir,
|
|
788
|
+
styling,
|
|
789
|
+
installDependencies,
|
|
790
|
+
packageManager
|
|
791
|
+
}
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
return {
|
|
795
|
+
...selected,
|
|
796
|
+
templateDir: path.join(frameworkPaths.templatesDir, selected.templateName),
|
|
797
|
+
templateConfigPath: path.join(frameworkPaths.templatesDir, selected.templateName, "reset.config.json"),
|
|
798
|
+
sourceFrontendDir: path.join(frameworkPaths.templatesDir, selected.templateName, "frontend"),
|
|
799
|
+
backendDependencySpec: resolveBackendDependencySpec(frameworkPaths),
|
|
800
|
+
cliDependencySpec: resolveCliDependencySpec(frameworkPaths),
|
|
801
|
+
sdkDependencySpec: resolveSdkDependencySpec(frameworkPaths),
|
|
802
|
+
packageManagerVersion: await resolvePackageManagerVersion(selected.packageManager),
|
|
803
|
+
force: Boolean(context.flags.force)
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
export async function run(context) {
|
|
808
|
+
const frameworkPaths = resolveFrameworkPaths()
|
|
809
|
+
const templates = listTemplates(frameworkPaths.templatesDir)
|
|
810
|
+
const formatTargetForShell = (targetDir) => {
|
|
811
|
+
const relative = path.relative(context.cwd, targetDir)
|
|
812
|
+
if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))) {
|
|
813
|
+
return relative || "."
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return targetDir
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
printBanner("reset-framework-cli create-app", description)
|
|
820
|
+
const options = await collectCreateAppOptions(context, templates)
|
|
821
|
+
|
|
822
|
+
if (!templates.includes(options.templateName)) {
|
|
823
|
+
throw new Error(`Unknown template: ${options.templateName}`)
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const targetFrontendDir =
|
|
827
|
+
options.frontendDir === "."
|
|
828
|
+
? options.targetDir
|
|
829
|
+
: path.join(options.targetDir, options.frontendDir)
|
|
830
|
+
|
|
831
|
+
await ensureWritableTarget(options.targetDir, options.force)
|
|
832
|
+
|
|
833
|
+
printSection("Project")
|
|
834
|
+
printKeyValueTable([
|
|
835
|
+
["Directory", options.targetDir],
|
|
836
|
+
["Template", options.templateName],
|
|
837
|
+
["Product", options.productName],
|
|
838
|
+
["App ID", options.appId],
|
|
839
|
+
["Frontend", options.frontendDir === "." ? "project root" : `${options.frontendDir}/`],
|
|
840
|
+
["Styling", options.styling],
|
|
841
|
+
["Package manager", options.packageManager],
|
|
842
|
+
["Install", options.installDependencies ? describeInstallCommand(options.packageManager) : "skipped"]
|
|
843
|
+
])
|
|
844
|
+
|
|
845
|
+
if (Boolean(context.flags["dry-run"])) {
|
|
846
|
+
console.log("")
|
|
847
|
+
printSection("Dry run")
|
|
848
|
+
printStatusTable([
|
|
849
|
+
["plan", "Copy starter", `${options.sourceFrontendDir} -> ${targetFrontendDir}`],
|
|
850
|
+
["plan", "Write README", path.join(options.targetDir, "README.md")],
|
|
851
|
+
["plan", "Write config", path.join(options.targetDir, "reset.config.json")],
|
|
852
|
+
["plan", "Write ignore file", path.join(options.targetDir, ".gitignore")],
|
|
853
|
+
["plan", "Write app package", path.join(options.targetDir, "package.json")],
|
|
854
|
+
["plan", "Write backend package", path.join(options.targetDir, "backend", "package.json")],
|
|
855
|
+
["plan", "Write backend entry", path.join(options.targetDir, "backend", "src", "index.ts")],
|
|
856
|
+
["plan", "Write SDK bridge", path.join(targetFrontendDir, "src", "lib", "reset.ts")],
|
|
857
|
+
["plan", "Write Vite config", path.join(targetFrontendDir, "vite.config.ts")],
|
|
858
|
+
["plan", "Write TS config", path.join(targetFrontendDir, "tsconfig.app.json")]
|
|
859
|
+
])
|
|
860
|
+
if (options.styling === "tailwindcss") {
|
|
861
|
+
printStatusTable([
|
|
862
|
+
["plan", "Patch package.json", path.join(targetFrontendDir, "package.json")],
|
|
863
|
+
["plan", "Write styles", path.join(targetFrontendDir, "src", "index.css")],
|
|
864
|
+
["plan", "Write starter screen", path.join(targetFrontendDir, "src", "App.tsx")]
|
|
865
|
+
])
|
|
866
|
+
}
|
|
867
|
+
if (options.installDependencies) {
|
|
868
|
+
printStatusTable([
|
|
869
|
+
["plan", "Install dependencies", `${describeInstallCommand(options.packageManager)} in ${options.targetDir}`]
|
|
870
|
+
])
|
|
871
|
+
}
|
|
872
|
+
return
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
console.log("")
|
|
876
|
+
const progress = createProgress(options.installDependencies ? 7 : 6, "Scaffold")
|
|
877
|
+
|
|
878
|
+
await mkdir(options.targetDir, { recursive: true })
|
|
879
|
+
progress.tick("Prepared project directory")
|
|
880
|
+
|
|
881
|
+
await copyFrontendTemplate(options.sourceFrontendDir, targetFrontendDir, options)
|
|
882
|
+
progress.tick("Copied frontend starter")
|
|
883
|
+
|
|
884
|
+
await writeProjectFiles(options)
|
|
885
|
+
await writeRootPackageFiles(options)
|
|
886
|
+
progress.tick("Wrote project files")
|
|
887
|
+
|
|
888
|
+
await applyFrontendOverrides(targetFrontendDir, options)
|
|
889
|
+
progress.tick("Applied frontend package wiring")
|
|
890
|
+
|
|
891
|
+
await writeBackendFiles(options)
|
|
892
|
+
progress.tick("Wrote backend starter")
|
|
893
|
+
|
|
894
|
+
const packageJsonPath = path.join(targetFrontendDir, "package.json")
|
|
895
|
+
const frontendPackage = JSON.parse(await readFile(packageJsonPath, "utf8"))
|
|
896
|
+
|
|
897
|
+
if (options.frontendDir === ".") {
|
|
898
|
+
const currentScripts = { ...(frontendPackage.scripts ?? {}) }
|
|
899
|
+
frontendPackage.name = options.appName
|
|
900
|
+
frontendPackage.private = true
|
|
901
|
+
frontendPackage.version = "0.1.0"
|
|
902
|
+
frontendPackage.description = `${options.productName} desktop app`
|
|
903
|
+
frontendPackage.workspaces = ["backend"]
|
|
904
|
+
frontendPackage.devDependencies = {
|
|
905
|
+
...frontendPackage.devDependencies,
|
|
906
|
+
"reset-framework-cli": options.cliDependencySpec
|
|
907
|
+
}
|
|
908
|
+
frontendPackage.scripts = {
|
|
909
|
+
dev: "reset-framework-cli dev",
|
|
910
|
+
"dev:web": currentScripts.dev ?? "vite",
|
|
911
|
+
build: "reset-framework-cli build",
|
|
912
|
+
"build:web": currentScripts.build ?? "tsc -b && vite build",
|
|
913
|
+
package: "reset-framework-cli package",
|
|
914
|
+
doctor: "reset-framework-cli doctor",
|
|
915
|
+
lint: currentScripts.lint ?? "eslint .",
|
|
916
|
+
preview: currentScripts.preview ?? "vite preview"
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (options.packageManagerVersion) {
|
|
920
|
+
frontendPackage.packageManager = `${options.packageManager}@${options.packageManagerVersion}`
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
frontendPackage.name = `${options.appName}-frontend`
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
await writeFile(packageJsonPath, JSON.stringify(frontendPackage, null, 2) + "\n", "utf8")
|
|
927
|
+
progress.tick("Finalized starter metadata")
|
|
928
|
+
|
|
929
|
+
if (options.installDependencies) {
|
|
930
|
+
await runCommand(
|
|
931
|
+
resolvePackageManagerCommand(options.packageManager),
|
|
932
|
+
getInstallCommandArgs(options.packageManager),
|
|
933
|
+
{
|
|
934
|
+
cwd: options.targetDir
|
|
935
|
+
}
|
|
936
|
+
)
|
|
937
|
+
progress.tick("Installed frontend dependencies")
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
console.log("")
|
|
941
|
+
printSection("Result")
|
|
942
|
+
printStatusTable([
|
|
943
|
+
["done", "Project", options.targetDir],
|
|
944
|
+
["done", "Next step", `cd ${formatTargetForShell(options.targetDir)} && ${options.packageManager} run dev`]
|
|
945
|
+
])
|
|
946
|
+
}
|