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