reset-framework-cli 1.1.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,34 @@ import { createRequire } from "node:module"
3
3
  import path from "node:path"
4
4
  import { fileURLToPath } from "node:url"
5
5
 
6
- const require = createRequire(import.meta.url)
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
+ ]
7
34
 
8
35
  function requireString(object, key, scope) {
9
36
  if (typeof object?.[key] !== "string" || object[key].trim() === "") {
@@ -107,29 +134,29 @@ function toTitleCase(value) {
107
134
  .join(" ") || "Reset App"
108
135
  }
109
136
 
110
- function readJsonFile(filePath) {
111
- return JSON.parse(readFileSync(filePath, "utf8"))
112
- }
113
-
114
- function fileExists(filePath) {
115
- return existsSync(filePath)
116
- }
117
-
118
- function getFrameworkBuildType(mode) {
119
- return mode === "release" ? "Release" : "Debug"
120
- }
121
-
122
- function resolveExistingCandidate(candidates) {
123
- return candidates.find((candidate) => existsSync(candidate)) ?? null
124
- }
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
+ }
125
152
 
126
153
  function isPathInside(parentDir, candidatePath) {
127
154
  const relative = path.relative(parentDir, candidatePath)
128
155
  return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative)
129
156
  }
130
157
 
131
- function resolvePackageInfo(packageName, fallbackRoot) {
132
- try {
158
+ function resolvePackageInfo(packageName, fallbackRoot) {
159
+ try {
133
160
  const packageJsonPath = require.resolve(`${packageName}/package.json`)
134
161
  const packageRoot = path.dirname(packageJsonPath)
135
162
  const manifest = readJsonFile(packageJsonPath)
@@ -152,10 +179,10 @@ function resolvePackageInfo(packageName, fallbackRoot) {
152
179
  version: manifest.version ?? "0.0.0",
153
180
  localFallback: true
154
181
  }
155
- }
156
- }
157
-
158
- function resolveWorkspacePackageInfo(packageName, workspaceRoot, fallbackInfo) {
182
+ }
183
+ }
184
+
185
+ function resolveWorkspacePackageInfo(packageName, workspaceRoot, fallbackInfo) {
159
186
  const packageJsonPath = path.join(workspaceRoot, "package.json")
160
187
 
161
188
  if (!existsSync(packageJsonPath)) {
@@ -164,157 +191,360 @@ function resolveWorkspacePackageInfo(packageName, workspaceRoot, fallbackInfo) {
164
191
 
165
192
  const manifest = readJsonFile(packageJsonPath)
166
193
 
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)
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"))
199
328
  )
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
- }
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
+ }
223
364
 
224
365
  export function resolveFrameworkBuildPaths(appPaths) {
225
- const frameworkCacheRoot = path.join(appPaths.appRoot, ".reset", "framework")
226
- const isWindows = process.platform === "win32"
227
- const devRuntimeRoot = path.join(frameworkCacheRoot, "build", "dev", "runtime")
228
- const releaseRuntimeRoot = path.join(frameworkCacheRoot, "build", "release", "runtime")
229
- const devAppBinaryCandidates = isWindows
230
- ? [
231
- path.join(devRuntimeRoot, getFrameworkBuildType("dev"), "reset-framework.exe"),
232
- path.join(devRuntimeRoot, "reset-framework.exe")
233
- ]
234
- : [
235
- path.join(
236
- frameworkCacheRoot,
237
- "build",
238
- "dev",
239
- "runtime",
240
- "reset-framework.app",
241
- "Contents",
242
- "MacOS",
243
- "reset-framework"
244
- )
245
- ]
246
- const releaseAppBinaryCandidates = isWindows
247
- ? [
248
- path.join(releaseRuntimeRoot, getFrameworkBuildType("release"), "reset-framework.exe"),
249
- path.join(releaseRuntimeRoot, "reset-framework.exe")
250
- ]
251
- : [
252
- path.join(
253
- frameworkCacheRoot,
254
- "build",
255
- "release",
256
- "runtime",
257
- "reset-framework.app"
258
- )
259
- ]
366
+ const frameworkCacheRoot = path.join(appPaths.appRoot, ".reset", "framework")
367
+ const isWindows = process.platform === "win32"
368
+ const devRuntimeRoot = path.join(frameworkCacheRoot, "build", "dev", "runtime")
369
+ const releaseRuntimeRoot = path.join(frameworkCacheRoot, "build", "release", "runtime")
370
+ const devAppBinaryCandidates = isWindows
371
+ ? [
372
+ path.join(devRuntimeRoot, getFrameworkBuildType("dev"), "reset-framework.exe"),
373
+ path.join(devRuntimeRoot, "reset-framework.exe")
374
+ ]
375
+ : [
376
+ path.join(
377
+ frameworkCacheRoot,
378
+ "build",
379
+ "dev",
380
+ "runtime",
381
+ "reset-framework.app",
382
+ "Contents",
383
+ "MacOS",
384
+ "reset-framework"
385
+ )
386
+ ]
387
+ const releaseAppBinaryCandidates = isWindows
388
+ ? [
389
+ path.join(releaseRuntimeRoot, getFrameworkBuildType("release"), "reset-framework.exe"),
390
+ path.join(releaseRuntimeRoot, "reset-framework.exe")
391
+ ]
392
+ : [
393
+ path.join(
394
+ frameworkCacheRoot,
395
+ "build",
396
+ "release",
397
+ "runtime",
398
+ "reset-framework.app"
399
+ )
400
+ ]
401
+
402
+ return {
403
+ frameworkCacheRoot,
404
+ devBuildDir: path.join(frameworkCacheRoot, "build", "dev"),
405
+ releaseBuildDir: path.join(frameworkCacheRoot, "build", "release"),
406
+ devRuntimeOutputCandidates: isWindows
407
+ ? [path.join(devRuntimeRoot, getFrameworkBuildType("dev")), devRuntimeRoot]
408
+ : [path.join(frameworkCacheRoot, "build", "dev", "runtime", "reset-framework.app")],
409
+ releaseRuntimeOutputCandidates: isWindows
410
+ ? [path.join(releaseRuntimeRoot, getFrameworkBuildType("release")), releaseRuntimeRoot]
411
+ : [path.join(frameworkCacheRoot, "build", "release", "runtime", "reset-framework.app")],
412
+ devAppBinaryCandidates,
413
+ releaseAppBinaryCandidates,
414
+ devAppBinary: devAppBinaryCandidates[0],
415
+ releaseAppTemplate: releaseAppBinaryCandidates[0]
416
+ }
417
+ }
260
418
 
261
- return {
262
- frameworkCacheRoot,
263
- devBuildDir: path.join(frameworkCacheRoot, "build", "dev"),
264
- releaseBuildDir: path.join(frameworkCacheRoot, "build", "release"),
265
- devRuntimeOutputCandidates: isWindows
266
- ? [path.join(devRuntimeRoot, getFrameworkBuildType("dev")), devRuntimeRoot]
267
- : [path.join(frameworkCacheRoot, "build", "dev", "runtime", "reset-framework.app")],
268
- releaseRuntimeOutputCandidates: isWindows
269
- ? [path.join(releaseRuntimeRoot, getFrameworkBuildType("release")), releaseRuntimeRoot]
270
- : [path.join(frameworkCacheRoot, "build", "release", "runtime", "reset-framework.app")],
271
- devAppBinaryCandidates,
272
- releaseAppBinaryCandidates,
273
- devAppBinary: devAppBinaryCandidates[0],
274
- releaseAppTemplate: releaseAppBinaryCandidates[0]
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
+ )
275
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
+ )
276
478
  }
277
479
 
278
- export function resolveFrameworkRuntimeBinary(frameworkBuildPaths, mode, options = {}) {
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])
283
499
  : (frameworkBuildPaths.devAppBinaryCandidates ?? [frameworkBuildPaths.devAppBinary])
284
- const existing = resolveExistingCandidate(candidates)
285
-
286
- if (existing) {
287
- return existing
288
- }
289
-
290
- if (mustExist) {
291
- throw new Error(`Missing built runtime executable. Looked in: ${candidates.join(", ")}`)
292
- }
500
+ const existing = resolveExistingCandidate(candidates)
501
+
502
+ if (existing) {
503
+ return existing
504
+ }
505
+
506
+ if (mustExist) {
507
+ throw new Error(`Missing built runtime executable. Looked in: ${candidates.join(", ")}`)
508
+ }
293
509
 
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
299
- const candidates =
300
- mode === "release"
301
- ? (frameworkBuildPaths.releaseRuntimeOutputCandidates ?? [])
302
- : (frameworkBuildPaths.devRuntimeOutputCandidates ?? [])
303
- const existing = resolveExistingCandidate(candidates)
515
+ const strategy = options.strategy ?? resolveFrameworkRuntimeStrategy(frameworkPaths, options)
304
516
 
305
- if (existing) {
306
- return existing
307
- }
517
+ if (strategy.kind === "prebuilt") {
518
+ if (existsSync(strategy.artifactRoot)) {
519
+ return strategy.artifactRoot
520
+ }
308
521
 
309
- if (candidates.length > 0) {
310
522
  if (mustExist) {
311
- throw new Error(`Missing built runtime output directory. Looked in: ${candidates.join(", ")}`)
523
+ throw new Error(`Missing bundled runtime output directory at ${strategy.artifactRoot}.`)
312
524
  }
313
525
 
314
- return candidates[0]
526
+ return strategy.artifactRoot
315
527
  }
316
528
 
317
- return path.dirname(resolveFrameworkRuntimeBinary(frameworkBuildPaths, mode, options))
529
+ const candidates =
530
+ mode === "release"
531
+ ? (frameworkBuildPaths.releaseRuntimeOutputCandidates ?? [])
532
+ : (frameworkBuildPaths.devRuntimeOutputCandidates ?? [])
533
+ const existing = resolveExistingCandidate(candidates)
534
+
535
+ if (existing) {
536
+ return existing
537
+ }
538
+
539
+ if (candidates.length > 0) {
540
+ if (mustExist) {
541
+ throw new Error(`Missing built runtime output directory. Looked in: ${candidates.join(", ")}`)
542
+ }
543
+
544
+ return candidates[0]
545
+ }
546
+
547
+ return path.dirname(resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPaths, mode, options))
318
548
  }
319
549
 
320
550
  export function resolveAppPaths(appRoot) {
@@ -339,33 +569,94 @@ export function resolveConfigPath(appPaths) {
339
569
  return appPaths.resetConfigPath
340
570
  }
341
571
 
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
- }
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
+ }
369
660
 
370
661
  export function assertAppProject(appPaths, config) {
371
662
  const checks = [...getAppChecks(appPaths)]
@@ -471,39 +762,39 @@ export function loadResetConfig(appPaths) {
471
762
  }
472
763
  }
473
764
 
474
- export function resolveAppOutputPaths(appPaths, config) {
475
- const outputRoot = path.resolve(appPaths.appRoot, config.build.outputDir)
476
- const frontendDir = resolveFrontendDir(appPaths, config)
477
- const isWindows = process.platform === "win32"
478
- const appBundleName = isWindows ? config.productName : `${config.productName}.app`
479
- const platformDir = path.join(outputRoot, isWindows ? "windows" : "macos")
480
- const appBundlePath = path.join(platformDir, appBundleName)
481
- const resourcesDir = isWindows
482
- ? path.join(appBundlePath, "resources")
483
- : path.join(appBundlePath, "Contents", "Resources")
484
- const packagesDir = path.join(outputRoot, "packages")
485
-
486
- return {
487
- outputRoot,
488
- macosDir: isWindows ? undefined : platformDir,
489
- windowsDir: isWindows ? platformDir : undefined,
490
- appBundlePath,
491
- appExecutablePath: isWindows
492
- ? path.join(appBundlePath, `${config.productName}.exe`)
493
- : undefined,
494
- resourcesDir,
495
- bundledConfigPath: path.join(resourcesDir, "reset.config.json"),
496
- bundledFrontendDir: path.join(resourcesDir, config.frontend.distDir),
497
- frontendDistDir: path.resolve(frontendDir, config.frontend.distDir),
498
- frontendEntryFile: path.resolve(
765
+ export function resolveAppOutputPaths(appPaths, config) {
766
+ const outputRoot = path.resolve(appPaths.appRoot, config.build.outputDir)
767
+ const frontendDir = resolveFrontendDir(appPaths, config)
768
+ const isWindows = process.platform === "win32"
769
+ const appBundleName = isWindows ? config.productName : `${config.productName}.app`
770
+ const platformDir = path.join(outputRoot, isWindows ? "windows" : "macos")
771
+ const appBundlePath = path.join(platformDir, appBundleName)
772
+ const resourcesDir = isWindows
773
+ ? path.join(appBundlePath, "resources")
774
+ : path.join(appBundlePath, "Contents", "Resources")
775
+ const packagesDir = path.join(outputRoot, "packages")
776
+
777
+ return {
778
+ outputRoot,
779
+ macosDir: isWindows ? undefined : platformDir,
780
+ windowsDir: isWindows ? platformDir : undefined,
781
+ appBundlePath,
782
+ appExecutablePath: isWindows
783
+ ? path.join(appBundlePath, `${config.productName}.exe`)
784
+ : undefined,
785
+ resourcesDir,
786
+ bundledConfigPath: path.join(resourcesDir, "reset.config.json"),
787
+ bundledFrontendDir: path.join(resourcesDir, config.frontend.distDir),
788
+ frontendDistDir: path.resolve(frontendDir, config.frontend.distDir),
789
+ frontendEntryFile: path.resolve(
499
790
  frontendDir,
500
791
  config.frontend.distDir,
501
792
  config.frontend.entryHtml
502
- ),
503
- packagesDir,
504
- zipPath: path.join(packagesDir, `${config.name}-${isWindows ? "windows" : "macos"}.zip`)
505
- }
506
- }
793
+ ),
794
+ packagesDir,
795
+ zipPath: path.join(packagesDir, `${config.name}-${isWindows ? "windows" : "macos"}.zip`)
796
+ }
797
+ }
507
798
 
508
799
  export function resolveFrontendDir(appPaths, config) {
509
800
  return path.resolve(appPaths.appRoot, config.project.frontendDir)