reset-framework-cli 1.1.2 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +47 -47
  3. package/package.json +4 -4
  4. package/src/commands/build.js +114 -113
  5. package/src/commands/dev.js +148 -144
  6. package/src/commands/doctor.js +89 -89
  7. package/src/commands/init.js +838 -903
  8. package/src/commands/package.js +49 -44
  9. package/src/index.js +213 -213
  10. package/src/lib/context.js +66 -66
  11. package/src/lib/framework.js +150 -28
  12. package/src/lib/logger.js +11 -11
  13. package/src/lib/output.js +214 -106
  14. package/src/lib/process.js +253 -156
  15. package/src/lib/project.js +559 -475
  16. package/src/lib/toolchain.js +62 -62
  17. package/src/lib/ui.js +244 -244
  18. package/templates/basic/README.md +15 -15
  19. package/templates/basic/frontend/README.md +73 -73
  20. package/templates/basic/frontend/eslint.config.js +23 -23
  21. package/templates/basic/frontend/index.html +13 -13
  22. package/templates/basic/frontend/package.json +31 -31
  23. package/templates/basic/frontend/public/icons.svg +24 -24
  24. package/templates/basic/frontend/src/App.css +216 -138
  25. package/templates/basic/frontend/src/App.tsx +77 -76
  26. package/templates/basic/frontend/src/assets/vite.svg +1 -1
  27. package/templates/basic/frontend/src/index.css +111 -111
  28. package/templates/basic/frontend/src/lib/reset.ts +1 -1
  29. package/templates/basic/frontend/src/main.tsx +10 -10
  30. package/templates/basic/frontend/tsconfig.app.json +28 -28
  31. package/templates/basic/frontend/tsconfig.json +7 -7
  32. package/templates/basic/frontend/tsconfig.node.json +26 -26
  33. package/templates/basic/frontend/vite.config.ts +16 -16
  34. package/templates/basic/reset.config.json +58 -58
@@ -1,112 +1,112 @@
1
- import { existsSync, readFileSync } from "node:fs"
2
- import { createRequire } from "node:module"
3
- import path from "node:path"
4
- import { fileURLToPath } from "node:url"
5
-
6
- const require = createRequire(import.meta.url)
7
-
8
- function requireString(object, key, scope) {
9
- if (typeof object?.[key] !== "string" || object[key].trim() === "") {
10
- throw new Error(`Missing or invalid string field '${scope}.${key}'`)
11
- }
12
-
13
- return object[key]
14
- }
15
-
16
- function optionalString(object, key, fallback) {
17
- if (object == null || !(key in object)) {
18
- return fallback
19
- }
20
-
21
- if (typeof object[key] !== "string" || object[key].trim() === "") {
22
- throw new Error(`Missing or invalid string field '${key}'`)
23
- }
24
-
25
- return object[key]
26
- }
27
-
28
- function optionalObject(object, key) {
29
- if (object == null || !(key in object)) {
30
- return {}
31
- }
32
-
33
- if (typeof object[key] !== "object" || object[key] === null || Array.isArray(object[key])) {
34
- throw new Error(`Missing or invalid object field '${key}'`)
35
- }
36
-
37
- return object[key]
38
- }
39
-
40
- function sanitizeName(value) {
41
- return value
42
- .toLowerCase()
43
- .replace(/[^a-z0-9]+/g, "-")
44
- .replace(/^-+|-+$/g, "") || "reset-app"
45
- }
46
-
47
- function normalizeFrontendDir(value) {
48
- const trimmed = value.trim()
49
-
50
- if (trimmed === "." || trimmed === "./") {
51
- return "."
52
- }
53
-
54
- if (path.isAbsolute(trimmed)) {
55
- throw new Error("project.frontendDir must be a relative path")
56
- }
57
-
58
- const normalized = path.normalize(trimmed).replace(/\\/g, "/").replace(/\/+$/g, "")
59
-
60
- if (normalized === "" || normalized === ".") {
61
- return "."
62
- }
63
-
64
- if (normalized.startsWith("../")) {
65
- throw new Error("project.frontendDir cannot point outside the app root")
66
- }
67
-
68
- return normalized
69
- }
70
-
71
- function normalizeStyling(value) {
72
- if (value === "css" || value === "tailwindcss") {
73
- return value
74
- }
75
-
76
- throw new Error("project.styling must be either 'css' or 'tailwindcss'")
77
- }
78
-
79
- function normalizeProtocolScheme(value) {
80
- if (typeof value !== "string" || value.trim() === "") {
81
- throw new Error("protocols.schemes[].scheme must be a non-empty string")
82
- }
83
-
84
- const normalized = value.trim().toLowerCase()
85
- if (!/^[a-z][a-z0-9+.-]*$/.test(normalized)) {
86
- throw new Error(
87
- "protocols.schemes[].scheme must start with a letter and only contain letters, numbers, '+', '-', and '.'"
88
- )
89
- }
90
-
91
- return normalized
92
- }
93
-
94
- function normalizeProtocolRole(value) {
95
- if (value === "Editor" || value === "Viewer" || value === "Shell" || value === "None") {
96
- return value
97
- }
98
-
99
- throw new Error("protocols.schemes[].role must be one of Editor, Viewer, Shell, None")
100
- }
101
-
102
- function toTitleCase(value) {
103
- return value
104
- .split(/[^a-zA-Z0-9]+/)
105
- .filter(Boolean)
106
- .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
107
- .join(" ") || "Reset App"
108
- }
109
-
1
+ import { existsSync, readFileSync } from "node:fs"
2
+ import { createRequire } from "node:module"
3
+ import path from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ const require = createRequire(import.meta.url)
7
+
8
+ function requireString(object, key, scope) {
9
+ if (typeof object?.[key] !== "string" || object[key].trim() === "") {
10
+ throw new Error(`Missing or invalid string field '${scope}.${key}'`)
11
+ }
12
+
13
+ return object[key]
14
+ }
15
+
16
+ function optionalString(object, key, fallback) {
17
+ if (object == null || !(key in object)) {
18
+ return fallback
19
+ }
20
+
21
+ if (typeof object[key] !== "string" || object[key].trim() === "") {
22
+ throw new Error(`Missing or invalid string field '${key}'`)
23
+ }
24
+
25
+ return object[key]
26
+ }
27
+
28
+ function optionalObject(object, key) {
29
+ if (object == null || !(key in object)) {
30
+ return {}
31
+ }
32
+
33
+ if (typeof object[key] !== "object" || object[key] === null || Array.isArray(object[key])) {
34
+ throw new Error(`Missing or invalid object field '${key}'`)
35
+ }
36
+
37
+ return object[key]
38
+ }
39
+
40
+ function sanitizeName(value) {
41
+ return value
42
+ .toLowerCase()
43
+ .replace(/[^a-z0-9]+/g, "-")
44
+ .replace(/^-+|-+$/g, "") || "reset-app"
45
+ }
46
+
47
+ function normalizeFrontendDir(value) {
48
+ const trimmed = value.trim()
49
+
50
+ if (trimmed === "." || trimmed === "./") {
51
+ return "."
52
+ }
53
+
54
+ if (path.isAbsolute(trimmed)) {
55
+ throw new Error("project.frontendDir must be a relative path")
56
+ }
57
+
58
+ const normalized = path.normalize(trimmed).replace(/\\/g, "/").replace(/\/+$/g, "")
59
+
60
+ if (normalized === "" || normalized === ".") {
61
+ return "."
62
+ }
63
+
64
+ if (normalized.startsWith("../")) {
65
+ throw new Error("project.frontendDir cannot point outside the app root")
66
+ }
67
+
68
+ return normalized
69
+ }
70
+
71
+ function normalizeStyling(value) {
72
+ if (value === "css" || value === "tailwindcss") {
73
+ return value
74
+ }
75
+
76
+ throw new Error("project.styling must be either 'css' or 'tailwindcss'")
77
+ }
78
+
79
+ function normalizeProtocolScheme(value) {
80
+ if (typeof value !== "string" || value.trim() === "") {
81
+ throw new Error("protocols.schemes[].scheme must be a non-empty string")
82
+ }
83
+
84
+ const normalized = value.trim().toLowerCase()
85
+ if (!/^[a-z][a-z0-9+.-]*$/.test(normalized)) {
86
+ throw new Error(
87
+ "protocols.schemes[].scheme must start with a letter and only contain letters, numbers, '+', '-', and '.'"
88
+ )
89
+ }
90
+
91
+ return normalized
92
+ }
93
+
94
+ function normalizeProtocolRole(value) {
95
+ if (value === "Editor" || value === "Viewer" || value === "Shell" || value === "None") {
96
+ return value
97
+ }
98
+
99
+ throw new Error("protocols.schemes[].role must be one of Editor, Viewer, Shell, None")
100
+ }
101
+
102
+ function toTitleCase(value) {
103
+ return value
104
+ .split(/[^a-zA-Z0-9]+/)
105
+ .filter(Boolean)
106
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
107
+ .join(" ") || "Reset App"
108
+ }
109
+
110
110
  function readJsonFile(filePath) {
111
111
  return JSON.parse(readFileSync(filePath, "utf8"))
112
112
  }
@@ -115,406 +115,490 @@ function fileExists(filePath) {
115
115
  return existsSync(filePath)
116
116
  }
117
117
 
118
- function isPathInside(parentDir, candidatePath) {
119
- const relative = path.relative(parentDir, candidatePath)
120
- return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative)
121
- }
122
-
123
- function resolvePackageInfo(packageName, fallbackRoot) {
124
- try {
125
- const packageJsonPath = require.resolve(`${packageName}/package.json`)
126
- const packageRoot = path.dirname(packageJsonPath)
127
- const manifest = readJsonFile(packageJsonPath)
128
-
129
- return {
130
- packageName: manifest.name ?? packageName,
131
- packageRoot,
132
- packageJsonPath,
133
- version: manifest.version ?? "0.0.0",
134
- localFallback: false
135
- }
136
- } catch {
137
- const packageJsonPath = path.join(fallbackRoot, "package.json")
138
- const manifest = readJsonFile(packageJsonPath)
139
-
140
- return {
141
- packageName: manifest.name ?? packageName,
142
- packageRoot: fallbackRoot,
143
- packageJsonPath,
144
- version: manifest.version ?? "0.0.0",
145
- localFallback: true
146
- }
147
- }
148
- }
149
-
150
- function resolveWorkspacePackageInfo(packageName, workspaceRoot, fallbackInfo) {
151
- const packageJsonPath = path.join(workspaceRoot, "package.json")
152
-
153
- if (!existsSync(packageJsonPath)) {
154
- return fallbackInfo
155
- }
156
-
157
- const manifest = readJsonFile(packageJsonPath)
158
-
159
- return {
160
- packageName: manifest.name ?? packageName,
161
- packageRoot: workspaceRoot,
162
- packageJsonPath,
163
- version: manifest.version ?? fallbackInfo.version,
164
- localFallback: true
165
- }
166
- }
167
-
168
- export function resolveFrameworkPaths() {
169
- const moduleDir = path.dirname(fileURLToPath(import.meta.url))
170
- const cliDir = path.resolve(moduleDir, "../..")
171
- const packagesDir = path.resolve(cliDir, "..")
172
- const cliPackageJsonPath = path.join(cliDir, "package.json")
173
- const cliManifest = readJsonFile(cliPackageJsonPath)
174
- const nativePackage = resolveWorkspacePackageInfo(
175
- "@reset-framework/native",
176
- path.join(packagesDir, "native"),
177
- resolvePackageInfo("@reset-framework/native", path.join(packagesDir, "native"))
178
- )
179
- const sdkPackage = resolveWorkspacePackageInfo(
180
- "@reset-framework/sdk",
181
- path.join(packagesDir, "sdk"),
182
- resolvePackageInfo("@reset-framework/sdk", path.join(packagesDir, "sdk"))
183
- )
184
- const schemaPackage = resolveWorkspacePackageInfo(
185
- "@reset-framework/schema",
186
- path.join(packagesDir, "schema"),
187
- resolvePackageInfo("@reset-framework/schema", path.join(packagesDir, "schema"))
188
- )
189
- const isWorkspaceLayout = [nativePackage, sdkPackage, schemaPackage].every((pkg) =>
190
- isPathInside(packagesDir, pkg.packageRoot)
191
- )
192
-
193
- return {
194
- cliDir,
195
- cliPackage: {
196
- packageName: cliManifest.name,
197
- packageRoot: cliDir,
198
- packageJsonPath: cliPackageJsonPath,
199
- version: cliManifest.version ?? "0.0.0",
200
- localFallback: true
201
- },
202
- packagesDir,
203
- frameworkRoot: nativePackage.packageRoot,
204
- frameworkPackage: nativePackage,
205
- isWorkspaceLayout,
206
- sdkPackage,
207
- schemaPackage,
208
- runtimeDir: path.join(nativePackage.packageRoot, "runtime"),
209
- rootCMakePath: path.join(nativePackage.packageRoot, "CMakeLists.txt"),
210
- cmakePresetsPath: path.join(nativePackage.packageRoot, "CMakePresets.json"),
211
- vcpkgManifestPath: path.join(nativePackage.packageRoot, "vcpkg.json"),
212
- templatesDir: path.join(cliDir, "templates"),
213
- }
214
- }
215
-
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
+ }
125
+
126
+ function isPathInside(parentDir, candidatePath) {
127
+ const relative = path.relative(parentDir, candidatePath)
128
+ return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative)
129
+ }
130
+
131
+ function resolvePackageInfo(packageName, fallbackRoot) {
132
+ try {
133
+ const packageJsonPath = require.resolve(`${packageName}/package.json`)
134
+ const packageRoot = path.dirname(packageJsonPath)
135
+ const manifest = readJsonFile(packageJsonPath)
136
+
137
+ return {
138
+ packageName: manifest.name ?? packageName,
139
+ packageRoot,
140
+ packageJsonPath,
141
+ version: manifest.version ?? "0.0.0",
142
+ localFallback: false
143
+ }
144
+ } catch {
145
+ const packageJsonPath = path.join(fallbackRoot, "package.json")
146
+ const manifest = readJsonFile(packageJsonPath)
147
+
148
+ return {
149
+ packageName: manifest.name ?? packageName,
150
+ packageRoot: fallbackRoot,
151
+ packageJsonPath,
152
+ version: manifest.version ?? "0.0.0",
153
+ localFallback: true
154
+ }
155
+ }
156
+ }
157
+
158
+ function resolveWorkspacePackageInfo(packageName, workspaceRoot, fallbackInfo) {
159
+ const packageJsonPath = path.join(workspaceRoot, "package.json")
160
+
161
+ if (!existsSync(packageJsonPath)) {
162
+ return fallbackInfo
163
+ }
164
+
165
+ const manifest = readJsonFile(packageJsonPath)
166
+
167
+ return {
168
+ packageName: manifest.name ?? packageName,
169
+ packageRoot: workspaceRoot,
170
+ packageJsonPath,
171
+ version: manifest.version ?? fallbackInfo.version,
172
+ localFallback: true
173
+ }
174
+ }
175
+
176
+ export function resolveFrameworkPaths() {
177
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url))
178
+ const cliDir = path.resolve(moduleDir, "../..")
179
+ const packagesDir = path.resolve(cliDir, "..")
180
+ const cliPackageJsonPath = path.join(cliDir, "package.json")
181
+ const cliManifest = readJsonFile(cliPackageJsonPath)
182
+ const nativePackage = resolveWorkspacePackageInfo(
183
+ "@reset-framework/native",
184
+ path.join(packagesDir, "native"),
185
+ resolvePackageInfo("@reset-framework/native", path.join(packagesDir, "native"))
186
+ )
187
+ const sdkPackage = resolveWorkspacePackageInfo(
188
+ "@reset-framework/sdk",
189
+ path.join(packagesDir, "sdk"),
190
+ resolvePackageInfo("@reset-framework/sdk", path.join(packagesDir, "sdk"))
191
+ )
192
+ const schemaPackage = resolveWorkspacePackageInfo(
193
+ "@reset-framework/schema",
194
+ path.join(packagesDir, "schema"),
195
+ resolvePackageInfo("@reset-framework/schema", path.join(packagesDir, "schema"))
196
+ )
197
+ const isWorkspaceLayout = [nativePackage, sdkPackage, schemaPackage].every((pkg) =>
198
+ isPathInside(packagesDir, pkg.packageRoot)
199
+ )
200
+
201
+ return {
202
+ cliDir,
203
+ cliPackage: {
204
+ packageName: cliManifest.name,
205
+ packageRoot: cliDir,
206
+ packageJsonPath: cliPackageJsonPath,
207
+ version: cliManifest.version ?? "0.0.0",
208
+ localFallback: true
209
+ },
210
+ packagesDir,
211
+ frameworkRoot: nativePackage.packageRoot,
212
+ frameworkPackage: nativePackage,
213
+ isWorkspaceLayout,
214
+ sdkPackage,
215
+ schemaPackage,
216
+ runtimeDir: path.join(nativePackage.packageRoot, "runtime"),
217
+ rootCMakePath: path.join(nativePackage.packageRoot, "CMakeLists.txt"),
218
+ cmakePresetsPath: path.join(nativePackage.packageRoot, "CMakePresets.json"),
219
+ vcpkgManifestPath: path.join(nativePackage.packageRoot, "vcpkg.json"),
220
+ templatesDir: path.join(cliDir, "templates"),
221
+ }
222
+ }
223
+
216
224
  export function resolveFrameworkBuildPaths(appPaths) {
217
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
+ ]
218
260
 
219
261
  return {
220
262
  frameworkCacheRoot,
221
263
  devBuildDir: path.join(frameworkCacheRoot, "build", "dev"),
222
264
  releaseBuildDir: path.join(frameworkCacheRoot, "build", "release"),
223
- devAppBinary: path.join(
224
- frameworkCacheRoot,
225
- "build",
226
- "dev",
227
- "runtime",
228
- "reset-framework.app",
229
- "Contents",
230
- "MacOS",
231
- "reset-framework"
232
- ),
233
- releaseAppTemplate: path.join(
234
- frameworkCacheRoot,
235
- "build",
236
- "release",
237
- "runtime",
238
- "reset-framework.app"
239
- )
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]
240
275
  }
241
276
  }
242
277
 
243
- export function resolveAppPaths(appRoot) {
244
- return {
245
- appRoot,
246
- appPackageJsonPath: path.join(appRoot, "package.json"),
247
- frontendDir: path.join(appRoot, "frontend"),
248
- resetConfigPath: path.join(appRoot, "reset.config.json"),
249
- legacyResetConfigPath: path.join(appRoot, "frontend", "reset.config.json")
250
- }
251
- }
278
+ export function resolveFrameworkRuntimeBinary(frameworkBuildPaths, mode, options = {}) {
279
+ const { mustExist = false } = options
280
+ const candidates =
281
+ mode === "release"
282
+ ? (frameworkBuildPaths.releaseAppBinaryCandidates ?? [frameworkBuildPaths.releaseAppTemplate])
283
+ : (frameworkBuildPaths.devAppBinaryCandidates ?? [frameworkBuildPaths.devAppBinary])
284
+ const existing = resolveExistingCandidate(candidates)
252
285
 
253
- export function resolveConfigPath(appPaths) {
254
- if (existsSync(appPaths.resetConfigPath)) {
255
- return appPaths.resetConfigPath
286
+ if (existing) {
287
+ return existing
256
288
  }
257
289
 
258
- if (existsSync(appPaths.legacyResetConfigPath)) {
259
- return appPaths.legacyResetConfigPath
290
+ if (mustExist) {
291
+ throw new Error(`Missing built runtime executable. Looked in: ${candidates.join(", ")}`)
260
292
  }
261
293
 
262
- return appPaths.resetConfigPath
263
- }
264
-
265
- export function getFrameworkChecks(frameworkPaths) {
266
- return [
267
- ["native", frameworkPaths.frameworkPackage.packageJsonPath],
268
- ["sdk", frameworkPaths.sdkPackage.packageJsonPath],
269
- ["schema", frameworkPaths.schemaPackage.packageJsonPath],
270
- ["cmake", frameworkPaths.rootCMakePath],
271
- ["runtime", frameworkPaths.runtimeDir],
272
- ["vcpkg", frameworkPaths.vcpkgManifestPath],
273
- ["templates", frameworkPaths.templatesDir]
274
- ]
275
- }
276
-
277
- export function getAppChecks(appPaths) {
278
- return [["config", resolveConfigPath(appPaths)]]
294
+ return candidates[0]
279
295
  }
280
296
 
281
- export function assertFrameworkInstall(frameworkPaths) {
282
- const missing = getFrameworkChecks(frameworkPaths)
283
- .filter(([, filePath]) => !existsSync(filePath))
284
- .map(([label]) => label)
297
+ export function resolveFrameworkRuntimeOutputDir(frameworkBuildPaths, mode, options = {}) {
298
+ const { mustExist = false } = options
299
+ const candidates =
300
+ mode === "release"
301
+ ? (frameworkBuildPaths.releaseRuntimeOutputCandidates ?? [])
302
+ : (frameworkBuildPaths.devRuntimeOutputCandidates ?? [])
303
+ const existing = resolveExistingCandidate(candidates)
285
304
 
286
- if (missing.length > 0) {
287
- throw new Error(
288
- `Reset CLI installation is incomplete. Missing: ${missing.join(", ")}`
289
- )
305
+ if (existing) {
306
+ return existing
290
307
  }
291
- }
292
-
293
- export function assertAppProject(appPaths, config) {
294
- const checks = [...getAppChecks(appPaths)]
295
-
296
- if (config) {
297
- checks.unshift(["frontend", resolveFrontendDir(appPaths, config)])
298
- }
299
-
300
- const missing = checks
301
- .filter(([, filePath]) => !existsSync(filePath))
302
- .map(([label]) => label)
303
-
304
- if (missing.length > 0) {
305
- throw new Error(
306
- `Current directory does not look like a Reset app. Missing: ${missing.join(", ")}`
307
- )
308
- }
309
- }
310
-
311
- export function validateResetConfig(rawConfig) {
312
- const frontend = optionalObject(rawConfig, "frontend")
313
- const build = optionalObject(rawConfig, "build")
314
- const project = optionalObject(rawConfig, "project")
315
- const security = optionalObject(rawConfig, "security")
316
- const protocols = optionalObject(rawConfig, "protocols")
317
- const windowConfig = optionalObject(rawConfig, "window")
318
- const rawProtocolSchemes = Array.isArray(protocols.schemes) ? protocols.schemes : []
319
- const seenProtocolSchemes = new Set()
320
-
321
- return {
322
- ...rawConfig,
323
- name: sanitizeName(requireString(rawConfig, "name", "config")),
324
- productName: requireString(rawConfig, "productName", "config"),
325
- appId: requireString(rawConfig, "appId", "config"),
326
- version: requireString(rawConfig, "version", "config"),
327
- window: {
328
- title: optionalString(windowConfig, "title", "Reset App")
329
- },
330
- frontend: {
331
- devUrl: requireString(frontend, "devUrl", "frontend"),
332
- distDir: optionalString(frontend, "distDir", "dist"),
333
- entryHtml: optionalString(frontend, "entryHtml", "index.html")
334
- },
335
- project: {
336
- frontendDir: normalizeFrontendDir(optionalString(project, "frontendDir", "frontend")),
337
- styling: normalizeStyling(optionalString(project, "styling", "css"))
338
- },
339
- build: {
340
- outputDir: optionalString(build, "outputDir", ".reset/build")
341
- },
342
- security: {
343
- permissions: Array.isArray(security.permissions)
344
- ? security.permissions.map((value) => {
345
- if (typeof value !== "string" || value.trim() === "") {
346
- throw new Error("Missing or invalid string field 'security.permissions'")
347
- }
348
-
349
- return value
350
- })
351
- : []
352
- },
353
- protocols: {
354
- schemes: rawProtocolSchemes.map((entry) => {
355
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
356
- throw new Error("protocols.schemes entries must be objects")
357
- }
358
-
359
- const scheme = normalizeProtocolScheme(entry.scheme)
360
- if (seenProtocolSchemes.has(scheme)) {
361
- throw new Error(`Duplicate protocol scheme '${scheme}'`)
362
- }
363
-
364
- seenProtocolSchemes.add(scheme)
365
308
 
366
- return {
367
- scheme,
368
- name:
369
- typeof entry.name === "string" && entry.name.trim() !== ""
370
- ? entry.name
371
- : scheme,
372
- role:
373
- entry.role === undefined
374
- ? "Viewer"
375
- : normalizeProtocolRole(entry.role)
376
- }
377
- })
378
- }
379
- }
380
- }
381
-
382
- export function loadResetConfig(appPaths) {
383
- const configPath = resolveConfigPath(appPaths)
384
- const raw = readFileSync(configPath, "utf8")
385
-
386
- try {
387
- return validateResetConfig(JSON.parse(raw))
388
- } catch (error) {
389
- if (error instanceof SyntaxError) {
390
- throw new Error(`Invalid JSON in ${configPath}: ${error.message}`)
309
+ if (candidates.length > 0) {
310
+ if (mustExist) {
311
+ throw new Error(`Missing built runtime output directory. Looked in: ${candidates.join(", ")}`)
391
312
  }
392
313
 
393
- throw error
394
- }
395
- }
396
-
314
+ return candidates[0]
315
+ }
316
+
317
+ return path.dirname(resolveFrameworkRuntimeBinary(frameworkBuildPaths, mode, options))
318
+ }
319
+
320
+ export function resolveAppPaths(appRoot) {
321
+ return {
322
+ appRoot,
323
+ appPackageJsonPath: path.join(appRoot, "package.json"),
324
+ frontendDir: path.join(appRoot, "frontend"),
325
+ resetConfigPath: path.join(appRoot, "reset.config.json"),
326
+ legacyResetConfigPath: path.join(appRoot, "frontend", "reset.config.json")
327
+ }
328
+ }
329
+
330
+ export function resolveConfigPath(appPaths) {
331
+ if (existsSync(appPaths.resetConfigPath)) {
332
+ return appPaths.resetConfigPath
333
+ }
334
+
335
+ if (existsSync(appPaths.legacyResetConfigPath)) {
336
+ return appPaths.legacyResetConfigPath
337
+ }
338
+
339
+ return appPaths.resetConfigPath
340
+ }
341
+
342
+ export function getFrameworkChecks(frameworkPaths) {
343
+ return [
344
+ ["native", frameworkPaths.frameworkPackage.packageJsonPath],
345
+ ["sdk", frameworkPaths.sdkPackage.packageJsonPath],
346
+ ["schema", frameworkPaths.schemaPackage.packageJsonPath],
347
+ ["cmake", frameworkPaths.rootCMakePath],
348
+ ["runtime", frameworkPaths.runtimeDir],
349
+ ["vcpkg", frameworkPaths.vcpkgManifestPath],
350
+ ["templates", frameworkPaths.templatesDir]
351
+ ]
352
+ }
353
+
354
+ export function getAppChecks(appPaths) {
355
+ return [["config", resolveConfigPath(appPaths)]]
356
+ }
357
+
358
+ export function assertFrameworkInstall(frameworkPaths) {
359
+ const missing = getFrameworkChecks(frameworkPaths)
360
+ .filter(([, filePath]) => !existsSync(filePath))
361
+ .map(([label]) => label)
362
+
363
+ if (missing.length > 0) {
364
+ throw new Error(
365
+ `Reset CLI installation is incomplete. Missing: ${missing.join(", ")}`
366
+ )
367
+ }
368
+ }
369
+
370
+ export function assertAppProject(appPaths, config) {
371
+ const checks = [...getAppChecks(appPaths)]
372
+
373
+ if (config) {
374
+ checks.unshift(["frontend", resolveFrontendDir(appPaths, config)])
375
+ }
376
+
377
+ const missing = checks
378
+ .filter(([, filePath]) => !existsSync(filePath))
379
+ .map(([label]) => label)
380
+
381
+ if (missing.length > 0) {
382
+ throw new Error(
383
+ `Current directory does not look like a Reset app. Missing: ${missing.join(", ")}`
384
+ )
385
+ }
386
+ }
387
+
388
+ export function validateResetConfig(rawConfig) {
389
+ const frontend = optionalObject(rawConfig, "frontend")
390
+ const build = optionalObject(rawConfig, "build")
391
+ const project = optionalObject(rawConfig, "project")
392
+ const security = optionalObject(rawConfig, "security")
393
+ const protocols = optionalObject(rawConfig, "protocols")
394
+ const windowConfig = optionalObject(rawConfig, "window")
395
+ const rawProtocolSchemes = Array.isArray(protocols.schemes) ? protocols.schemes : []
396
+ const seenProtocolSchemes = new Set()
397
+
398
+ return {
399
+ ...rawConfig,
400
+ name: sanitizeName(requireString(rawConfig, "name", "config")),
401
+ productName: requireString(rawConfig, "productName", "config"),
402
+ appId: requireString(rawConfig, "appId", "config"),
403
+ version: requireString(rawConfig, "version", "config"),
404
+ window: {
405
+ title: optionalString(windowConfig, "title", "Reset App")
406
+ },
407
+ frontend: {
408
+ devUrl: requireString(frontend, "devUrl", "frontend"),
409
+ distDir: optionalString(frontend, "distDir", "dist"),
410
+ entryHtml: optionalString(frontend, "entryHtml", "index.html")
411
+ },
412
+ project: {
413
+ frontendDir: normalizeFrontendDir(optionalString(project, "frontendDir", "frontend")),
414
+ styling: normalizeStyling(optionalString(project, "styling", "css"))
415
+ },
416
+ build: {
417
+ outputDir: optionalString(build, "outputDir", ".reset/build")
418
+ },
419
+ security: {
420
+ permissions: Array.isArray(security.permissions)
421
+ ? security.permissions.map((value) => {
422
+ if (typeof value !== "string" || value.trim() === "") {
423
+ throw new Error("Missing or invalid string field 'security.permissions'")
424
+ }
425
+
426
+ return value
427
+ })
428
+ : []
429
+ },
430
+ protocols: {
431
+ schemes: rawProtocolSchemes.map((entry) => {
432
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
433
+ throw new Error("protocols.schemes entries must be objects")
434
+ }
435
+
436
+ const scheme = normalizeProtocolScheme(entry.scheme)
437
+ if (seenProtocolSchemes.has(scheme)) {
438
+ throw new Error(`Duplicate protocol scheme '${scheme}'`)
439
+ }
440
+
441
+ seenProtocolSchemes.add(scheme)
442
+
443
+ return {
444
+ scheme,
445
+ name:
446
+ typeof entry.name === "string" && entry.name.trim() !== ""
447
+ ? entry.name
448
+ : scheme,
449
+ role:
450
+ entry.role === undefined
451
+ ? "Viewer"
452
+ : normalizeProtocolRole(entry.role)
453
+ }
454
+ })
455
+ }
456
+ }
457
+ }
458
+
459
+ export function loadResetConfig(appPaths) {
460
+ const configPath = resolveConfigPath(appPaths)
461
+ const raw = readFileSync(configPath, "utf8")
462
+
463
+ try {
464
+ return validateResetConfig(JSON.parse(raw))
465
+ } catch (error) {
466
+ if (error instanceof SyntaxError) {
467
+ throw new Error(`Invalid JSON in ${configPath}: ${error.message}`)
468
+ }
469
+
470
+ throw error
471
+ }
472
+ }
473
+
397
474
  export function resolveAppOutputPaths(appPaths, config) {
398
475
  const outputRoot = path.resolve(appPaths.appRoot, config.build.outputDir)
399
476
  const frontendDir = resolveFrontendDir(appPaths, config)
400
- const appBundleName = `${config.productName}.app`
401
- const macosDir = path.join(outputRoot, "macos")
402
- const appBundlePath = path.join(macosDir, appBundleName)
403
- const resourcesDir = path.join(appBundlePath, "Contents", "Resources")
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")
404
484
  const packagesDir = path.join(outputRoot, "packages")
405
485
 
406
486
  return {
407
487
  outputRoot,
408
- macosDir,
488
+ macosDir: isWindows ? undefined : platformDir,
489
+ windowsDir: isWindows ? platformDir : undefined,
409
490
  appBundlePath,
491
+ appExecutablePath: isWindows
492
+ ? path.join(appBundlePath, `${config.productName}.exe`)
493
+ : undefined,
410
494
  resourcesDir,
411
495
  bundledConfigPath: path.join(resourcesDir, "reset.config.json"),
412
496
  bundledFrontendDir: path.join(resourcesDir, config.frontend.distDir),
413
497
  frontendDistDir: path.resolve(frontendDir, config.frontend.distDir),
414
498
  frontendEntryFile: path.resolve(
415
- frontendDir,
416
- config.frontend.distDir,
417
- config.frontend.entryHtml
499
+ frontendDir,
500
+ config.frontend.distDir,
501
+ config.frontend.entryHtml
418
502
  ),
419
503
  packagesDir,
420
- zipPath: path.join(packagesDir, `${config.name}-macos.zip`)
421
- }
422
- }
423
-
424
- export function resolveFrontendDir(appPaths, config) {
425
- return path.resolve(appPaths.appRoot, config.project.frontendDir)
426
- }
427
-
428
- export function resolveDevServerOptions(config) {
429
- const url = new URL(config.frontend.devUrl)
430
-
431
- return {
432
- host: url.hostname,
433
- port: url.port || "5173",
434
- url: url.toString()
435
- }
436
- }
437
-
438
- export function makeAppMetadata(appName) {
439
- const name = sanitizeName(appName)
440
- const productName = toTitleCase(appName)
441
-
442
- return {
443
- name,
444
- productName,
445
- appId: `com.example.${name}`,
446
- title: productName
447
- }
448
- }
449
-
450
- export function resolveSdkDependencySpec(frameworkPaths) {
451
- if (frameworkPaths.isWorkspaceLayout || frameworkPaths.sdkPackage.localFallback) {
452
- return `file:${frameworkPaths.sdkPackage.packageRoot}`
453
- }
454
-
455
- return `^${frameworkPaths.sdkPackage.version}`
456
- }
457
-
458
- export function resolveCliDependencySpec(frameworkPaths) {
459
- if (frameworkPaths.isWorkspaceLayout || frameworkPaths.cliPackage.localFallback) {
460
- return `file:${frameworkPaths.cliPackage.packageRoot}`
461
- }
462
-
463
- return `^${frameworkPaths.cliPackage.version}`
464
- }
465
-
466
- function detectPackageManagerFromManifest(packageJsonPath) {
467
- if (!fileExists(packageJsonPath)) {
468
- return null
469
- }
470
-
471
- const manifest = readJsonFile(packageJsonPath)
472
- const raw = typeof manifest.packageManager === "string" ? manifest.packageManager.trim() : ""
473
- if (raw === "") {
474
- return null
475
- }
476
-
477
- const [name] = raw.split("@")
478
- if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") {
479
- return name
480
- }
481
-
482
- return null
483
- }
484
-
485
- function detectPackageManagerFromLocks(directory) {
486
- if (!fileExists(directory)) {
487
- return null
488
- }
489
-
490
- if (fileExists(path.join(directory, "bun.lock")) || fileExists(path.join(directory, "bun.lockb"))) {
491
- return "bun"
492
- }
493
-
494
- if (fileExists(path.join(directory, "pnpm-lock.yaml"))) {
495
- return "pnpm"
496
- }
497
-
498
- if (fileExists(path.join(directory, "yarn.lock"))) {
499
- return "yarn"
500
- }
501
-
502
- if (fileExists(path.join(directory, "package-lock.json"))) {
503
- return "npm"
504
- }
505
-
506
- return null
507
- }
508
-
509
- export function resolveAppPackageManager(appPaths, config) {
510
- const frontendDir = resolveFrontendDir(appPaths, config)
511
- const frontendPackageJsonPath = path.join(frontendDir, "package.json")
512
-
513
- return (
514
- detectPackageManagerFromManifest(appPaths.appPackageJsonPath) ??
515
- detectPackageManagerFromManifest(frontendPackageJsonPath) ??
516
- detectPackageManagerFromLocks(appPaths.appRoot) ??
517
- detectPackageManagerFromLocks(frontendDir) ??
518
- "npm"
519
- )
520
- }
504
+ zipPath: path.join(packagesDir, `${config.name}-${isWindows ? "windows" : "macos"}.zip`)
505
+ }
506
+ }
507
+
508
+ export function resolveFrontendDir(appPaths, config) {
509
+ return path.resolve(appPaths.appRoot, config.project.frontendDir)
510
+ }
511
+
512
+ export function resolveDevServerOptions(config) {
513
+ const url = new URL(config.frontend.devUrl)
514
+
515
+ return {
516
+ host: url.hostname,
517
+ port: url.port || "5173",
518
+ url: url.toString()
519
+ }
520
+ }
521
+
522
+ export function makeAppMetadata(appName) {
523
+ const name = sanitizeName(appName)
524
+ const productName = toTitleCase(appName)
525
+
526
+ return {
527
+ name,
528
+ productName,
529
+ appId: `com.example.${name}`,
530
+ title: productName
531
+ }
532
+ }
533
+
534
+ export function resolveSdkDependencySpec(frameworkPaths) {
535
+ if (frameworkPaths.isWorkspaceLayout || frameworkPaths.sdkPackage.localFallback) {
536
+ return `file:${frameworkPaths.sdkPackage.packageRoot}`
537
+ }
538
+
539
+ return `^${frameworkPaths.sdkPackage.version}`
540
+ }
541
+
542
+ export function resolveCliDependencySpec(frameworkPaths) {
543
+ if (frameworkPaths.isWorkspaceLayout || frameworkPaths.cliPackage.localFallback) {
544
+ return `file:${frameworkPaths.cliPackage.packageRoot}`
545
+ }
546
+
547
+ return `^${frameworkPaths.cliPackage.version}`
548
+ }
549
+
550
+ function detectPackageManagerFromManifest(packageJsonPath) {
551
+ if (!fileExists(packageJsonPath)) {
552
+ return null
553
+ }
554
+
555
+ const manifest = readJsonFile(packageJsonPath)
556
+ const raw = typeof manifest.packageManager === "string" ? manifest.packageManager.trim() : ""
557
+ if (raw === "") {
558
+ return null
559
+ }
560
+
561
+ const [name] = raw.split("@")
562
+ if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") {
563
+ return name
564
+ }
565
+
566
+ return null
567
+ }
568
+
569
+ function detectPackageManagerFromLocks(directory) {
570
+ if (!fileExists(directory)) {
571
+ return null
572
+ }
573
+
574
+ if (fileExists(path.join(directory, "bun.lock")) || fileExists(path.join(directory, "bun.lockb"))) {
575
+ return "bun"
576
+ }
577
+
578
+ if (fileExists(path.join(directory, "pnpm-lock.yaml"))) {
579
+ return "pnpm"
580
+ }
581
+
582
+ if (fileExists(path.join(directory, "yarn.lock"))) {
583
+ return "yarn"
584
+ }
585
+
586
+ if (fileExists(path.join(directory, "package-lock.json"))) {
587
+ return "npm"
588
+ }
589
+
590
+ return null
591
+ }
592
+
593
+ export function resolveAppPackageManager(appPaths, config) {
594
+ const frontendDir = resolveFrontendDir(appPaths, config)
595
+ const frontendPackageJsonPath = path.join(frontendDir, "package.json")
596
+
597
+ return (
598
+ detectPackageManagerFromManifest(appPaths.appPackageJsonPath) ??
599
+ detectPackageManagerFromManifest(frontendPackageJsonPath) ??
600
+ detectPackageManagerFromLocks(appPaths.appRoot) ??
601
+ detectPackageManagerFromLocks(frontendDir) ??
602
+ "npm"
603
+ )
604
+ }