reset-framework-cli 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +19 -19
  3. package/package.json +6 -6
  4. package/src/commands/build.js +71 -71
  5. package/src/commands/dev.js +121 -121
  6. package/src/commands/doctor.js +54 -54
  7. package/src/commands/init.js +866 -866
  8. package/src/commands/package.js +68 -68
  9. package/src/index.js +195 -195
  10. package/src/lib/context.js +66 -66
  11. package/src/lib/framework.js +57 -57
  12. package/src/lib/logger.js +11 -11
  13. package/src/lib/output.js +234 -234
  14. package/src/lib/process.js +303 -303
  15. package/src/lib/project.js +493 -493
  16. package/src/lib/toolchain.js +62 -62
  17. package/src/lib/ui.js +244 -244
  18. package/templates/basic/README.md +15 -15
  19. package/templates/basic/frontend/README.md +73 -73
  20. package/templates/basic/frontend/eslint.config.js +23 -23
  21. package/templates/basic/frontend/index.html +13 -13
  22. package/templates/basic/frontend/package.json +31 -31
  23. package/templates/basic/frontend/public/icons.svg +24 -24
  24. package/templates/basic/frontend/src/App.css +216 -216
  25. package/templates/basic/frontend/src/App.tsx +77 -77
  26. package/templates/basic/frontend/src/assets/vite.svg +1 -1
  27. package/templates/basic/frontend/src/index.css +111 -111
  28. package/templates/basic/frontend/src/lib/reset.ts +1 -1
  29. package/templates/basic/frontend/src/main.tsx +10 -10
  30. package/templates/basic/frontend/tsconfig.app.json +28 -28
  31. package/templates/basic/frontend/tsconfig.json +7 -7
  32. package/templates/basic/frontend/tsconfig.node.json +26 -26
  33. package/templates/basic/frontend/vite.config.ts +16 -16
  34. package/templates/basic/reset.config.json +58 -58
@@ -1,8 +1,8 @@
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
-
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
6
  const require = createRequire(import.meta.url)
7
7
 
8
8
  const runtimePackageDescriptors = [
@@ -31,166 +31,166 @@ const runtimePackageDescriptors = [
31
31
  binarySegments: ["dist", "win32-x64", "reset-framework.exe"]
32
32
  }
33
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)
156
- }
157
-
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)
156
+ }
157
+
158
158
  function resolvePackageInfo(packageName, fallbackRoot) {
159
159
  try {
160
- const packageJsonPath = require.resolve(`${packageName}/package.json`)
161
- const packageRoot = path.dirname(packageJsonPath)
162
- const manifest = readJsonFile(packageJsonPath)
163
-
164
- return {
165
- packageName: manifest.name ?? packageName,
166
- packageRoot,
167
- packageJsonPath,
168
- version: manifest.version ?? "0.0.0",
169
- localFallback: false
170
- }
171
- } catch {
172
- const packageJsonPath = path.join(fallbackRoot, "package.json")
173
- const manifest = readJsonFile(packageJsonPath)
174
-
175
- return {
176
- packageName: manifest.name ?? packageName,
177
- packageRoot: fallbackRoot,
178
- packageJsonPath,
179
- version: manifest.version ?? "0.0.0",
180
- localFallback: true
181
- }
160
+ const packageJsonPath = require.resolve(`${packageName}/package.json`)
161
+ const packageRoot = path.dirname(packageJsonPath)
162
+ const manifest = readJsonFile(packageJsonPath)
163
+
164
+ return {
165
+ packageName: manifest.name ?? packageName,
166
+ packageRoot,
167
+ packageJsonPath,
168
+ version: manifest.version ?? "0.0.0",
169
+ localFallback: false
170
+ }
171
+ } catch {
172
+ const packageJsonPath = path.join(fallbackRoot, "package.json")
173
+ const manifest = readJsonFile(packageJsonPath)
174
+
175
+ return {
176
+ packageName: manifest.name ?? packageName,
177
+ packageRoot: fallbackRoot,
178
+ packageJsonPath,
179
+ version: manifest.version ?? "0.0.0",
180
+ localFallback: true
181
+ }
182
182
  }
183
183
  }
184
184
 
185
185
  function resolveWorkspacePackageInfo(packageName, workspaceRoot, fallbackInfo) {
186
- const packageJsonPath = path.join(workspaceRoot, "package.json")
187
-
188
- if (!existsSync(packageJsonPath)) {
189
- return fallbackInfo
190
- }
191
-
192
- const manifest = readJsonFile(packageJsonPath)
193
-
186
+ const packageJsonPath = path.join(workspaceRoot, "package.json")
187
+
188
+ if (!existsSync(packageJsonPath)) {
189
+ return fallbackInfo
190
+ }
191
+
192
+ const manifest = readJsonFile(packageJsonPath)
193
+
194
194
  return {
195
195
  packageName: manifest.name ?? packageName,
196
196
  packageRoot: workspaceRoot,
@@ -325,7 +325,7 @@ export function resolveFrameworkPaths() {
325
325
  "@reset-framework/sdk",
326
326
  path.join(packagesDir, "sdk"),
327
327
  resolvePackageInfo("@reset-framework/sdk", path.join(packagesDir, "sdk"))
328
- )
328
+ )
329
329
  const schemaPackage = resolveWorkspacePackageInfo(
330
330
  "@reset-framework/schema",
331
331
  path.join(packagesDir, "schema"),
@@ -361,58 +361,58 @@ export function resolveFrameworkPaths() {
361
361
  templatesDir: path.join(cliDir, "templates")
362
362
  }
363
363
  }
364
-
364
+
365
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]
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
416
  }
417
417
  }
418
418
 
@@ -497,15 +497,15 @@ export function resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPath
497
497
  mode === "release"
498
498
  ? (frameworkBuildPaths.releaseAppBinaryCandidates ?? [frameworkBuildPaths.releaseAppTemplate])
499
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
- }
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
509
 
510
510
  return candidates[0]
511
511
  }
@@ -530,45 +530,45 @@ export function resolveFrameworkRuntimeOutputDir(frameworkPaths, frameworkBuildP
530
530
  mode === "release"
531
531
  ? (frameworkBuildPaths.releaseRuntimeOutputCandidates ?? [])
532
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
-
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
547
  return path.dirname(resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPaths, mode, options))
548
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
-
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
572
  export function getFrameworkChecks(frameworkPaths) {
573
573
  return [
574
574
  ["sdk", frameworkPaths.sdkPackage.packageJsonPath],
@@ -657,239 +657,239 @@ export function assertFrameworkSourceInstall(frameworkPaths) {
657
657
  export function getAppChecks(appPaths) {
658
658
  return [["config", resolveConfigPath(appPaths)]]
659
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"
894
- )
895
- }
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"
894
+ )
895
+ }