reset-framework-cli 1.1.5 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +57 -47
  3. package/package.json +8 -4
  4. package/src/commands/build.js +125 -113
  5. package/src/commands/dev.js +156 -144
  6. package/src/commands/doctor.js +137 -89
  7. package/src/commands/init.js +822 -822
  8. package/src/commands/package.js +41 -41
  9. package/src/index.js +215 -213
  10. package/src/lib/context.js +66 -66
  11. package/src/lib/framework.js +126 -39
  12. package/src/lib/logger.js +11 -11
  13. package/src/lib/output.js +147 -147
  14. package/src/lib/process.js +148 -148
  15. package/src/lib/project.js +759 -468
  16. package/src/lib/toolchain.js +62 -62
  17. package/src/lib/ui.js +244 -244
  18. package/templates/basic/README.md +15 -15
  19. package/templates/basic/frontend/README.md +73 -73
  20. package/templates/basic/frontend/eslint.config.js +23 -23
  21. package/templates/basic/frontend/index.html +13 -13
  22. package/templates/basic/frontend/package.json +31 -31
  23. package/templates/basic/frontend/public/icons.svg +24 -24
  24. package/templates/basic/frontend/src/App.css +216 -216
  25. package/templates/basic/frontend/src/App.tsx +77 -77
  26. package/templates/basic/frontend/src/assets/vite.svg +1 -1
  27. package/templates/basic/frontend/src/index.css +111 -111
  28. package/templates/basic/frontend/src/lib/reset.ts +1 -1
  29. package/templates/basic/frontend/src/main.tsx +10 -10
  30. package/templates/basic/frontend/tsconfig.app.json +28 -28
  31. package/templates/basic/frontend/tsconfig.json +7 -7
  32. package/templates/basic/frontend/tsconfig.node.json +26 -26
  33. package/templates/basic/frontend/vite.config.ts +16 -16
  34. package/templates/basic/reset.config.json +58 -58
@@ -1,112 +1,139 @@
1
- import { existsSync, readFileSync } from "node:fs"
2
- import { createRequire } from "node:module"
3
- import path from "node:path"
4
- import { fileURLToPath } from "node:url"
5
-
6
- const require = createRequire(import.meta.url)
7
-
8
- function requireString(object, key, scope) {
9
- if (typeof object?.[key] !== "string" || object[key].trim() === "") {
10
- throw new Error(`Missing or invalid string field '${scope}.${key}'`)
11
- }
12
-
13
- return object[key]
14
- }
15
-
16
- function optionalString(object, key, fallback) {
17
- if (object == null || !(key in object)) {
18
- return fallback
19
- }
20
-
21
- if (typeof object[key] !== "string" || object[key].trim() === "") {
22
- throw new Error(`Missing or invalid string field '${key}'`)
23
- }
24
-
25
- return object[key]
26
- }
27
-
28
- function optionalObject(object, key) {
29
- if (object == null || !(key in object)) {
30
- return {}
31
- }
32
-
33
- if (typeof object[key] !== "object" || object[key] === null || Array.isArray(object[key])) {
34
- throw new Error(`Missing or invalid object field '${key}'`)
35
- }
36
-
37
- return object[key]
38
- }
39
-
40
- function sanitizeName(value) {
41
- return value
42
- .toLowerCase()
43
- .replace(/[^a-z0-9]+/g, "-")
44
- .replace(/^-+|-+$/g, "") || "reset-app"
45
- }
46
-
47
- function normalizeFrontendDir(value) {
48
- const trimmed = value.trim()
49
-
50
- if (trimmed === "." || trimmed === "./") {
51
- return "."
52
- }
53
-
54
- if (path.isAbsolute(trimmed)) {
55
- throw new Error("project.frontendDir must be a relative path")
56
- }
57
-
58
- const normalized = path.normalize(trimmed).replace(/\\/g, "/").replace(/\/+$/g, "")
59
-
60
- if (normalized === "" || normalized === ".") {
61
- return "."
62
- }
63
-
64
- if (normalized.startsWith("../")) {
65
- throw new Error("project.frontendDir cannot point outside the app root")
66
- }
67
-
68
- return normalized
69
- }
70
-
71
- function normalizeStyling(value) {
72
- if (value === "css" || value === "tailwindcss") {
73
- return value
74
- }
75
-
76
- throw new Error("project.styling must be either 'css' or 'tailwindcss'")
77
- }
78
-
79
- function normalizeProtocolScheme(value) {
80
- if (typeof value !== "string" || value.trim() === "") {
81
- throw new Error("protocols.schemes[].scheme must be a non-empty string")
82
- }
83
-
84
- const normalized = value.trim().toLowerCase()
85
- if (!/^[a-z][a-z0-9+.-]*$/.test(normalized)) {
86
- throw new Error(
87
- "protocols.schemes[].scheme must start with a letter and only contain letters, numbers, '+', '-', and '.'"
88
- )
89
- }
90
-
91
- return normalized
92
- }
93
-
94
- function normalizeProtocolRole(value) {
95
- if (value === "Editor" || value === "Viewer" || value === "Shell" || value === "None") {
96
- return value
97
- }
98
-
99
- throw new Error("protocols.schemes[].role must be one of Editor, Viewer, Shell, None")
100
- }
101
-
102
- function toTitleCase(value) {
103
- return value
104
- .split(/[^a-zA-Z0-9]+/)
105
- .filter(Boolean)
106
- .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
107
- .join(" ") || "Reset App"
108
- }
109
-
1
+ import { existsSync, readFileSync } from "node:fs"
2
+ import { createRequire } from "node:module"
3
+ import path from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ const require = createRequire(import.meta.url)
7
+
8
+ const runtimePackageDescriptors = [
9
+ {
10
+ packageName: "@reset-framework/runtime-darwin-arm64",
11
+ workspaceDirName: "runtime-darwin-arm64",
12
+ platform: "darwin",
13
+ arch: "arm64",
14
+ artifactRootSegments: ["dist", "darwin-arm64", "reset-framework.app"],
15
+ binarySegments: ["dist", "darwin-arm64", "reset-framework.app", "Contents", "MacOS", "reset-framework"]
16
+ },
17
+ {
18
+ packageName: "@reset-framework/runtime-darwin-x64",
19
+ workspaceDirName: "runtime-darwin-x64",
20
+ platform: "darwin",
21
+ arch: "x64",
22
+ artifactRootSegments: ["dist", "darwin-x64", "reset-framework.app"],
23
+ binarySegments: ["dist", "darwin-x64", "reset-framework.app", "Contents", "MacOS", "reset-framework"]
24
+ },
25
+ {
26
+ packageName: "@reset-framework/runtime-win32-x64",
27
+ workspaceDirName: "runtime-win32-x64",
28
+ platform: "win32",
29
+ arch: "x64",
30
+ artifactRootSegments: ["dist", "win32-x64"],
31
+ binarySegments: ["dist", "win32-x64", "reset-framework.exe"]
32
+ }
33
+ ]
34
+
35
+ function requireString(object, key, scope) {
36
+ if (typeof object?.[key] !== "string" || object[key].trim() === "") {
37
+ throw new Error(`Missing or invalid string field '${scope}.${key}'`)
38
+ }
39
+
40
+ return object[key]
41
+ }
42
+
43
+ function optionalString(object, key, fallback) {
44
+ if (object == null || !(key in object)) {
45
+ return fallback
46
+ }
47
+
48
+ if (typeof object[key] !== "string" || object[key].trim() === "") {
49
+ throw new Error(`Missing or invalid string field '${key}'`)
50
+ }
51
+
52
+ return object[key]
53
+ }
54
+
55
+ function optionalObject(object, key) {
56
+ if (object == null || !(key in object)) {
57
+ return {}
58
+ }
59
+
60
+ if (typeof object[key] !== "object" || object[key] === null || Array.isArray(object[key])) {
61
+ throw new Error(`Missing or invalid object field '${key}'`)
62
+ }
63
+
64
+ return object[key]
65
+ }
66
+
67
+ function sanitizeName(value) {
68
+ return value
69
+ .toLowerCase()
70
+ .replace(/[^a-z0-9]+/g, "-")
71
+ .replace(/^-+|-+$/g, "") || "reset-app"
72
+ }
73
+
74
+ function normalizeFrontendDir(value) {
75
+ const trimmed = value.trim()
76
+
77
+ if (trimmed === "." || trimmed === "./") {
78
+ return "."
79
+ }
80
+
81
+ if (path.isAbsolute(trimmed)) {
82
+ throw new Error("project.frontendDir must be a relative path")
83
+ }
84
+
85
+ const normalized = path.normalize(trimmed).replace(/\\/g, "/").replace(/\/+$/g, "")
86
+
87
+ if (normalized === "" || normalized === ".") {
88
+ return "."
89
+ }
90
+
91
+ if (normalized.startsWith("../")) {
92
+ throw new Error("project.frontendDir cannot point outside the app root")
93
+ }
94
+
95
+ return normalized
96
+ }
97
+
98
+ function normalizeStyling(value) {
99
+ if (value === "css" || value === "tailwindcss") {
100
+ return value
101
+ }
102
+
103
+ throw new Error("project.styling must be either 'css' or 'tailwindcss'")
104
+ }
105
+
106
+ function normalizeProtocolScheme(value) {
107
+ if (typeof value !== "string" || value.trim() === "") {
108
+ throw new Error("protocols.schemes[].scheme must be a non-empty string")
109
+ }
110
+
111
+ const normalized = value.trim().toLowerCase()
112
+ if (!/^[a-z][a-z0-9+.-]*$/.test(normalized)) {
113
+ throw new Error(
114
+ "protocols.schemes[].scheme must start with a letter and only contain letters, numbers, '+', '-', and '.'"
115
+ )
116
+ }
117
+
118
+ return normalized
119
+ }
120
+
121
+ function normalizeProtocolRole(value) {
122
+ if (value === "Editor" || value === "Viewer" || value === "Shell" || value === "None") {
123
+ return value
124
+ }
125
+
126
+ throw new Error("protocols.schemes[].role must be one of Editor, Viewer, Shell, None")
127
+ }
128
+
129
+ function toTitleCase(value) {
130
+ return value
131
+ .split(/[^a-zA-Z0-9]+/)
132
+ .filter(Boolean)
133
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
134
+ .join(" ") || "Reset App"
135
+ }
136
+
110
137
  function readJsonFile(filePath) {
111
138
  return JSON.parse(readFileSync(filePath, "utf8"))
112
139
  }
@@ -122,105 +149,219 @@ function getFrameworkBuildType(mode) {
122
149
  function resolveExistingCandidate(candidates) {
123
150
  return candidates.find((candidate) => existsSync(candidate)) ?? null
124
151
  }
125
-
126
- function isPathInside(parentDir, candidatePath) {
127
- const relative = path.relative(parentDir, candidatePath)
128
- return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative)
129
- }
130
-
131
- function resolvePackageInfo(packageName, fallbackRoot) {
132
- try {
133
- const packageJsonPath = require.resolve(`${packageName}/package.json`)
134
- const packageRoot = path.dirname(packageJsonPath)
135
- const manifest = readJsonFile(packageJsonPath)
136
-
137
- return {
138
- packageName: manifest.name ?? packageName,
139
- packageRoot,
140
- packageJsonPath,
141
- version: manifest.version ?? "0.0.0",
142
- localFallback: false
143
- }
144
- } catch {
145
- const packageJsonPath = path.join(fallbackRoot, "package.json")
146
- const manifest = readJsonFile(packageJsonPath)
147
-
148
- return {
149
- packageName: manifest.name ?? packageName,
150
- packageRoot: fallbackRoot,
151
- packageJsonPath,
152
- version: manifest.version ?? "0.0.0",
153
- localFallback: true
154
- }
155
- }
156
- }
157
-
158
- function resolveWorkspacePackageInfo(packageName, workspaceRoot, fallbackInfo) {
159
- const packageJsonPath = path.join(workspaceRoot, "package.json")
160
-
161
- if (!existsSync(packageJsonPath)) {
162
- return fallbackInfo
163
- }
164
-
165
- const manifest = readJsonFile(packageJsonPath)
166
-
167
- return {
168
- packageName: manifest.name ?? packageName,
169
- packageRoot: workspaceRoot,
170
- packageJsonPath,
171
- version: manifest.version ?? fallbackInfo.version,
172
- localFallback: true
173
- }
174
- }
175
-
176
- export function resolveFrameworkPaths() {
177
- const moduleDir = path.dirname(fileURLToPath(import.meta.url))
178
- const cliDir = path.resolve(moduleDir, "../..")
179
- const packagesDir = path.resolve(cliDir, "..")
180
- const cliPackageJsonPath = path.join(cliDir, "package.json")
181
- const cliManifest = readJsonFile(cliPackageJsonPath)
182
- const nativePackage = resolveWorkspacePackageInfo(
183
- "@reset-framework/native",
184
- path.join(packagesDir, "native"),
185
- resolvePackageInfo("@reset-framework/native", path.join(packagesDir, "native"))
186
- )
187
- const sdkPackage = resolveWorkspacePackageInfo(
188
- "@reset-framework/sdk",
189
- path.join(packagesDir, "sdk"),
190
- resolvePackageInfo("@reset-framework/sdk", path.join(packagesDir, "sdk"))
191
- )
192
- const schemaPackage = resolveWorkspacePackageInfo(
193
- "@reset-framework/schema",
194
- path.join(packagesDir, "schema"),
195
- resolvePackageInfo("@reset-framework/schema", path.join(packagesDir, "schema"))
196
- )
197
- const isWorkspaceLayout = [nativePackage, sdkPackage, schemaPackage].every((pkg) =>
198
- isPathInside(packagesDir, pkg.packageRoot)
199
- )
200
-
201
- return {
202
- cliDir,
203
- cliPackage: {
204
- packageName: cliManifest.name,
205
- packageRoot: cliDir,
206
- packageJsonPath: cliPackageJsonPath,
207
- version: cliManifest.version ?? "0.0.0",
208
- localFallback: true
209
- },
210
- packagesDir,
211
- frameworkRoot: nativePackage.packageRoot,
212
- frameworkPackage: nativePackage,
213
- isWorkspaceLayout,
214
- sdkPackage,
215
- schemaPackage,
216
- runtimeDir: path.join(nativePackage.packageRoot, "runtime"),
217
- rootCMakePath: path.join(nativePackage.packageRoot, "CMakeLists.txt"),
218
- cmakePresetsPath: path.join(nativePackage.packageRoot, "CMakePresets.json"),
219
- vcpkgManifestPath: path.join(nativePackage.packageRoot, "vcpkg.json"),
220
- templatesDir: path.join(cliDir, "templates"),
221
- }
222
- }
223
-
152
+
153
+ function isPathInside(parentDir, candidatePath) {
154
+ const relative = path.relative(parentDir, candidatePath)
155
+ return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative)
156
+ }
157
+
158
+ function resolvePackageInfo(packageName, fallbackRoot) {
159
+ try {
160
+ const packageJsonPath = require.resolve(`${packageName}/package.json`)
161
+ const packageRoot = path.dirname(packageJsonPath)
162
+ const manifest = readJsonFile(packageJsonPath)
163
+
164
+ return {
165
+ packageName: manifest.name ?? packageName,
166
+ packageRoot,
167
+ packageJsonPath,
168
+ version: manifest.version ?? "0.0.0",
169
+ localFallback: false
170
+ }
171
+ } catch {
172
+ const packageJsonPath = path.join(fallbackRoot, "package.json")
173
+ const manifest = readJsonFile(packageJsonPath)
174
+
175
+ return {
176
+ packageName: manifest.name ?? packageName,
177
+ packageRoot: fallbackRoot,
178
+ packageJsonPath,
179
+ version: manifest.version ?? "0.0.0",
180
+ localFallback: true
181
+ }
182
+ }
183
+ }
184
+
185
+ function resolveWorkspacePackageInfo(packageName, workspaceRoot, fallbackInfo) {
186
+ const packageJsonPath = path.join(workspaceRoot, "package.json")
187
+
188
+ if (!existsSync(packageJsonPath)) {
189
+ return fallbackInfo
190
+ }
191
+
192
+ const manifest = readJsonFile(packageJsonPath)
193
+
194
+ return {
195
+ packageName: manifest.name ?? packageName,
196
+ packageRoot: workspaceRoot,
197
+ packageJsonPath,
198
+ version: manifest.version ?? fallbackInfo.version,
199
+ localFallback: true
200
+ }
201
+ }
202
+
203
+ function resolveOptionalPackageInfo(packageName, fallbackRoot) {
204
+ try {
205
+ const packageJsonPath = require.resolve(`${packageName}/package.json`)
206
+ const packageRoot = path.dirname(packageJsonPath)
207
+ const manifest = readJsonFile(packageJsonPath)
208
+
209
+ return {
210
+ packageName: manifest.name ?? packageName,
211
+ packageRoot,
212
+ packageJsonPath,
213
+ version: manifest.version ?? "0.0.0",
214
+ localFallback: false
215
+ }
216
+ } catch {
217
+ const packageJsonPath = path.join(fallbackRoot, "package.json")
218
+
219
+ if (!existsSync(packageJsonPath)) {
220
+ return null
221
+ }
222
+
223
+ const manifest = readJsonFile(packageJsonPath)
224
+
225
+ return {
226
+ packageName: manifest.name ?? packageName,
227
+ packageRoot: fallbackRoot,
228
+ packageJsonPath,
229
+ version: manifest.version ?? "0.0.0",
230
+ localFallback: true
231
+ }
232
+ }
233
+ }
234
+
235
+ function resolveOptionalWorkspacePackageInfo(packageName, workspaceRoot, fallbackInfo) {
236
+ const packageJsonPath = path.join(workspaceRoot, "package.json")
237
+
238
+ if (!existsSync(packageJsonPath)) {
239
+ return fallbackInfo
240
+ }
241
+
242
+ const manifest = readJsonFile(packageJsonPath)
243
+ const version = fallbackInfo?.version ?? manifest.version ?? "0.0.0"
244
+
245
+ return {
246
+ packageName: manifest.name ?? packageName,
247
+ packageRoot: workspaceRoot,
248
+ packageJsonPath,
249
+ version,
250
+ localFallback: true
251
+ }
252
+ }
253
+
254
+ function resolveCurrentRuntimeDescriptor() {
255
+ return runtimePackageDescriptors.find(
256
+ (descriptor) =>
257
+ descriptor.platform === process.platform &&
258
+ descriptor.arch === process.arch
259
+ ) ?? null
260
+ }
261
+
262
+ function resolveRuntimePackage(packagesDir) {
263
+ const descriptor = resolveCurrentRuntimeDescriptor()
264
+
265
+ if (!descriptor) {
266
+ return null
267
+ }
268
+
269
+ const workspaceRoot = path.join(packagesDir, descriptor.workspaceDirName)
270
+ const packageInfo = resolveOptionalWorkspacePackageInfo(
271
+ descriptor.packageName,
272
+ workspaceRoot,
273
+ resolveOptionalPackageInfo(descriptor.packageName, workspaceRoot)
274
+ )
275
+
276
+ return {
277
+ descriptor,
278
+ packageInfo,
279
+ artifactRoot: packageInfo ? path.join(packageInfo.packageRoot, ...descriptor.artifactRootSegments) : null,
280
+ binaryPath: packageInfo ? path.join(packageInfo.packageRoot, ...descriptor.binarySegments) : null
281
+ }
282
+ }
283
+
284
+ function hasSourceRuntimeLayout(frameworkPaths) {
285
+ return Boolean(
286
+ frameworkPaths.frameworkPackage &&
287
+ frameworkPaths.frameworkRoot &&
288
+ frameworkPaths.runtimeDir &&
289
+ frameworkPaths.rootCMakePath &&
290
+ frameworkPaths.vcpkgManifestPath &&
291
+ existsSync(frameworkPaths.frameworkPackage.packageJsonPath) &&
292
+ existsSync(frameworkPaths.frameworkRoot) &&
293
+ existsSync(frameworkPaths.runtimeDir) &&
294
+ existsSync(frameworkPaths.rootCMakePath) &&
295
+ existsSync(frameworkPaths.vcpkgManifestPath)
296
+ )
297
+ }
298
+
299
+ function isSourceRuntimeRequested(options = {}) {
300
+ if (options.preferSource === true) {
301
+ return true
302
+ }
303
+
304
+ const raw = process.env.RESET_FRAMEWORK_RUNTIME_SOURCE
305
+ if (typeof raw !== "string") {
306
+ return false
307
+ }
308
+
309
+ return ["1", "true", "yes", "on", "source"].includes(raw.trim().toLowerCase())
310
+ }
311
+
312
+ export function resolveFrameworkPaths() {
313
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url))
314
+ const cliDir = path.resolve(moduleDir, "../..")
315
+ const packagesDir = path.resolve(cliDir, "..")
316
+ const isWorkspacePackagesDir = path.basename(packagesDir) === "packages"
317
+ const cliPackageJsonPath = path.join(cliDir, "package.json")
318
+ const cliManifest = readJsonFile(cliPackageJsonPath)
319
+ const nativePackage = resolveOptionalWorkspacePackageInfo(
320
+ "@reset-framework/native",
321
+ path.join(packagesDir, "native"),
322
+ resolveOptionalPackageInfo("@reset-framework/native", path.join(packagesDir, "native"))
323
+ )
324
+ const sdkPackage = resolveWorkspacePackageInfo(
325
+ "@reset-framework/sdk",
326
+ path.join(packagesDir, "sdk"),
327
+ resolvePackageInfo("@reset-framework/sdk", path.join(packagesDir, "sdk"))
328
+ )
329
+ const schemaPackage = resolveWorkspacePackageInfo(
330
+ "@reset-framework/schema",
331
+ path.join(packagesDir, "schema"),
332
+ resolvePackageInfo("@reset-framework/schema", path.join(packagesDir, "schema"))
333
+ )
334
+ const runtimePackage = resolveRuntimePackage(packagesDir)
335
+ const isWorkspaceLayout =
336
+ isWorkspacePackagesDir &&
337
+ [cliDir, sdkPackage.packageRoot, schemaPackage.packageRoot].every((packageRoot) =>
338
+ isPathInside(packagesDir, packageRoot)
339
+ )
340
+
341
+ return {
342
+ cliDir,
343
+ cliPackage: {
344
+ packageName: cliManifest.name,
345
+ packageRoot: cliDir,
346
+ packageJsonPath: cliPackageJsonPath,
347
+ version: cliManifest.version ?? "0.0.0",
348
+ localFallback: isWorkspaceLayout
349
+ },
350
+ packagesDir,
351
+ frameworkRoot: nativePackage?.packageRoot ?? null,
352
+ frameworkPackage: nativePackage,
353
+ isWorkspaceLayout,
354
+ sdkPackage,
355
+ schemaPackage,
356
+ runtimePackage,
357
+ runtimeDir: nativePackage ? path.join(nativePackage.packageRoot, "runtime") : null,
358
+ rootCMakePath: nativePackage ? path.join(nativePackage.packageRoot, "CMakeLists.txt") : null,
359
+ cmakePresetsPath: nativePackage ? path.join(nativePackage.packageRoot, "CMakePresets.json") : null,
360
+ vcpkgManifestPath: nativePackage ? path.join(nativePackage.packageRoot, "vcpkg.json") : null,
361
+ templatesDir: path.join(cliDir, "templates")
362
+ }
363
+ }
364
+
224
365
  export function resolveFrameworkBuildPaths(appPaths) {
225
366
  const frameworkCacheRoot = path.join(appPaths.appRoot, ".reset", "framework")
226
367
  const isWindows = process.platform === "win32"
@@ -275,8 +416,83 @@ export function resolveFrameworkBuildPaths(appPaths) {
275
416
  }
276
417
  }
277
418
 
278
- export function resolveFrameworkRuntimeBinary(frameworkBuildPaths, mode, options = {}) {
419
+ export function resolveFrameworkRuntimeStrategy(frameworkPaths, options = {}) {
420
+ const sourceRequested = isSourceRuntimeRequested(options)
421
+ const runtimePackage = frameworkPaths.runtimePackage
422
+ const hasPrebuiltRuntime =
423
+ Boolean(runtimePackage?.packageInfo) &&
424
+ Boolean(runtimePackage?.artifactRoot) &&
425
+ Boolean(runtimePackage?.binaryPath) &&
426
+ existsSync(runtimePackage.artifactRoot) &&
427
+ existsSync(runtimePackage.binaryPath)
428
+ const hasSourceRuntime = hasSourceRuntimeLayout(frameworkPaths)
429
+
430
+ if (sourceRequested) {
431
+ if (!hasSourceRuntime) {
432
+ throw new Error(
433
+ "Source runtime was requested, but @reset-framework/native is not installed in this CLI environment."
434
+ )
435
+ }
436
+
437
+ return {
438
+ kind: "source",
439
+ packageInfo: frameworkPaths.frameworkPackage,
440
+ label: `${frameworkPaths.frameworkPackage.packageName}@${frameworkPaths.frameworkPackage.version}`
441
+ }
442
+ }
443
+
444
+ if (hasPrebuiltRuntime) {
445
+ return {
446
+ kind: "prebuilt",
447
+ packageInfo: runtimePackage.packageInfo,
448
+ descriptor: runtimePackage.descriptor,
449
+ artifactRoot: runtimePackage.artifactRoot,
450
+ binaryPath: runtimePackage.binaryPath,
451
+ label: `${runtimePackage.packageInfo.packageName}@${runtimePackage.packageInfo.version}`
452
+ }
453
+ }
454
+
455
+ if (hasSourceRuntime) {
456
+ return {
457
+ kind: "source",
458
+ packageInfo: frameworkPaths.frameworkPackage,
459
+ label: `${frameworkPaths.frameworkPackage.packageName}@${frameworkPaths.frameworkPackage.version}`
460
+ }
461
+ }
462
+
463
+ if (runtimePackage?.packageInfo) {
464
+ throw new Error(
465
+ `Installed runtime package ${runtimePackage.packageInfo.packageName} is missing bundled runtime artifacts under ${runtimePackage.artifactRoot}.`
466
+ )
467
+ }
468
+
469
+ if (runtimePackage?.descriptor) {
470
+ throw new Error(
471
+ `No bundled runtime package is installed for ${process.platform}-${process.arch}. Expected ${runtimePackage.descriptor.packageName}.`
472
+ )
473
+ }
474
+
475
+ throw new Error(
476
+ `Reset Framework does not currently provide a bundled runtime for ${process.platform}-${process.arch}.`
477
+ )
478
+ }
479
+
480
+ export function resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPaths, mode, options = {}) {
279
481
  const { mustExist = false } = options
482
+ const strategy = options.strategy ?? resolveFrameworkRuntimeStrategy(frameworkPaths, options)
483
+
484
+ if (strategy.kind === "prebuilt") {
485
+ if (existsSync(strategy.binaryPath)) {
486
+ return strategy.binaryPath
487
+ }
488
+
489
+ if (mustExist) {
490
+ throw new Error(`Missing bundled runtime executable at ${strategy.binaryPath}.`)
491
+ }
492
+
493
+ return strategy.binaryPath
494
+ }
495
+
280
496
  const candidates =
281
497
  mode === "release"
282
498
  ? (frameworkBuildPaths.releaseAppBinaryCandidates ?? [frameworkBuildPaths.releaseAppTemplate])
@@ -294,8 +510,22 @@ export function resolveFrameworkRuntimeBinary(frameworkBuildPaths, mode, options
294
510
  return candidates[0]
295
511
  }
296
512
 
297
- export function resolveFrameworkRuntimeOutputDir(frameworkBuildPaths, mode, options = {}) {
513
+ export function resolveFrameworkRuntimeOutputDir(frameworkPaths, frameworkBuildPaths, mode, options = {}) {
298
514
  const { mustExist = false } = options
515
+ const strategy = options.strategy ?? resolveFrameworkRuntimeStrategy(frameworkPaths, options)
516
+
517
+ if (strategy.kind === "prebuilt") {
518
+ if (existsSync(strategy.artifactRoot)) {
519
+ return strategy.artifactRoot
520
+ }
521
+
522
+ if (mustExist) {
523
+ throw new Error(`Missing bundled runtime output directory at ${strategy.artifactRoot}.`)
524
+ }
525
+
526
+ return strategy.artifactRoot
527
+ }
528
+
299
529
  const candidates =
300
530
  mode === "release"
301
531
  ? (frameworkBuildPaths.releaseRuntimeOutputCandidates ?? [])
@@ -314,163 +544,224 @@ export function resolveFrameworkRuntimeOutputDir(frameworkBuildPaths, mode, opti
314
544
  return candidates[0]
315
545
  }
316
546
 
317
- return path.dirname(resolveFrameworkRuntimeBinary(frameworkBuildPaths, mode, options))
318
- }
319
-
320
- export function resolveAppPaths(appRoot) {
321
- return {
322
- appRoot,
323
- appPackageJsonPath: path.join(appRoot, "package.json"),
324
- frontendDir: path.join(appRoot, "frontend"),
325
- resetConfigPath: path.join(appRoot, "reset.config.json"),
326
- legacyResetConfigPath: path.join(appRoot, "frontend", "reset.config.json")
327
- }
328
- }
329
-
330
- export function resolveConfigPath(appPaths) {
331
- if (existsSync(appPaths.resetConfigPath)) {
332
- return appPaths.resetConfigPath
333
- }
334
-
335
- if (existsSync(appPaths.legacyResetConfigPath)) {
336
- return appPaths.legacyResetConfigPath
337
- }
338
-
339
- return appPaths.resetConfigPath
340
- }
341
-
342
- export function getFrameworkChecks(frameworkPaths) {
343
- return [
344
- ["native", frameworkPaths.frameworkPackage.packageJsonPath],
345
- ["sdk", frameworkPaths.sdkPackage.packageJsonPath],
346
- ["schema", frameworkPaths.schemaPackage.packageJsonPath],
347
- ["cmake", frameworkPaths.rootCMakePath],
348
- ["runtime", frameworkPaths.runtimeDir],
349
- ["vcpkg", frameworkPaths.vcpkgManifestPath],
350
- ["templates", frameworkPaths.templatesDir]
351
- ]
352
- }
353
-
354
- export function getAppChecks(appPaths) {
355
- return [["config", resolveConfigPath(appPaths)]]
356
- }
357
-
358
- export function assertFrameworkInstall(frameworkPaths) {
359
- const missing = getFrameworkChecks(frameworkPaths)
360
- .filter(([, filePath]) => !existsSync(filePath))
361
- .map(([label]) => label)
362
-
363
- if (missing.length > 0) {
364
- throw new Error(
365
- `Reset CLI installation is incomplete. Missing: ${missing.join(", ")}`
366
- )
367
- }
368
- }
369
-
370
- export function assertAppProject(appPaths, config) {
371
- const checks = [...getAppChecks(appPaths)]
372
-
373
- if (config) {
374
- checks.unshift(["frontend", resolveFrontendDir(appPaths, config)])
375
- }
376
-
377
- const missing = checks
378
- .filter(([, filePath]) => !existsSync(filePath))
379
- .map(([label]) => label)
380
-
381
- if (missing.length > 0) {
382
- throw new Error(
383
- `Current directory does not look like a Reset app. Missing: ${missing.join(", ")}`
384
- )
385
- }
386
- }
387
-
388
- export function validateResetConfig(rawConfig) {
389
- const frontend = optionalObject(rawConfig, "frontend")
390
- const build = optionalObject(rawConfig, "build")
391
- const project = optionalObject(rawConfig, "project")
392
- const security = optionalObject(rawConfig, "security")
393
- const protocols = optionalObject(rawConfig, "protocols")
394
- const windowConfig = optionalObject(rawConfig, "window")
395
- const rawProtocolSchemes = Array.isArray(protocols.schemes) ? protocols.schemes : []
396
- const seenProtocolSchemes = new Set()
397
-
398
- return {
399
- ...rawConfig,
400
- name: sanitizeName(requireString(rawConfig, "name", "config")),
401
- productName: requireString(rawConfig, "productName", "config"),
402
- appId: requireString(rawConfig, "appId", "config"),
403
- version: requireString(rawConfig, "version", "config"),
404
- window: {
405
- title: optionalString(windowConfig, "title", "Reset App")
406
- },
407
- frontend: {
408
- devUrl: requireString(frontend, "devUrl", "frontend"),
409
- distDir: optionalString(frontend, "distDir", "dist"),
410
- entryHtml: optionalString(frontend, "entryHtml", "index.html")
411
- },
412
- project: {
413
- frontendDir: normalizeFrontendDir(optionalString(project, "frontendDir", "frontend")),
414
- styling: normalizeStyling(optionalString(project, "styling", "css"))
415
- },
416
- build: {
417
- outputDir: optionalString(build, "outputDir", ".reset/build")
418
- },
419
- security: {
420
- permissions: Array.isArray(security.permissions)
421
- ? security.permissions.map((value) => {
422
- if (typeof value !== "string" || value.trim() === "") {
423
- throw new Error("Missing or invalid string field 'security.permissions'")
424
- }
425
-
426
- return value
427
- })
428
- : []
429
- },
430
- protocols: {
431
- schemes: rawProtocolSchemes.map((entry) => {
432
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
433
- throw new Error("protocols.schemes entries must be objects")
434
- }
435
-
436
- const scheme = normalizeProtocolScheme(entry.scheme)
437
- if (seenProtocolSchemes.has(scheme)) {
438
- throw new Error(`Duplicate protocol scheme '${scheme}'`)
439
- }
440
-
441
- seenProtocolSchemes.add(scheme)
442
-
443
- return {
444
- scheme,
445
- name:
446
- typeof entry.name === "string" && entry.name.trim() !== ""
447
- ? entry.name
448
- : scheme,
449
- role:
450
- entry.role === undefined
451
- ? "Viewer"
452
- : normalizeProtocolRole(entry.role)
453
- }
454
- })
455
- }
456
- }
457
- }
458
-
459
- export function loadResetConfig(appPaths) {
460
- const configPath = resolveConfigPath(appPaths)
461
- const raw = readFileSync(configPath, "utf8")
462
-
463
- try {
464
- return validateResetConfig(JSON.parse(raw))
465
- } catch (error) {
466
- if (error instanceof SyntaxError) {
467
- throw new Error(`Invalid JSON in ${configPath}: ${error.message}`)
468
- }
469
-
470
- throw error
471
- }
472
- }
473
-
547
+ return path.dirname(resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPaths, mode, options))
548
+ }
549
+
550
+ export function resolveAppPaths(appRoot) {
551
+ return {
552
+ appRoot,
553
+ appPackageJsonPath: path.join(appRoot, "package.json"),
554
+ frontendDir: path.join(appRoot, "frontend"),
555
+ resetConfigPath: path.join(appRoot, "reset.config.json"),
556
+ legacyResetConfigPath: path.join(appRoot, "frontend", "reset.config.json")
557
+ }
558
+ }
559
+
560
+ export function resolveConfigPath(appPaths) {
561
+ if (existsSync(appPaths.resetConfigPath)) {
562
+ return appPaths.resetConfigPath
563
+ }
564
+
565
+ if (existsSync(appPaths.legacyResetConfigPath)) {
566
+ return appPaths.legacyResetConfigPath
567
+ }
568
+
569
+ return appPaths.resetConfigPath
570
+ }
571
+
572
+ export function getFrameworkChecks(frameworkPaths) {
573
+ return [
574
+ ["sdk", frameworkPaths.sdkPackage.packageJsonPath],
575
+ ["schema", frameworkPaths.schemaPackage.packageJsonPath],
576
+ ["templates", frameworkPaths.templatesDir]
577
+ ]
578
+ }
579
+
580
+ export function getFrameworkRuntimeChecks(frameworkPaths) {
581
+ const checks = []
582
+
583
+ if (frameworkPaths.runtimePackage?.packageInfo) {
584
+ checks.push(["runtime package", frameworkPaths.runtimePackage.packageInfo.packageJsonPath])
585
+ if (frameworkPaths.runtimePackage.artifactRoot) {
586
+ checks.push(["runtime artifact", frameworkPaths.runtimePackage.artifactRoot])
587
+ }
588
+ }
589
+
590
+ if (frameworkPaths.frameworkPackage) {
591
+ checks.push(["native source", frameworkPaths.frameworkPackage.packageJsonPath])
592
+ }
593
+
594
+ if (frameworkPaths.rootCMakePath) {
595
+ checks.push(["cmake", frameworkPaths.rootCMakePath])
596
+ }
597
+
598
+ if (frameworkPaths.runtimeDir) {
599
+ checks.push(["runtime source", frameworkPaths.runtimeDir])
600
+ }
601
+
602
+ if (frameworkPaths.vcpkgManifestPath) {
603
+ checks.push(["vcpkg", frameworkPaths.vcpkgManifestPath])
604
+ }
605
+
606
+ return checks
607
+ }
608
+
609
+ export function getFrameworkRuntimeModeSummary(frameworkPaths, options = {}) {
610
+ const strategy = resolveFrameworkRuntimeStrategy(frameworkPaths, options)
611
+
612
+ return {
613
+ kind: strategy.kind,
614
+ label: strategy.label
615
+ }
616
+ }
617
+
618
+ export function hasFrameworkSourcePackage(frameworkPaths) {
619
+ return hasSourceRuntimeLayout(frameworkPaths)
620
+ }
621
+
622
+ export function isFrameworkSourceRequested(options = {}) {
623
+ return isSourceRuntimeRequested(options)
624
+ }
625
+
626
+ export function assertFrameworkInstall(frameworkPaths) {
627
+ const missing = getFrameworkChecks(frameworkPaths)
628
+ .filter(([, filePath]) => !existsSync(filePath))
629
+ .map(([label]) => label)
630
+
631
+ if (missing.length > 0) {
632
+ throw new Error(
633
+ `Reset CLI installation is incomplete. Missing: ${missing.join(", ")}`
634
+ )
635
+ }
636
+ }
637
+
638
+ export function assertFrameworkSourceInstall(frameworkPaths) {
639
+ const missing = [
640
+ ["native source", frameworkPaths.frameworkPackage?.packageJsonPath],
641
+ ["sdk", frameworkPaths.sdkPackage.packageJsonPath],
642
+ ["cmake", frameworkPaths.rootCMakePath],
643
+ ["runtime", frameworkPaths.runtimeDir],
644
+ ["vcpkg", frameworkPaths.vcpkgManifestPath],
645
+ ["templates", frameworkPaths.templatesDir]
646
+ ]
647
+ .filter(([, filePath]) => !filePath || !existsSync(filePath))
648
+ .map(([label]) => label)
649
+
650
+ if (missing.length > 0) {
651
+ throw new Error(
652
+ `Source runtime is unavailable in this CLI installation. Missing: ${missing.join(", ")}`
653
+ )
654
+ }
655
+ }
656
+
657
+ export function getAppChecks(appPaths) {
658
+ return [["config", resolveConfigPath(appPaths)]]
659
+ }
660
+
661
+ export function assertAppProject(appPaths, config) {
662
+ const checks = [...getAppChecks(appPaths)]
663
+
664
+ if (config) {
665
+ checks.unshift(["frontend", resolveFrontendDir(appPaths, config)])
666
+ }
667
+
668
+ const missing = checks
669
+ .filter(([, filePath]) => !existsSync(filePath))
670
+ .map(([label]) => label)
671
+
672
+ if (missing.length > 0) {
673
+ throw new Error(
674
+ `Current directory does not look like a Reset app. Missing: ${missing.join(", ")}`
675
+ )
676
+ }
677
+ }
678
+
679
+ export function validateResetConfig(rawConfig) {
680
+ const frontend = optionalObject(rawConfig, "frontend")
681
+ const build = optionalObject(rawConfig, "build")
682
+ const project = optionalObject(rawConfig, "project")
683
+ const security = optionalObject(rawConfig, "security")
684
+ const protocols = optionalObject(rawConfig, "protocols")
685
+ const windowConfig = optionalObject(rawConfig, "window")
686
+ const rawProtocolSchemes = Array.isArray(protocols.schemes) ? protocols.schemes : []
687
+ const seenProtocolSchemes = new Set()
688
+
689
+ return {
690
+ ...rawConfig,
691
+ name: sanitizeName(requireString(rawConfig, "name", "config")),
692
+ productName: requireString(rawConfig, "productName", "config"),
693
+ appId: requireString(rawConfig, "appId", "config"),
694
+ version: requireString(rawConfig, "version", "config"),
695
+ window: {
696
+ title: optionalString(windowConfig, "title", "Reset App")
697
+ },
698
+ frontend: {
699
+ devUrl: requireString(frontend, "devUrl", "frontend"),
700
+ distDir: optionalString(frontend, "distDir", "dist"),
701
+ entryHtml: optionalString(frontend, "entryHtml", "index.html")
702
+ },
703
+ project: {
704
+ frontendDir: normalizeFrontendDir(optionalString(project, "frontendDir", "frontend")),
705
+ styling: normalizeStyling(optionalString(project, "styling", "css"))
706
+ },
707
+ build: {
708
+ outputDir: optionalString(build, "outputDir", ".reset/build")
709
+ },
710
+ security: {
711
+ permissions: Array.isArray(security.permissions)
712
+ ? security.permissions.map((value) => {
713
+ if (typeof value !== "string" || value.trim() === "") {
714
+ throw new Error("Missing or invalid string field 'security.permissions'")
715
+ }
716
+
717
+ return value
718
+ })
719
+ : []
720
+ },
721
+ protocols: {
722
+ schemes: rawProtocolSchemes.map((entry) => {
723
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
724
+ throw new Error("protocols.schemes entries must be objects")
725
+ }
726
+
727
+ const scheme = normalizeProtocolScheme(entry.scheme)
728
+ if (seenProtocolSchemes.has(scheme)) {
729
+ throw new Error(`Duplicate protocol scheme '${scheme}'`)
730
+ }
731
+
732
+ seenProtocolSchemes.add(scheme)
733
+
734
+ return {
735
+ scheme,
736
+ name:
737
+ typeof entry.name === "string" && entry.name.trim() !== ""
738
+ ? entry.name
739
+ : scheme,
740
+ role:
741
+ entry.role === undefined
742
+ ? "Viewer"
743
+ : normalizeProtocolRole(entry.role)
744
+ }
745
+ })
746
+ }
747
+ }
748
+ }
749
+
750
+ export function loadResetConfig(appPaths) {
751
+ const configPath = resolveConfigPath(appPaths)
752
+ const raw = readFileSync(configPath, "utf8")
753
+
754
+ try {
755
+ return validateResetConfig(JSON.parse(raw))
756
+ } catch (error) {
757
+ if (error instanceof SyntaxError) {
758
+ throw new Error(`Invalid JSON in ${configPath}: ${error.message}`)
759
+ }
760
+
761
+ throw error
762
+ }
763
+ }
764
+
474
765
  export function resolveAppOutputPaths(appPaths, config) {
475
766
  const outputRoot = path.resolve(appPaths.appRoot, config.build.outputDir)
476
767
  const frontendDir = resolveFrontendDir(appPaths, config)
@@ -496,109 +787,109 @@ export function resolveAppOutputPaths(appPaths, config) {
496
787
  bundledFrontendDir: path.join(resourcesDir, config.frontend.distDir),
497
788
  frontendDistDir: path.resolve(frontendDir, config.frontend.distDir),
498
789
  frontendEntryFile: path.resolve(
499
- frontendDir,
500
- config.frontend.distDir,
501
- config.frontend.entryHtml
790
+ frontendDir,
791
+ config.frontend.distDir,
792
+ config.frontend.entryHtml
502
793
  ),
503
794
  packagesDir,
504
795
  zipPath: path.join(packagesDir, `${config.name}-${isWindows ? "windows" : "macos"}.zip`)
505
796
  }
506
797
  }
507
-
508
- export function resolveFrontendDir(appPaths, config) {
509
- return path.resolve(appPaths.appRoot, config.project.frontendDir)
510
- }
511
-
512
- export function resolveDevServerOptions(config) {
513
- const url = new URL(config.frontend.devUrl)
514
-
515
- return {
516
- host: url.hostname,
517
- port: url.port || "5173",
518
- url: url.toString()
519
- }
520
- }
521
-
522
- export function makeAppMetadata(appName) {
523
- const name = sanitizeName(appName)
524
- const productName = toTitleCase(appName)
525
-
526
- return {
527
- name,
528
- productName,
529
- appId: `com.example.${name}`,
530
- title: productName
531
- }
532
- }
533
-
534
- export function resolveSdkDependencySpec(frameworkPaths) {
535
- if (frameworkPaths.isWorkspaceLayout || frameworkPaths.sdkPackage.localFallback) {
536
- return `file:${frameworkPaths.sdkPackage.packageRoot}`
537
- }
538
-
539
- return `^${frameworkPaths.sdkPackage.version}`
540
- }
541
-
542
- export function resolveCliDependencySpec(frameworkPaths) {
543
- if (frameworkPaths.isWorkspaceLayout || frameworkPaths.cliPackage.localFallback) {
544
- return `file:${frameworkPaths.cliPackage.packageRoot}`
545
- }
546
-
547
- return `^${frameworkPaths.cliPackage.version}`
548
- }
549
-
550
- function detectPackageManagerFromManifest(packageJsonPath) {
551
- if (!fileExists(packageJsonPath)) {
552
- return null
553
- }
554
-
555
- const manifest = readJsonFile(packageJsonPath)
556
- const raw = typeof manifest.packageManager === "string" ? manifest.packageManager.trim() : ""
557
- if (raw === "") {
558
- return null
559
- }
560
-
561
- const [name] = raw.split("@")
562
- if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") {
563
- return name
564
- }
565
-
566
- return null
567
- }
568
-
569
- function detectPackageManagerFromLocks(directory) {
570
- if (!fileExists(directory)) {
571
- return null
572
- }
573
-
574
- if (fileExists(path.join(directory, "bun.lock")) || fileExists(path.join(directory, "bun.lockb"))) {
575
- return "bun"
576
- }
577
-
578
- if (fileExists(path.join(directory, "pnpm-lock.yaml"))) {
579
- return "pnpm"
580
- }
581
-
582
- if (fileExists(path.join(directory, "yarn.lock"))) {
583
- return "yarn"
584
- }
585
-
586
- if (fileExists(path.join(directory, "package-lock.json"))) {
587
- return "npm"
588
- }
589
-
590
- return null
591
- }
592
-
593
- export function resolveAppPackageManager(appPaths, config) {
594
- const frontendDir = resolveFrontendDir(appPaths, config)
595
- const frontendPackageJsonPath = path.join(frontendDir, "package.json")
596
-
597
- return (
598
- detectPackageManagerFromManifest(appPaths.appPackageJsonPath) ??
599
- detectPackageManagerFromManifest(frontendPackageJsonPath) ??
600
- detectPackageManagerFromLocks(appPaths.appRoot) ??
601
- detectPackageManagerFromLocks(frontendDir) ??
602
- "npm"
603
- )
604
- }
798
+
799
+ export function resolveFrontendDir(appPaths, config) {
800
+ return path.resolve(appPaths.appRoot, config.project.frontendDir)
801
+ }
802
+
803
+ export function resolveDevServerOptions(config) {
804
+ const url = new URL(config.frontend.devUrl)
805
+
806
+ return {
807
+ host: url.hostname,
808
+ port: url.port || "5173",
809
+ url: url.toString()
810
+ }
811
+ }
812
+
813
+ export function makeAppMetadata(appName) {
814
+ const name = sanitizeName(appName)
815
+ const productName = toTitleCase(appName)
816
+
817
+ return {
818
+ name,
819
+ productName,
820
+ appId: `com.example.${name}`,
821
+ title: productName
822
+ }
823
+ }
824
+
825
+ export function resolveSdkDependencySpec(frameworkPaths) {
826
+ if (frameworkPaths.isWorkspaceLayout || frameworkPaths.sdkPackage.localFallback) {
827
+ return `file:${frameworkPaths.sdkPackage.packageRoot}`
828
+ }
829
+
830
+ return `^${frameworkPaths.sdkPackage.version}`
831
+ }
832
+
833
+ export function resolveCliDependencySpec(frameworkPaths) {
834
+ if (frameworkPaths.isWorkspaceLayout || frameworkPaths.cliPackage.localFallback) {
835
+ return `file:${frameworkPaths.cliPackage.packageRoot}`
836
+ }
837
+
838
+ return `^${frameworkPaths.cliPackage.version}`
839
+ }
840
+
841
+ function detectPackageManagerFromManifest(packageJsonPath) {
842
+ if (!fileExists(packageJsonPath)) {
843
+ return null
844
+ }
845
+
846
+ const manifest = readJsonFile(packageJsonPath)
847
+ const raw = typeof manifest.packageManager === "string" ? manifest.packageManager.trim() : ""
848
+ if (raw === "") {
849
+ return null
850
+ }
851
+
852
+ const [name] = raw.split("@")
853
+ if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") {
854
+ return name
855
+ }
856
+
857
+ return null
858
+ }
859
+
860
+ function detectPackageManagerFromLocks(directory) {
861
+ if (!fileExists(directory)) {
862
+ return null
863
+ }
864
+
865
+ if (fileExists(path.join(directory, "bun.lock")) || fileExists(path.join(directory, "bun.lockb"))) {
866
+ return "bun"
867
+ }
868
+
869
+ if (fileExists(path.join(directory, "pnpm-lock.yaml"))) {
870
+ return "pnpm"
871
+ }
872
+
873
+ if (fileExists(path.join(directory, "yarn.lock"))) {
874
+ return "yarn"
875
+ }
876
+
877
+ if (fileExists(path.join(directory, "package-lock.json"))) {
878
+ return "npm"
879
+ }
880
+
881
+ return null
882
+ }
883
+
884
+ export function resolveAppPackageManager(appPaths, config) {
885
+ const frontendDir = resolveFrontendDir(appPaths, config)
886
+ const frontendPackageJsonPath = path.join(frontendDir, "package.json")
887
+
888
+ return (
889
+ detectPackageManagerFromManifest(appPaths.appPackageJsonPath) ??
890
+ detectPackageManagerFromManifest(frontendPackageJsonPath) ??
891
+ detectPackageManagerFromLocks(appPaths.appRoot) ??
892
+ detectPackageManagerFromLocks(frontendDir) ??
893
+ "npm"
894
+ )
895
+ }