reset-framework-cli 1.0.1 → 1.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reset-framework-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "description": "Command-line tooling for Reset Framework.",
6
6
  "license": "MIT",
@@ -11,9 +11,9 @@
11
11
  "node": ">=20.19.0"
12
12
  },
13
13
  "dependencies": {
14
- "@reset-framework/native": "1.0.1",
15
- "@reset-framework/schema": "1.0.1",
16
- "@reset-framework/sdk": "1.0.1"
14
+ "@reset-framework/native": "1.0.2",
15
+ "@reset-framework/schema": "1.0.2",
16
+ "@reset-framework/sdk": "1.0.2"
17
17
  },
18
18
  "bin": {
19
19
  "reset-framework-cli": "src/index.js"
@@ -1,12 +1,13 @@
1
1
  import { buildFrameworkRuntime } from "../lib/framework.js"
2
2
  import { stageMacOSAppBundle } from "../lib/output.js"
3
- import { resolveNpmCommand, runCommand } from "../lib/process.js"
3
+ import { resolvePackageManagerCommand, runCommand } from "../lib/process.js"
4
4
  import {
5
5
  assertAppProject,
6
6
  assertFrameworkInstall,
7
7
  loadResetConfig,
8
8
  resolveAppOutputPaths,
9
9
  resolveAppPaths,
10
+ resolveAppPackageManager,
10
11
  resolveConfigPath,
11
12
  resolveFrontendDir,
12
13
  resolveFrameworkBuildPaths,
@@ -34,6 +35,7 @@ export async function run(context) {
34
35
  assertFrameworkInstall(frameworkPaths)
35
36
  const config = loadResetConfig(appPaths)
36
37
  assertAppProject(appPaths, config)
38
+ const packageManager = resolveAppPackageManager(appPaths, config)
37
39
  const outputPaths = resolveAppOutputPaths(appPaths, config)
38
40
  const configPath = resolveConfigPath(appPaths)
39
41
  const frontendDir = resolveFrontendDir(appPaths, config)
@@ -49,6 +51,7 @@ export async function run(context) {
49
51
  printKeyValueTable([
50
52
  ["Config", configPath],
51
53
  ["Frontend", frontendDir],
54
+ ["Package manager", packageManager],
52
55
  ["Output", outputPaths.appBundlePath]
53
56
  ])
54
57
  console.log("")
@@ -82,7 +85,7 @@ export async function run(context) {
82
85
  const progress = createProgress(steps.length, "Build")
83
86
 
84
87
  if (!skipFrontend) {
85
- await runCommand(resolveNpmCommand(), ["run", "build"], {
88
+ await runCommand(resolvePackageManagerCommand(packageManager), ["run", "build"], {
86
89
  cwd: frontendDir,
87
90
  dryRun
88
91
  })
@@ -1,7 +1,7 @@
1
1
  import { buildFrameworkRuntime } from "../lib/framework.js"
2
2
  import {
3
3
  registerChildCleanup,
4
- resolveNpmCommand,
4
+ resolvePackageManagerCommand,
5
5
  spawnCommand,
6
6
  waitForProcessExit,
7
7
  waitForUrl
@@ -11,6 +11,7 @@ import {
11
11
  assertFrameworkInstall,
12
12
  loadResetConfig,
13
13
  resolveAppPaths,
14
+ resolveAppPackageManager,
14
15
  resolveConfigPath,
15
16
  resolveDevServerOptions,
16
17
  resolveFrontendDir,
@@ -39,6 +40,7 @@ export async function run(context) {
39
40
  assertFrameworkInstall(frameworkPaths)
40
41
  const config = loadResetConfig(appPaths)
41
42
  assertAppProject(appPaths, config)
43
+ const packageManager = resolveAppPackageManager(appPaths, config)
42
44
  const devServer = resolveDevServerOptions(config)
43
45
  const configPath = resolveConfigPath(appPaths)
44
46
  const frontendDir = resolveFrontendDir(appPaths, config)
@@ -54,6 +56,7 @@ export async function run(context) {
54
56
  printKeyValueTable([
55
57
  ["Config", configPath],
56
58
  ["Frontend", frontendDir],
59
+ ["Package manager", packageManager],
57
60
  ["Dev URL", config.frontend.devUrl]
58
61
  ])
59
62
  console.log("")
@@ -97,7 +100,7 @@ export async function run(context) {
97
100
  const children = []
98
101
 
99
102
  if (!skipFrontend) {
100
- const frontendProcess = spawnCommand(resolveNpmCommand(), [
103
+ const frontendProcess = spawnCommand(resolvePackageManagerCommand(packageManager), [
101
104
  "run",
102
105
  "dev",
103
106
  "--",
@@ -3,12 +3,14 @@ import { cp, mkdir, readFile, readdir, writeFile } from "node:fs/promises"
3
3
  import path from "node:path"
4
4
 
5
5
  import {
6
+ captureCommandOutput,
6
7
  getInstallCommandArgs,
7
8
  resolvePackageManagerCommand,
8
9
  runCommand
9
10
  } from "../lib/process.js"
10
11
  import {
11
12
  makeAppMetadata,
13
+ resolveCliDependencySpec,
12
14
  resolveFrameworkPaths,
13
15
  resolveSdkDependencySpec
14
16
  } from "../lib/project.js"
@@ -172,6 +174,84 @@ function App() {
172
174
  export default App
173
175
  `
174
176
 
177
+ const rootFrontendRunner = `import { readFileSync } from 'node:fs'
178
+ import path from 'node:path'
179
+ import { spawn } from 'node:child_process'
180
+
181
+ const script = process.argv[2]
182
+ if (!script) {
183
+ throw new Error('Missing frontend script name.')
184
+ }
185
+
186
+ const appRoot = process.cwd()
187
+ const config = JSON.parse(readFileSync(path.join(appRoot, 'reset.config.json'), 'utf8'))
188
+ const frontendDir =
189
+ typeof config?.project?.frontendDir === 'string' && config.project.frontendDir.trim() !== ''
190
+ ? path.resolve(appRoot, config.project.frontendDir)
191
+ : path.join(appRoot, 'frontend')
192
+
193
+ function resolvePackageManagerCommand() {
194
+ const packageJsonPath = path.join(appRoot, 'package.json')
195
+ const manifest = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
196
+ const packageManager = typeof manifest.packageManager === 'string' ? manifest.packageManager.trim() : ''
197
+ const name = packageManager.split('@')[0]
198
+
199
+ if (name === 'bun') {
200
+ return process.platform === 'win32' ? 'bun.exe' : 'bun'
201
+ }
202
+
203
+ if (name === 'pnpm') {
204
+ return process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
205
+ }
206
+
207
+ if (name === 'yarn') {
208
+ return process.platform === 'win32' ? 'yarn.cmd' : 'yarn'
209
+ }
210
+
211
+ if (name === 'npm') {
212
+ return process.platform === 'win32' ? 'npm.cmd' : 'npm'
213
+ }
214
+
215
+ const candidates = [
216
+ ['bun.lock', process.platform === 'win32' ? 'bun.exe' : 'bun'],
217
+ ['bun.lockb', process.platform === 'win32' ? 'bun.exe' : 'bun'],
218
+ ['pnpm-lock.yaml', process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'],
219
+ ['yarn.lock', process.platform === 'win32' ? 'yarn.cmd' : 'yarn'],
220
+ ['package-lock.json', process.platform === 'win32' ? 'npm.cmd' : 'npm']
221
+ ]
222
+
223
+ for (const [fileName, command] of candidates) {
224
+ try {
225
+ readFileSync(path.join(appRoot, fileName), 'utf8')
226
+ return command
227
+ } catch {}
228
+ }
229
+
230
+ return process.platform === 'win32' ? 'npm.cmd' : 'npm'
231
+ }
232
+
233
+ const command = resolvePackageManagerCommand()
234
+ const child = spawn(command, ['run', script, '--', ...process.argv.slice(3)], {
235
+ cwd: frontendDir,
236
+ stdio: 'inherit',
237
+ env: process.env
238
+ })
239
+
240
+ child.once('error', (error) => {
241
+ console.error(error)
242
+ process.exit(1)
243
+ })
244
+
245
+ child.once('exit', (code, signal) => {
246
+ if (signal) {
247
+ process.kill(process.pid, signal)
248
+ return
249
+ }
250
+
251
+ process.exit(code ?? 1)
252
+ })
253
+ `
254
+
175
255
  export const description = "Scaffold a starter app with an interactive project setup"
176
256
 
177
257
  function listTemplates(templatesDir) {
@@ -183,15 +263,84 @@ function listTemplates(templatesDir) {
183
263
 
184
264
  function getRootGitignore(frontendDir) {
185
265
  if (frontendDir === ".") {
186
- return `.reset
187
- node_modules
188
- dist
266
+ return `.DS_Store
267
+ Thumbs.db
268
+ .idea/
269
+ .vs/
270
+ .vscode/
271
+ .cache/
272
+ .reset/
273
+ .turbo/
274
+ build/
275
+ coverage/
276
+ dist/
277
+ node_modules/
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/
189
303
  `
190
304
  }
191
305
 
192
- return `.reset
193
- ${frontendDir}/node_modules
194
- ${frontendDir}/dist
306
+ return `.DS_Store
307
+ Thumbs.db
308
+ .idea/
309
+ .vs/
310
+ .vscode/
311
+ .cache/
312
+ .reset/
313
+ .turbo/
314
+ build/
315
+ coverage/
316
+ node_modules/
317
+ ${frontendDir}/node_modules/
318
+ ${frontendDir}/dist/
319
+ npm-debug.log*
320
+ yarn-debug.log*
321
+ yarn-error.log*
322
+ .pnpm-debug.log*
323
+ *.log
324
+ *.zip
325
+ *.tgz
326
+ *.tsbuildinfo
327
+ *.dSYM/
328
+ .env
329
+ .env.*
330
+ .env.local
331
+ .env.*.local
332
+ .eslintcache
333
+ bun.lock
334
+ bun.lockb
335
+ compile_commands.json
336
+ CMakeUserPresets.json
337
+ CMakeCache.txt
338
+ CMakeFiles/
339
+ cmake_install.cmake
340
+ install_manifest.txt
341
+ cmake-build-*/
342
+ Testing/
343
+ scripts/local/
195
344
  `
196
345
  }
197
346
 
@@ -310,6 +459,91 @@ async function applyFrontendOverrides(frontendDir, options) {
310
459
  await writeFile(path.join(frontendDir, "vite.config.ts"), standaloneViteConfig, "utf8")
311
460
  }
312
461
 
462
+ function createRootPackageJson(options) {
463
+ const packageManagerVersion = options.packageManagerVersion
464
+ ? `${options.packageManager}@${options.packageManagerVersion}`
465
+ : undefined
466
+
467
+ const base = {
468
+ name: options.appName,
469
+ private: true,
470
+ version: "0.1.0",
471
+ description: `${options.productName} desktop app`,
472
+ scripts: {
473
+ dev: "reset-framework-cli dev",
474
+ build: "reset-framework-cli build",
475
+ package: "reset-framework-cli package",
476
+ doctor: "reset-framework-cli doctor"
477
+ },
478
+ devDependencies: {
479
+ "reset-framework-cli": options.cliDependencySpec
480
+ }
481
+ }
482
+
483
+ if (packageManagerVersion) {
484
+ base.packageManager = packageManagerVersion
485
+ }
486
+
487
+ if (options.frontendDir !== ".") {
488
+ base.workspaces = [options.frontendDir]
489
+ base.scripts["dev:web"] = "node ./scripts/run-frontend.mjs dev"
490
+ base.scripts["build:web"] = "node ./scripts/run-frontend.mjs build"
491
+ base.scripts["lint:web"] = "node ./scripts/run-frontend.mjs lint"
492
+ base.scripts["preview:web"] = "node ./scripts/run-frontend.mjs preview"
493
+ }
494
+
495
+ return base
496
+ }
497
+
498
+ function createRootReadme(options) {
499
+ const run = `${options.packageManager} run`
500
+ const webPath = options.frontendDir === "." ? "project root" : `${options.frontendDir}/`
501
+
502
+ return `# ${options.productName}
503
+
504
+ Generated with \`reset-framework-cli\`.
505
+
506
+ ## Commands
507
+
508
+ - \`${run} dev\`: start the frontend dev server and native desktop runtime
509
+ - \`${run} build\`: build the frontend and stage the desktop app bundle
510
+ - \`${run} package\`: archive the built desktop app
511
+ - \`${run} doctor\`: inspect the project and framework installation
512
+
513
+ ## Web-only commands
514
+
515
+ ${options.frontendDir === "." ? `- \`${run} dev:web\`: run the Vite frontend only
516
+ - \`${run} build:web\`: build the frontend only` : `- \`${run} dev:web\`: run the frontend workspace only
517
+ - \`${run} build:web\`: build the frontend workspace only`}
518
+
519
+ ## Project files
520
+
521
+ - \`reset.config.json\`: desktop app metadata and runtime configuration
522
+ - \`${webPath}\`: web frontend source
523
+ - \`.reset/\`: generated build output and native runtime cache
524
+ `
525
+ }
526
+
527
+ async function writeRootPackageFiles(options) {
528
+ await writeFile(
529
+ path.join(options.targetDir, "README.md"),
530
+ createRootReadme(options),
531
+ "utf8"
532
+ )
533
+
534
+ if (options.frontendDir !== ".") {
535
+ await writeFile(
536
+ path.join(options.targetDir, "package.json"),
537
+ JSON.stringify(createRootPackageJson(options), null, 2) + "\n",
538
+ "utf8"
539
+ )
540
+
541
+ const scriptsDir = path.join(options.targetDir, "scripts")
542
+ await mkdir(scriptsDir, { recursive: true })
543
+ await writeFile(path.join(scriptsDir, "run-frontend.mjs"), rootFrontendRunner, "utf8")
544
+ }
545
+ }
546
+
313
547
  async function writeProjectFiles(options) {
314
548
  const templateConfig = JSON.parse(await readFile(options.templateConfigPath, "utf8"))
315
549
 
@@ -343,6 +577,20 @@ async function writeProjectFiles(options) {
343
577
  )
344
578
  }
345
579
 
580
+ async function resolvePackageManagerVersion(packageManager) {
581
+ if (packageManager !== "npm" && packageManager !== "pnpm" && packageManager !== "yarn" && packageManager !== "bun") {
582
+ return null
583
+ }
584
+
585
+ try {
586
+ const command = resolvePackageManagerCommand(packageManager)
587
+ const result = await captureCommandOutput(command, ["--version"])
588
+ return typeof result === "string" && result.trim() !== "" ? result.trim() : null
589
+ } catch {
590
+ return null
591
+ }
592
+ }
593
+
346
594
  async function collectCreateAppOptions(context, templates) {
347
595
  const initialTarget = context.args[0] ?? "my-app"
348
596
  const defaultTargetDir = path.resolve(context.cwd, initialTarget)
@@ -377,8 +625,10 @@ async function collectCreateAppOptions(context, templates) {
377
625
  templateDir: path.join(frameworkPaths.templatesDir, defaults.templateName),
378
626
  templateConfigPath: path.join(frameworkPaths.templatesDir, defaults.templateName, "reset.config.json"),
379
627
  sourceFrontendDir: path.join(frameworkPaths.templatesDir, defaults.templateName, "frontend"),
628
+ cliDependencySpec: resolveCliDependencySpec(frameworkPaths),
380
629
  sdkDependencySpec: resolveSdkDependencySpec(frameworkPaths),
381
630
  packageManager: resolvePackageManagerOption(context),
631
+ packageManagerVersion: await resolvePackageManagerVersion(resolvePackageManagerOption(context)),
382
632
  installDependencies: !context.flags["no-install"],
383
633
  force: Boolean(context.flags.force)
384
634
  }
@@ -464,6 +714,33 @@ async function collectCreateAppOptions(context, templates) {
464
714
  defaultValue: true
465
715
  })
466
716
 
717
+ const packageManager = await promptSelect(rl, {
718
+ label: "Package manager",
719
+ defaultValue: resolvePackageManagerOption(context),
720
+ choices: [
721
+ {
722
+ value: "npm",
723
+ label: "npm",
724
+ description: "Wide compatibility and predictable installs."
725
+ },
726
+ {
727
+ value: "bun",
728
+ label: "bun",
729
+ description: "Fast installs and lets bun run the root desktop scripts."
730
+ },
731
+ {
732
+ value: "pnpm",
733
+ label: "pnpm",
734
+ description: "Strict dependency graph with workspace support."
735
+ },
736
+ {
737
+ value: "yarn",
738
+ label: "yarn",
739
+ description: "Classic workspace-based JavaScript package workflow."
740
+ }
741
+ ]
742
+ })
743
+
467
744
  printSection("Summary")
468
745
  printKeyValueTable([
469
746
  ["Directory", targetDir],
@@ -472,6 +749,7 @@ async function collectCreateAppOptions(context, templates) {
472
749
  ["App ID", appId],
473
750
  ["Frontend", frontendDir === "." ? "project root" : `${frontendDir}/`],
474
751
  ["Styling", styling],
752
+ ["Package manager", packageManager],
475
753
  ["Install", installDependencies ? "yes" : "no"]
476
754
  ])
477
755
  console.log("")
@@ -494,7 +772,7 @@ async function collectCreateAppOptions(context, templates) {
494
772
  frontendDir,
495
773
  styling,
496
774
  installDependencies,
497
- packageManager: resolvePackageManagerOption(context)
775
+ packageManager
498
776
  }
499
777
  })
500
778
 
@@ -503,7 +781,9 @@ async function collectCreateAppOptions(context, templates) {
503
781
  templateDir: path.join(frameworkPaths.templatesDir, selected.templateName),
504
782
  templateConfigPath: path.join(frameworkPaths.templatesDir, selected.templateName, "reset.config.json"),
505
783
  sourceFrontendDir: path.join(frameworkPaths.templatesDir, selected.templateName, "frontend"),
784
+ cliDependencySpec: resolveCliDependencySpec(frameworkPaths),
506
785
  sdkDependencySpec: resolveSdkDependencySpec(frameworkPaths),
786
+ packageManagerVersion: await resolvePackageManagerVersion(selected.packageManager),
507
787
  force: Boolean(context.flags.force)
508
788
  }
509
789
  }
@@ -542,6 +822,7 @@ export async function run(context) {
542
822
  ["App ID", options.appId],
543
823
  ["Frontend", options.frontendDir === "." ? "project root" : `${options.frontendDir}/`],
544
824
  ["Styling", options.styling],
825
+ ["Package manager", options.packageManager],
545
826
  ["Install", options.installDependencies ? describeInstallCommand(options.packageManager) : "skipped"]
546
827
  ])
547
828
 
@@ -553,10 +834,16 @@ export async function run(context) {
553
834
  ["plan", "Write README", path.join(options.targetDir, "README.md")],
554
835
  ["plan", "Write config", path.join(options.targetDir, "reset.config.json")],
555
836
  ["plan", "Write ignore file", path.join(options.targetDir, ".gitignore")],
837
+ ["plan", "Write app package", path.join(options.targetDir, "package.json")],
556
838
  ["plan", "Write SDK bridge", path.join(targetFrontendDir, "src", "lib", "reset.ts")],
557
839
  ["plan", "Write Vite config", path.join(targetFrontendDir, "vite.config.ts")],
558
840
  ["plan", "Write TS config", path.join(targetFrontendDir, "tsconfig.app.json")]
559
841
  ])
842
+ if (options.frontendDir !== ".") {
843
+ printStatusTable([
844
+ ["plan", "Write frontend runner", path.join(options.targetDir, "scripts", "run-frontend.mjs")]
845
+ ])
846
+ }
560
847
  if (options.styling === "tailwindcss") {
561
848
  printStatusTable([
562
849
  ["plan", "Patch package.json", path.join(targetFrontendDir, "package.json")],
@@ -566,14 +853,14 @@ export async function run(context) {
566
853
  }
567
854
  if (options.installDependencies) {
568
855
  printStatusTable([
569
- ["plan", "Install dependencies", `${describeInstallCommand(options.packageManager)} in ${targetFrontendDir}`]
856
+ ["plan", "Install dependencies", `${describeInstallCommand(options.packageManager)} in ${options.targetDir}`]
570
857
  ])
571
858
  }
572
859
  return
573
860
  }
574
861
 
575
862
  console.log("")
576
- const progress = createProgress(options.installDependencies ? 7 : 6, "Scaffold")
863
+ const progress = createProgress(options.installDependencies ? 6 : 5, "Scaffold")
577
864
 
578
865
  await mkdir(options.targetDir, { recursive: true })
579
866
  progress.tick("Prepared project directory")
@@ -581,20 +868,44 @@ export async function run(context) {
581
868
  await copyFrontendTemplate(options.sourceFrontendDir, targetFrontendDir, options)
582
869
  progress.tick("Copied frontend starter")
583
870
 
584
- await cp(path.join(options.templateDir, "README.md"), path.join(options.targetDir, "README.md"), {
585
- force: options.force
586
- })
587
- progress.tick("Wrote project README")
588
-
589
871
  await writeProjectFiles(options)
590
- progress.tick("Wrote reset.config.json and root files")
872
+ await writeRootPackageFiles(options)
873
+ progress.tick("Wrote project files")
591
874
 
592
875
  await applyFrontendOverrides(targetFrontendDir, options)
593
876
  progress.tick("Applied frontend package wiring")
594
877
 
595
878
  const packageJsonPath = path.join(targetFrontendDir, "package.json")
596
879
  const frontendPackage = JSON.parse(await readFile(packageJsonPath, "utf8"))
597
- frontendPackage.name = options.appName
880
+
881
+ if (options.frontendDir === ".") {
882
+ const currentScripts = { ...(frontendPackage.scripts ?? {}) }
883
+ frontendPackage.name = options.appName
884
+ frontendPackage.private = true
885
+ frontendPackage.version = "0.1.0"
886
+ frontendPackage.description = `${options.productName} desktop app`
887
+ frontendPackage.devDependencies = {
888
+ ...frontendPackage.devDependencies,
889
+ "reset-framework-cli": options.cliDependencySpec
890
+ }
891
+ frontendPackage.scripts = {
892
+ dev: "reset-framework-cli dev",
893
+ "dev:web": currentScripts.dev ?? "vite",
894
+ build: "reset-framework-cli build",
895
+ "build:web": currentScripts.build ?? "tsc -b && vite build",
896
+ package: "reset-framework-cli package",
897
+ doctor: "reset-framework-cli doctor",
898
+ lint: currentScripts.lint ?? "eslint .",
899
+ preview: currentScripts.preview ?? "vite preview"
900
+ }
901
+
902
+ if (options.packageManagerVersion) {
903
+ frontendPackage.packageManager = `${options.packageManager}@${options.packageManagerVersion}`
904
+ }
905
+ } else {
906
+ frontendPackage.name = `${options.appName}-frontend`
907
+ }
908
+
598
909
  await writeFile(packageJsonPath, JSON.stringify(frontendPackage, null, 2) + "\n", "utf8")
599
910
  progress.tick("Finalized starter metadata")
600
911
 
@@ -603,7 +914,7 @@ export async function run(context) {
603
914
  resolvePackageManagerCommand(options.packageManager),
604
915
  getInstallCommandArgs(options.packageManager),
605
916
  {
606
- cwd: targetFrontendDir
917
+ cwd: options.targetDir
607
918
  }
608
919
  )
609
920
  progress.tick("Installed frontend dependencies")
@@ -613,6 +924,6 @@ export async function run(context) {
613
924
  printSection("Result")
614
925
  printStatusTable([
615
926
  ["done", "Project", options.targetDir],
616
- ["done", "Next step", `cd ${formatTargetForShell(options.targetDir)} && reset-framework-cli dev`]
927
+ ["done", "Next step", `cd ${formatTargetForShell(options.targetDir)} && ${options.packageManager} run dev`]
617
928
  ])
618
929
  }
@@ -82,6 +82,47 @@ export async function runCommand(command, args = [], options = {}) {
82
82
  })
83
83
  }
84
84
 
85
+ export async function captureCommandOutput(command, args = [], options = {}) {
86
+ const { cwd, env } = options
87
+
88
+ await Promise.resolve()
89
+
90
+ return await new Promise((resolve, reject) => {
91
+ const child = spawn(command, args, {
92
+ cwd,
93
+ env: env ? { ...process.env, ...env } : process.env,
94
+ stdio: ["ignore", "pipe", "pipe"]
95
+ })
96
+
97
+ let stdout = ""
98
+ let stderr = ""
99
+
100
+ child.stdout?.on("data", (chunk) => {
101
+ stdout += String(chunk)
102
+ })
103
+
104
+ child.stderr?.on("data", (chunk) => {
105
+ stderr += String(chunk)
106
+ })
107
+
108
+ child.once("error", reject)
109
+ child.once("exit", (code, signal) => {
110
+ if (code === 0) {
111
+ resolve(stdout.trim())
112
+ return
113
+ }
114
+
115
+ reject(
116
+ new Error(
117
+ signal
118
+ ? `Command terminated by signal: ${signal}`
119
+ : (stderr.trim() || stdout.trim() || `Command exited with code ${code}`)
120
+ )
121
+ )
122
+ })
123
+ })
124
+ }
125
+
85
126
  export function spawnCommand(command, args = [], options = {}) {
86
127
  const { cwd, dryRun = false, env } = options
87
128
 
@@ -88,6 +88,10 @@ function readJsonFile(filePath) {
88
88
  return JSON.parse(readFileSync(filePath, "utf8"))
89
89
  }
90
90
 
91
+ function fileExists(filePath) {
92
+ return existsSync(filePath)
93
+ }
94
+
91
95
  function isPathInside(parentDir, candidatePath) {
92
96
  const relative = path.relative(parentDir, candidatePath)
93
97
  return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative)
@@ -142,6 +146,8 @@ export function resolveFrameworkPaths() {
142
146
  const moduleDir = path.dirname(fileURLToPath(import.meta.url))
143
147
  const cliDir = path.resolve(moduleDir, "../..")
144
148
  const packagesDir = path.resolve(cliDir, "..")
149
+ const cliPackageJsonPath = path.join(cliDir, "package.json")
150
+ const cliManifest = readJsonFile(cliPackageJsonPath)
145
151
  const nativePackage = resolveWorkspacePackageInfo(
146
152
  "@reset-framework/native",
147
153
  path.join(packagesDir, "native"),
@@ -163,6 +169,13 @@ export function resolveFrameworkPaths() {
163
169
 
164
170
  return {
165
171
  cliDir,
172
+ cliPackage: {
173
+ packageName: cliManifest.name,
174
+ packageRoot: cliDir,
175
+ packageJsonPath: cliPackageJsonPath,
176
+ version: cliManifest.version ?? "0.0.0",
177
+ localFallback: true
178
+ },
166
179
  packagesDir,
167
180
  frameworkRoot: nativePackage.packageRoot,
168
181
  frameworkPackage: nativePackage,
@@ -207,6 +220,7 @@ export function resolveFrameworkBuildPaths(appPaths) {
207
220
  export function resolveAppPaths(appRoot) {
208
221
  return {
209
222
  appRoot,
223
+ appPackageJsonPath: path.join(appRoot, "package.json"),
210
224
  frontendDir: path.join(appRoot, "frontend"),
211
225
  resetConfigPath: path.join(appRoot, "reset.config.json"),
212
226
  legacyResetConfigPath: path.join(appRoot, "frontend", "reset.config.json")
@@ -388,3 +402,67 @@ export function resolveSdkDependencySpec(frameworkPaths) {
388
402
 
389
403
  return `^${frameworkPaths.sdkPackage.version}`
390
404
  }
405
+
406
+ export function resolveCliDependencySpec(frameworkPaths) {
407
+ if (frameworkPaths.isWorkspaceLayout || frameworkPaths.cliPackage.localFallback) {
408
+ return `file:${frameworkPaths.cliPackage.packageRoot}`
409
+ }
410
+
411
+ return `^${frameworkPaths.cliPackage.version}`
412
+ }
413
+
414
+ function detectPackageManagerFromManifest(packageJsonPath) {
415
+ if (!fileExists(packageJsonPath)) {
416
+ return null
417
+ }
418
+
419
+ const manifest = readJsonFile(packageJsonPath)
420
+ const raw = typeof manifest.packageManager === "string" ? manifest.packageManager.trim() : ""
421
+ if (raw === "") {
422
+ return null
423
+ }
424
+
425
+ const [name] = raw.split("@")
426
+ if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") {
427
+ return name
428
+ }
429
+
430
+ return null
431
+ }
432
+
433
+ function detectPackageManagerFromLocks(directory) {
434
+ if (!fileExists(directory)) {
435
+ return null
436
+ }
437
+
438
+ if (fileExists(path.join(directory, "bun.lock")) || fileExists(path.join(directory, "bun.lockb"))) {
439
+ return "bun"
440
+ }
441
+
442
+ if (fileExists(path.join(directory, "pnpm-lock.yaml"))) {
443
+ return "pnpm"
444
+ }
445
+
446
+ if (fileExists(path.join(directory, "yarn.lock"))) {
447
+ return "yarn"
448
+ }
449
+
450
+ if (fileExists(path.join(directory, "package-lock.json"))) {
451
+ return "npm"
452
+ }
453
+
454
+ return null
455
+ }
456
+
457
+ export function resolveAppPackageManager(appPaths, config) {
458
+ const frontendDir = resolveFrontendDir(appPaths, config)
459
+ const frontendPackageJsonPath = path.join(frontendDir, "package.json")
460
+
461
+ return (
462
+ detectPackageManagerFromManifest(appPaths.appPackageJsonPath) ??
463
+ detectPackageManagerFromManifest(frontendPackageJsonPath) ??
464
+ detectPackageManagerFromLocks(appPaths.appRoot) ??
465
+ detectPackageManagerFromLocks(frontendDir) ??
466
+ "npm"
467
+ )
468
+ }