reset-framework-cli 1.2.1 → 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 +8 -6
  4. package/src/commands/build.js +144 -126
  5. package/src/commands/dev.js +195 -174
  6. package/src/commands/doctor.js +140 -133
  7. package/src/commands/init.js +946 -866
  8. package/src/commands/package.js +68 -68
  9. package/src/index.js +195 -195
  10. package/src/lib/backend.js +123 -0
  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 -268
  15. package/src/lib/process.js +303 -303
  16. package/src/lib/project.js +897 -833
  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"
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)
72
156
  }
73
157
 
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")
158
+ function normalizeResolutionPaths(value) {
159
+ if (!Array.isArray(value)) {
160
+ return []
83
161
  }
84
162
 
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
163
+ return value.filter((entry) => typeof entry === "string" && entry.trim() !== "")
96
164
  }
97
165
 
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
- }
166
+ function resolvePackageJsonPath(packageName, resolutionPaths) {
167
+ const packageJsonSpecifier = `${packageName}/package.json`
105
168
 
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
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"
177
+ return require.resolve(packageJsonSpecifier)
135
178
  }
136
179
 
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)
156
- }
180
+ function resolvePackageInfo(packageName, fallbackRoot, options = {}) {
181
+ const resolutionPaths = normalizeResolutionPaths(options.resolutionPaths)
157
182
 
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,626 +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 })
355
+ )
356
+ const backendPackage = resolveWorkspacePackageInfo(
357
+ "@reset-framework/backend",
358
+ path.join(packagesDir, "backend"),
359
+ resolvePackageInfo("@reset-framework/backend", path.join(packagesDir, "backend"), { resolutionPaths })
328
360
  )
329
361
  const schemaPackage = resolveWorkspacePackageInfo(
330
362
  "@reset-framework/schema",
331
363
  path.join(packagesDir, "schema"),
332
- resolvePackageInfo("@reset-framework/schema", path.join(packagesDir, "schema"))
333
- )
334
- const runtimePackage = resolveRuntimePackage(packagesDir)
335
- const isWorkspaceLayout =
336
- isWorkspacePackagesDir &&
337
- [cliDir, sdkPackage.packageRoot, schemaPackage.packageRoot].every((packageRoot) =>
338
- isPathInside(packagesDir, packageRoot)
339
- )
340
-
341
- return {
342
- cliDir,
343
- cliPackage: {
344
- packageName: cliManifest.name,
345
- packageRoot: cliDir,
346
- packageJsonPath: cliPackageJsonPath,
347
- version: cliManifest.version ?? "0.0.0",
348
- localFallback: isWorkspaceLayout
349
- },
350
- packagesDir,
351
- frameworkRoot: nativePackage?.packageRoot ?? null,
352
- frameworkPackage: nativePackage,
353
- isWorkspaceLayout,
354
- sdkPackage,
355
- schemaPackage,
356
- runtimePackage,
357
- runtimeDir: nativePackage ? path.join(nativePackage.packageRoot, "runtime") : null,
358
- rootCMakePath: nativePackage ? path.join(nativePackage.packageRoot, "CMakeLists.txt") : null,
359
- cmakePresetsPath: nativePackage ? path.join(nativePackage.packageRoot, "CMakePresets.json") : null,
360
- vcpkgManifestPath: nativePackage ? path.join(nativePackage.packageRoot, "vcpkg.json") : null,
361
- templatesDir: path.join(cliDir, "templates")
362
- }
363
- }
364
-
365
- export function resolveFrameworkBuildPaths(appPaths) {
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
- }
418
-
419
- export function resolveFrameworkRuntimeStrategy(frameworkPaths, options = {}) {
420
- const sourceRequested = isSourceRuntimeRequested(options)
421
- const runtimePackage = frameworkPaths.runtimePackage
422
- const hasPrebuiltRuntime =
423
- Boolean(runtimePackage?.packageInfo) &&
424
- Boolean(runtimePackage?.artifactRoot) &&
425
- Boolean(runtimePackage?.binaryPath) &&
426
- existsSync(runtimePackage.artifactRoot) &&
427
- existsSync(runtimePackage.binaryPath)
428
- const hasSourceRuntime = hasSourceRuntimeLayout(frameworkPaths)
429
-
430
- if (sourceRequested) {
431
- if (!hasSourceRuntime) {
432
- throw new Error(
433
- "Source runtime was requested, but @reset-framework/native is not installed in this CLI environment."
434
- )
435
- }
436
-
437
- return {
438
- kind: "source",
439
- packageInfo: frameworkPaths.frameworkPackage,
440
- label: `${frameworkPaths.frameworkPackage.packageName}@${frameworkPaths.frameworkPackage.version}`
441
- }
442
- }
443
-
444
- if (hasPrebuiltRuntime) {
445
- return {
446
- kind: "prebuilt",
447
- packageInfo: runtimePackage.packageInfo,
448
- descriptor: runtimePackage.descriptor,
449
- artifactRoot: runtimePackage.artifactRoot,
450
- binaryPath: runtimePackage.binaryPath,
451
- label: `${runtimePackage.packageInfo.packageName}@${runtimePackage.packageInfo.version}`
452
- }
453
- }
454
-
455
- if (hasSourceRuntime) {
456
- return {
457
- kind: "source",
458
- packageInfo: frameworkPaths.frameworkPackage,
459
- label: `${frameworkPaths.frameworkPackage.packageName}@${frameworkPaths.frameworkPackage.version}`
460
- }
461
- }
462
-
463
- if (runtimePackage?.packageInfo) {
464
- throw new Error(
465
- `Installed runtime package ${runtimePackage.packageInfo.packageName} is missing bundled runtime artifacts under ${runtimePackage.artifactRoot}.`
466
- )
467
- }
468
-
469
- if (runtimePackage?.descriptor) {
470
- throw new Error(
471
- `No bundled runtime package is installed for ${process.platform}-${process.arch}. Expected ${runtimePackage.descriptor.packageName}.`
472
- )
473
- }
474
-
475
- throw new Error(
476
- `Reset Framework does not currently provide a bundled runtime for ${process.platform}-${process.arch}.`
477
- )
478
- }
479
-
480
- export function resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPaths, mode, options = {}) {
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
-
496
- const candidates =
497
- mode === "release"
498
- ? (frameworkBuildPaths.releaseAppBinaryCandidates ?? [frameworkBuildPaths.releaseAppTemplate])
499
- : (frameworkBuildPaths.devAppBinaryCandidates ?? [frameworkBuildPaths.devAppBinary])
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
- }
509
-
510
- return candidates[0]
511
- }
512
-
513
- export function resolveFrameworkRuntimeOutputDir(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.artifactRoot)) {
519
- return strategy.artifactRoot
520
- }
521
-
522
- if (mustExist) {
523
- throw new Error(`Missing bundled runtime output directory at ${strategy.artifactRoot}.`)
524
- }
525
-
526
- return strategy.artifactRoot
527
- }
528
-
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))
548
- }
549
-
550
- export function resolveAppPaths(appRoot) {
551
- return {
552
- appRoot,
553
- appPackageJsonPath: path.join(appRoot, "package.json"),
554
- frontendDir: path.join(appRoot, "frontend"),
555
- resetConfigPath: path.join(appRoot, "reset.config.json"),
556
- legacyResetConfigPath: path.join(appRoot, "frontend", "reset.config.json")
557
- }
558
- }
559
-
560
- export function resolveConfigPath(appPaths) {
561
- if (existsSync(appPaths.resetConfigPath)) {
562
- return appPaths.resetConfigPath
563
- }
564
-
565
- if (existsSync(appPaths.legacyResetConfigPath)) {
566
- return appPaths.legacyResetConfigPath
567
- }
568
-
569
- return appPaths.resetConfigPath
570
- }
571
-
572
- export function getFrameworkChecks(frameworkPaths) {
573
- return [
574
- ["sdk", frameworkPaths.sdkPackage.packageJsonPath],
575
- ["schema", frameworkPaths.schemaPackage.packageJsonPath],
576
- ["templates", frameworkPaths.templatesDir]
577
- ]
578
- }
579
-
580
- export function getFrameworkRuntimeChecks(frameworkPaths) {
581
- const checks = []
582
-
583
- if (frameworkPaths.runtimePackage?.packageInfo) {
584
- checks.push(["runtime package", frameworkPaths.runtimePackage.packageInfo.packageJsonPath])
585
- if (frameworkPaths.runtimePackage.artifactRoot) {
586
- checks.push(["runtime artifact", frameworkPaths.runtimePackage.artifactRoot])
587
- }
588
- }
589
-
590
- if (frameworkPaths.frameworkPackage) {
591
- checks.push(["native source", frameworkPaths.frameworkPackage.packageJsonPath])
592
- }
593
-
594
- if (frameworkPaths.rootCMakePath) {
595
- checks.push(["cmake", frameworkPaths.rootCMakePath])
596
- }
597
-
598
- if (frameworkPaths.runtimeDir) {
599
- checks.push(["runtime source", frameworkPaths.runtimeDir])
600
- }
601
-
602
- if (frameworkPaths.vcpkgManifestPath) {
603
- checks.push(["vcpkg", frameworkPaths.vcpkgManifestPath])
604
- }
605
-
606
- return checks
607
- }
608
-
609
- export function getFrameworkRuntimeModeSummary(frameworkPaths, options = {}) {
610
- const strategy = resolveFrameworkRuntimeStrategy(frameworkPaths, options)
611
-
612
- return {
613
- kind: strategy.kind,
614
- label: strategy.label
615
- }
616
- }
617
-
618
- export function hasFrameworkSourcePackage(frameworkPaths) {
619
- return hasSourceRuntimeLayout(frameworkPaths)
620
- }
621
-
622
- export function isFrameworkSourceRequested(options = {}) {
623
- return isSourceRuntimeRequested(options)
624
- }
625
-
626
- export function assertFrameworkInstall(frameworkPaths) {
627
- const missing = getFrameworkChecks(frameworkPaths)
628
- .filter(([, filePath]) => !existsSync(filePath))
629
- .map(([label]) => label)
630
-
631
- if (missing.length > 0) {
632
- throw new Error(
633
- `Reset CLI installation is incomplete. Missing: ${missing.join(", ")}`
634
- )
635
- }
636
- }
637
-
638
- export function assertFrameworkSourceInstall(frameworkPaths) {
639
- const missing = [
640
- ["native source", frameworkPaths.frameworkPackage?.packageJsonPath],
641
- ["sdk", frameworkPaths.sdkPackage.packageJsonPath],
642
- ["cmake", frameworkPaths.rootCMakePath],
643
- ["runtime", frameworkPaths.runtimeDir],
644
- ["vcpkg", frameworkPaths.vcpkgManifestPath],
645
- ["templates", frameworkPaths.templatesDir]
646
- ]
647
- .filter(([, filePath]) => !filePath || !existsSync(filePath))
648
- .map(([label]) => label)
649
-
650
- if (missing.length > 0) {
651
- throw new Error(
652
- `Source runtime is unavailable in this CLI installation. Missing: ${missing.join(", ")}`
653
- )
654
- }
655
- }
656
-
657
- export function getAppChecks(appPaths) {
658
- return [["config", resolveConfigPath(appPaths)]]
659
- }
660
-
661
- export function assertAppProject(appPaths, config) {
662
- const checks = [...getAppChecks(appPaths)]
663
-
664
- if (config) {
665
- checks.unshift(["frontend", resolveFrontendDir(appPaths, config)])
666
- }
667
-
668
- const missing = checks
669
- .filter(([, filePath]) => !existsSync(filePath))
670
- .map(([label]) => label)
671
-
672
- if (missing.length > 0) {
673
- throw new Error(
674
- `Current directory does not look like a Reset app. Missing: ${missing.join(", ")}`
675
- )
676
- }
677
- }
678
-
679
- export function validateResetConfig(rawConfig) {
680
- const frontend = optionalObject(rawConfig, "frontend")
681
- const build = optionalObject(rawConfig, "build")
682
- const project = optionalObject(rawConfig, "project")
683
- const security = optionalObject(rawConfig, "security")
684
- const protocols = optionalObject(rawConfig, "protocols")
685
- const windowConfig = optionalObject(rawConfig, "window")
686
- const rawProtocolSchemes = Array.isArray(protocols.schemes) ? protocols.schemes : []
687
- const seenProtocolSchemes = new Set()
688
-
689
- return {
690
- ...rawConfig,
691
- name: sanitizeName(requireString(rawConfig, "name", "config")),
692
- productName: requireString(rawConfig, "productName", "config"),
693
- appId: requireString(rawConfig, "appId", "config"),
694
- version: requireString(rawConfig, "version", "config"),
695
- window: {
696
- title: optionalString(windowConfig, "title", "Reset App")
697
- },
698
- frontend: {
699
- devUrl: requireString(frontend, "devUrl", "frontend"),
700
- distDir: optionalString(frontend, "distDir", "dist"),
701
- entryHtml: optionalString(frontend, "entryHtml", "index.html")
702
- },
703
- project: {
704
- frontendDir: normalizeFrontendDir(optionalString(project, "frontendDir", "frontend")),
705
- styling: normalizeStyling(optionalString(project, "styling", "css"))
706
- },
707
- build: {
708
- outputDir: optionalString(build, "outputDir", ".reset/build")
709
- },
710
- security: {
711
- permissions: Array.isArray(security.permissions)
712
- ? security.permissions.map((value) => {
713
- if (typeof value !== "string" || value.trim() === "") {
714
- throw new Error("Missing or invalid string field 'security.permissions'")
715
- }
716
-
717
- return value
718
- })
719
- : []
720
- },
721
- protocols: {
722
- schemes: rawProtocolSchemes.map((entry) => {
723
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
724
- throw new Error("protocols.schemes entries must be objects")
725
- }
726
-
727
- const scheme = normalizeProtocolScheme(entry.scheme)
728
- if (seenProtocolSchemes.has(scheme)) {
729
- throw new Error(`Duplicate protocol scheme '${scheme}'`)
730
- }
731
-
732
- seenProtocolSchemes.add(scheme)
733
-
734
- return {
735
- scheme,
736
- name:
737
- typeof entry.name === "string" && entry.name.trim() !== ""
738
- ? entry.name
739
- : scheme,
740
- role:
741
- entry.role === undefined
742
- ? "Viewer"
743
- : normalizeProtocolRole(entry.role)
744
- }
745
- })
746
- }
747
- }
748
- }
749
-
750
- export function loadResetConfig(appPaths) {
751
- const configPath = resolveConfigPath(appPaths)
752
- const raw = readFileSync(configPath, "utf8")
753
-
754
- try {
755
- return validateResetConfig(JSON.parse(raw))
756
- } catch (error) {
757
- if (error instanceof SyntaxError) {
758
- throw new Error(`Invalid JSON in ${configPath}: ${error.message}`)
759
- }
760
-
761
- throw error
762
- }
763
- }
764
-
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(
790
- frontendDir,
791
- config.frontend.distDir,
792
- config.frontend.entryHtml
793
- ),
794
- packagesDir,
795
- zipPath: path.join(packagesDir, `${config.name}-${isWindows ? "windows" : "macos"}.zip`)
796
- }
797
- }
798
-
799
- export function resolveFrontendDir(appPaths, config) {
800
- return path.resolve(appPaths.appRoot, config.project.frontendDir)
801
- }
802
-
803
- export function resolveDevServerOptions(config) {
804
- const url = new URL(config.frontend.devUrl)
805
-
806
- return {
807
- host: url.hostname,
808
- port: url.port || "5173",
809
- url: url.toString()
810
- }
811
- }
812
-
813
- export function makeAppMetadata(appName) {
814
- const name = sanitizeName(appName)
815
- const productName = toTitleCase(appName)
816
-
817
- return {
818
- name,
819
- productName,
820
- appId: `com.example.${name}`,
821
- title: productName
822
- }
823
- }
824
-
825
- export function resolveSdkDependencySpec(frameworkPaths) {
826
- if (frameworkPaths.isWorkspaceLayout || frameworkPaths.sdkPackage.localFallback) {
827
- return `file:${frameworkPaths.sdkPackage.packageRoot}`
828
- }
829
-
830
- return `^${frameworkPaths.sdkPackage.version}`
831
- }
832
-
833
- export function resolveCliDependencySpec(frameworkPaths) {
834
- if (frameworkPaths.isWorkspaceLayout || frameworkPaths.cliPackage.localFallback) {
835
- return `file:${frameworkPaths.cliPackage.packageRoot}`
836
- }
837
-
838
- return `^${frameworkPaths.cliPackage.version}`
839
- }
840
-
841
- function detectPackageManagerFromManifest(packageJsonPath) {
842
- if (!fileExists(packageJsonPath)) {
843
- return null
844
- }
845
-
846
- const manifest = readJsonFile(packageJsonPath)
847
- const raw = typeof manifest.packageManager === "string" ? manifest.packageManager.trim() : ""
848
- if (raw === "") {
849
- return null
850
- }
851
-
852
- const [name] = raw.split("@")
853
- if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") {
854
- return name
855
- }
856
-
857
- return null
858
- }
859
-
860
- function detectPackageManagerFromLocks(directory) {
861
- if (!fileExists(directory)) {
862
- return null
863
- }
864
-
865
- if (fileExists(path.join(directory, "bun.lock")) || fileExists(path.join(directory, "bun.lockb"))) {
866
- return "bun"
867
- }
868
-
869
- if (fileExists(path.join(directory, "pnpm-lock.yaml"))) {
870
- return "pnpm"
871
- }
872
-
873
- if (fileExists(path.join(directory, "yarn.lock"))) {
874
- return "yarn"
875
- }
876
-
877
- if (fileExists(path.join(directory, "package-lock.json"))) {
878
- return "npm"
879
- }
880
-
881
- return null
882
- }
883
-
884
- export function resolveAppPackageManager(appPaths, config) {
885
- const frontendDir = resolveFrontendDir(appPaths, config)
886
- const frontendPackageJsonPath = path.join(frontendDir, "package.json")
887
-
888
- return (
889
- detectPackageManagerFromManifest(appPaths.appPackageJsonPath) ??
890
- detectPackageManagerFromManifest(frontendPackageJsonPath) ??
891
- detectPackageManagerFromLocks(appPaths.appRoot) ??
892
- detectPackageManagerFromLocks(frontendDir) ??
893
- "npm"
364
+ resolvePackageInfo("@reset-framework/schema", path.join(packagesDir, "schema"), { resolutionPaths })
894
365
  )
895
- }
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
+ }