reset-framework-cli 1.2.0 → 1.2.2

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 +19 -19
  3. package/package.json +8 -6
  4. package/src/commands/build.js +91 -73
  5. package/src/commands/dev.js +143 -122
  6. package/src/commands/doctor.js +61 -54
  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 +251 -236
  15. package/src/lib/process.js +303 -303
  16. package/src/lib/project.js +531 -494
  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
package/src/lib/output.js CHANGED
@@ -1,124 +1,125 @@
1
- import path from "node:path"
2
- import { cp, mkdir, readdir, rm, writeFile } from "node:fs/promises"
3
- import { existsSync } from "node:fs"
4
-
5
- import { logger } from "./logger.js"
6
- import { runCommand } from "./process.js"
7
- import {
8
- resolveFrameworkRuntimeBinary,
9
- resolveFrameworkRuntimeOutputDir
10
- } from "./project.js"
11
-
12
- function escapePlistString(value) {
13
- return value
14
- .replaceAll("&", "&")
15
- .replaceAll("<", "&lt;")
16
- .replaceAll(">", "&gt;")
17
- }
18
-
19
- function serializePlistValue(value, indentLevel = 1) {
20
- const indent = " ".repeat(indentLevel)
21
-
22
- if (Array.isArray(value)) {
23
- if (value.length === 0) {
24
- return `${indent}<array/>\n`
25
- }
26
-
27
- return (
28
- `${indent}<array>\n` +
29
- value.map((entry) => serializePlistValue(entry, indentLevel + 1)).join("") +
30
- `${indent}</array>\n`
31
- )
32
- }
33
-
34
- if (typeof value === "object" && value !== null) {
35
- const entries = Object.entries(value)
36
- if (entries.length === 0) {
37
- return `${indent}<dict/>\n`
38
- }
39
-
40
- return (
41
- `${indent}<dict>\n` +
42
- entries
43
- .map(
44
- ([key, entryValue]) =>
45
- `${" ".repeat(indentLevel + 1)}<key>${escapePlistString(key)}</key>\n${serializePlistValue(entryValue, indentLevel + 1)}`
46
- )
47
- .join("") +
48
- `${indent}</dict>\n`
49
- )
50
- }
51
-
52
- if (typeof value === "boolean") {
53
- return `${indent}<${value ? "true" : "false"}/>\n`
54
- }
55
-
56
- if (typeof value === "number") {
57
- return `${indent}<integer>${value}</integer>\n`
58
- }
59
-
60
- return `${indent}<string>${escapePlistString(String(value))}</string>\n`
61
- }
62
-
63
- function buildMacOSInfoPlist(config) {
64
- const urlTypes = Array.isArray(config.protocols?.schemes)
65
- ? config.protocols.schemes.map((schemeConfig) => ({
66
- CFBundleTypeRole: schemeConfig.role ?? "Viewer",
67
- CFBundleURLName:
68
- typeof schemeConfig.name === "string" && schemeConfig.name.trim() !== ""
69
- ? schemeConfig.name
70
- : `${config.appId}.${schemeConfig.scheme}`,
71
- CFBundleURLSchemes: [schemeConfig.scheme]
72
- }))
73
- : []
74
-
75
- const plist = {
76
- CFBundleDevelopmentRegion: "English",
77
- CFBundleDisplayName: config.productName,
78
- CFBundleExecutable: "reset-framework",
79
- CFBundleIdentifier: config.appId,
80
- CFBundleInfoDictionaryVersion: "6.0",
81
- CFBundleName: config.productName,
82
- CFBundlePackageType: "APPL",
83
- CFBundleShortVersionString: config.version,
84
- CFBundleVersion: config.version,
85
- CSResourcesFileMapped: true
86
- }
87
-
88
- if (urlTypes.length > 0) {
89
- plist.CFBundleURLTypes = urlTypes
90
- }
91
-
92
- return (
93
- `<?xml version="1.0" encoding="UTF-8"?>\n` +
94
- `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n` +
95
- `<plist version="1.0">\n` +
96
- serializePlistValue(plist, 0) +
97
- `</plist>\n`
98
- )
99
- }
100
-
101
- function shouldCopyWindowsRuntimeArtifact(entryName) {
102
- const extension = path.extname(entryName).toLowerCase()
103
-
104
- return [
105
- ".bin",
106
- ".dat",
107
- ".dll",
108
- ".exe",
109
- ".json",
110
- ".manifest",
111
- ".pak",
112
- ".pri"
113
- ].includes(extension)
114
- }
115
-
116
- function escapePowerShellLiteral(value) {
117
- return String(value).replaceAll("'", "''")
118
- }
119
-
1
+ import path from "node:path"
2
+ import { cp, mkdir, readdir, rm, writeFile } from "node:fs/promises"
3
+ import { existsSync } from "node:fs"
4
+
5
+ import { stageAppBackend } from "./backend.js"
6
+ import { logger } from "./logger.js"
7
+ import { runCommand } from "./process.js"
8
+ import {
9
+ resolveFrameworkRuntimeBinary,
10
+ resolveFrameworkRuntimeOutputDir
11
+ } from "./project.js"
12
+
13
+ function escapePlistString(value) {
14
+ return value
15
+ .replaceAll("&", "&amp;")
16
+ .replaceAll("<", "&lt;")
17
+ .replaceAll(">", "&gt;")
18
+ }
19
+
20
+ function serializePlistValue(value, indentLevel = 1) {
21
+ const indent = " ".repeat(indentLevel)
22
+
23
+ if (Array.isArray(value)) {
24
+ if (value.length === 0) {
25
+ return `${indent}<array/>\n`
26
+ }
27
+
28
+ return (
29
+ `${indent}<array>\n` +
30
+ value.map((entry) => serializePlistValue(entry, indentLevel + 1)).join("") +
31
+ `${indent}</array>\n`
32
+ )
33
+ }
34
+
35
+ if (typeof value === "object" && value !== null) {
36
+ const entries = Object.entries(value)
37
+ if (entries.length === 0) {
38
+ return `${indent}<dict/>\n`
39
+ }
40
+
41
+ return (
42
+ `${indent}<dict>\n` +
43
+ entries
44
+ .map(
45
+ ([key, entryValue]) =>
46
+ `${" ".repeat(indentLevel + 1)}<key>${escapePlistString(key)}</key>\n${serializePlistValue(entryValue, indentLevel + 1)}`
47
+ )
48
+ .join("") +
49
+ `${indent}</dict>\n`
50
+ )
51
+ }
52
+
53
+ if (typeof value === "boolean") {
54
+ return `${indent}<${value ? "true" : "false"}/>\n`
55
+ }
56
+
57
+ if (typeof value === "number") {
58
+ return `${indent}<integer>${value}</integer>\n`
59
+ }
60
+
61
+ return `${indent}<string>${escapePlistString(String(value))}</string>\n`
62
+ }
63
+
64
+ function buildMacOSInfoPlist(config) {
65
+ const urlTypes = Array.isArray(config.protocols?.schemes)
66
+ ? config.protocols.schemes.map((schemeConfig) => ({
67
+ CFBundleTypeRole: schemeConfig.role ?? "Viewer",
68
+ CFBundleURLName:
69
+ typeof schemeConfig.name === "string" && schemeConfig.name.trim() !== ""
70
+ ? schemeConfig.name
71
+ : `${config.appId}.${schemeConfig.scheme}`,
72
+ CFBundleURLSchemes: [schemeConfig.scheme]
73
+ }))
74
+ : []
75
+
76
+ const plist = {
77
+ CFBundleDevelopmentRegion: "English",
78
+ CFBundleDisplayName: config.productName,
79
+ CFBundleExecutable: "reset-framework",
80
+ CFBundleIdentifier: config.appId,
81
+ CFBundleInfoDictionaryVersion: "6.0",
82
+ CFBundleName: config.productName,
83
+ CFBundlePackageType: "APPL",
84
+ CFBundleShortVersionString: config.version,
85
+ CFBundleVersion: config.version,
86
+ CSResourcesFileMapped: true
87
+ }
88
+
89
+ if (urlTypes.length > 0) {
90
+ plist.CFBundleURLTypes = urlTypes
91
+ }
92
+
93
+ return (
94
+ `<?xml version="1.0" encoding="UTF-8"?>\n` +
95
+ `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n` +
96
+ `<plist version="1.0">\n` +
97
+ serializePlistValue(plist, 0) +
98
+ `</plist>\n`
99
+ )
100
+ }
101
+
102
+ function shouldCopyWindowsRuntimeArtifact(entryName) {
103
+ const extension = path.extname(entryName).toLowerCase()
104
+
105
+ return [
106
+ ".bin",
107
+ ".dat",
108
+ ".dll",
109
+ ".exe",
110
+ ".json",
111
+ ".manifest",
112
+ ".pak",
113
+ ".pri"
114
+ ].includes(extension)
115
+ }
116
+
117
+ function escapePowerShellLiteral(value) {
118
+ return String(value).replaceAll("'", "''")
119
+ }
120
+
120
121
  export async function stageMacOSAppBundle(frameworkPaths, frameworkBuildPaths, config, options = {}) {
121
- const { dryRun = false, outputPaths, configPath, preferSource = false } = options
122
+ const { dryRun = false, outputPaths, configPath, preferSource = false, backendArtifact = null } = options
122
123
  const runtimeSourceBundle = resolveFrameworkRuntimeOutputDir(frameworkPaths, frameworkBuildPaths, "release", {
123
124
  mustExist: !dryRun,
124
125
  preferSource
@@ -131,37 +132,44 @@ export async function stageMacOSAppBundle(frameworkPaths, frameworkBuildPaths, c
131
132
  if (!existsSync(outputPaths.frontendEntryFile) && !dryRun) {
132
133
  throw new Error(
133
134
  `Missing frontend build output at ${outputPaths.frontendEntryFile}. Run the frontend build first.`
134
- )
135
- }
136
-
135
+ )
136
+ }
137
+
137
138
  logger.info(`Staging app bundle into ${outputPaths.appBundlePath}`)
138
139
 
139
140
  if (dryRun) {
140
141
  logger.info(`- copy ${runtimeSourceBundle} -> ${outputPaths.appBundlePath}`)
141
142
  logger.info(`- copy ${configPath} -> ${outputPaths.bundledConfigPath}`)
142
143
  logger.info(`- copy ${outputPaths.frontendDistDir} -> ${outputPaths.bundledFrontendDir}`)
144
+ if (backendArtifact) {
145
+ logger.info(`- copy ${backendArtifact.bundleEntryPath} -> ${outputPaths.bundledBackendEntryPath}`)
146
+ logger.info(`- copy ${backendArtifact.runnerPath} -> ${outputPaths.bundledBackendRunnerPath}`)
147
+ logger.info(`- copy ${backendArtifact.supportModulePath} -> ${outputPaths.bundledBackendSupportModulePath}`)
148
+ logger.info(`- copy ${backendArtifact.runtimeExecutablePath} -> ${outputPaths.bundledBackendRuntimePath}`)
149
+ }
143
150
  return outputPaths
144
- }
145
-
151
+ }
152
+
146
153
  await mkdir(outputPaths.macosDir, { recursive: true })
147
154
  await rm(outputPaths.appBundlePath, { recursive: true, force: true })
148
155
  await cp(runtimeSourceBundle, outputPaths.appBundlePath, { recursive: true })
149
-
150
- await mkdir(outputPaths.resourcesDir, { recursive: true })
151
- await cp(configPath, outputPaths.bundledConfigPath, { force: true })
152
- await rm(outputPaths.bundledFrontendDir, { recursive: true, force: true })
153
- await cp(outputPaths.frontendDistDir, outputPaths.bundledFrontendDir, { recursive: true })
154
- await writeFile(
155
- path.join(outputPaths.appBundlePath, "Contents", "Info.plist"),
156
- buildMacOSInfoPlist(config),
157
- "utf8"
158
- )
159
-
160
- return outputPaths
161
- }
162
-
156
+
157
+ await mkdir(outputPaths.resourcesDir, { recursive: true })
158
+ await cp(configPath, outputPaths.bundledConfigPath, { force: true })
159
+ await rm(outputPaths.bundledFrontendDir, { recursive: true, force: true })
160
+ await cp(outputPaths.frontendDistDir, outputPaths.bundledFrontendDir, { recursive: true })
161
+ await stageAppBackend(backendArtifact, outputPaths)
162
+ await writeFile(
163
+ path.join(outputPaths.appBundlePath, "Contents", "Info.plist"),
164
+ buildMacOSInfoPlist(config),
165
+ "utf8"
166
+ )
167
+
168
+ return outputPaths
169
+ }
170
+
163
171
  export async function stageWindowsAppBundle(frameworkPaths, frameworkBuildPaths, config, options = {}) {
164
- const { dryRun = false, outputPaths, configPath, preferSource = false } = options
172
+ const { dryRun = false, outputPaths, configPath, preferSource = false, backendArtifact = null } = options
165
173
  const runtimeSourceBinary = resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPaths, "release", {
166
174
  mustExist: !dryRun,
167
175
  preferSource
@@ -170,99 +178,106 @@ export async function stageWindowsAppBundle(frameworkPaths, frameworkBuildPaths,
170
178
  mustExist: !dryRun,
171
179
  preferSource
172
180
  })
173
-
174
- if (!outputPaths) {
175
- throw new Error("stageWindowsAppBundle requires resolved output paths")
176
- }
177
-
178
- if (!existsSync(outputPaths.frontendEntryFile) && !dryRun) {
179
- throw new Error(
180
- `Missing frontend build output at ${outputPaths.frontendEntryFile}. Run the frontend build first.`
181
- )
182
- }
183
-
184
- logger.info(`Staging Windows app into ${outputPaths.appBundlePath}`)
185
-
186
- if (dryRun) {
187
- logger.info(`- copy ${runtimeSourceDir} -> ${outputPaths.appBundlePath}`)
188
- logger.info(`- rename ${runtimeSourceBinary} -> ${outputPaths.appExecutablePath}`)
189
- logger.info(`- copy ${configPath} -> ${outputPaths.bundledConfigPath}`)
190
- logger.info(`- copy ${outputPaths.frontendDistDir} -> ${outputPaths.bundledFrontendDir}`)
191
- return outputPaths
192
- }
193
-
194
- await mkdir(outputPaths.windowsDir, { recursive: true })
195
- await rm(outputPaths.appBundlePath, { recursive: true, force: true })
196
- await mkdir(outputPaths.appBundlePath, { recursive: true })
197
- const runtimeEntries = await readdir(runtimeSourceDir, { withFileTypes: true })
198
- const runtimeBinaryName = path.basename(runtimeSourceBinary)
199
-
200
- for (const entry of runtimeEntries) {
201
- if (!entry.isDirectory() && !shouldCopyWindowsRuntimeArtifact(entry.name)) {
202
- continue
203
- }
204
-
205
- const sourcePath = path.join(runtimeSourceDir, entry.name)
206
- const targetPath =
207
- entry.name.toLowerCase() === runtimeBinaryName.toLowerCase()
208
- ? outputPaths.appExecutablePath
209
- : path.join(outputPaths.appBundlePath, entry.name)
210
-
211
- await cp(sourcePath, targetPath, { recursive: true, force: true })
212
- }
213
-
214
- await mkdir(outputPaths.resourcesDir, { recursive: true })
215
- await cp(configPath, outputPaths.bundledConfigPath, { force: true })
216
- await rm(outputPaths.bundledFrontendDir, { recursive: true, force: true })
217
- await cp(outputPaths.frontendDistDir, outputPaths.bundledFrontendDir, { recursive: true })
218
-
219
- return outputPaths
220
- }
221
-
222
- export async function createMacOSZipArchive(outputPaths, options = {}) {
223
- const { dryRun = false } = options
224
-
225
- logger.info(`Packaging ${outputPaths.appBundlePath} -> ${outputPaths.zipPath}`)
226
-
227
- if (process.platform !== "darwin") {
228
- throw new Error("Packaging is only implemented for macOS right now")
229
- }
230
-
231
- if (!dryRun) {
232
- await mkdir(outputPaths.packagesDir, { recursive: true })
233
- }
234
-
235
- await runCommand(
236
- "ditto",
237
- ["-c", "-k", "--sequesterRsrc", "--keepParent", outputPaths.appBundlePath, outputPaths.zipPath],
238
- { dryRun }
239
- )
240
- }
241
-
242
- export async function createWindowsZipArchive(outputPaths, options = {}) {
243
- const { dryRun = false } = options
244
-
245
- logger.info(`Packaging ${outputPaths.appBundlePath} -> ${outputPaths.zipPath}`)
246
-
247
- if (process.platform !== "win32") {
248
- throw new Error("Packaging is only implemented for Windows and macOS right now")
249
- }
250
-
251
- if (!dryRun) {
252
- await mkdir(outputPaths.packagesDir, { recursive: true })
253
- await rm(outputPaths.zipPath, { force: true })
254
- }
255
-
256
- const literalSourcePath = escapePowerShellLiteral(outputPaths.appBundlePath)
257
- const literalDestinationPath = escapePowerShellLiteral(outputPaths.zipPath)
258
- const archiveCommand = [
259
- "$ErrorActionPreference = 'Stop'",
260
- `Compress-Archive -LiteralPath '${literalSourcePath}' -DestinationPath '${literalDestinationPath}' -CompressionLevel Optimal`
261
- ].join("; ")
262
-
263
- await runCommand(
264
- "powershell.exe",
265
- ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", archiveCommand],
266
- { dryRun }
267
- )
268
- }
181
+
182
+ if (!outputPaths) {
183
+ throw new Error("stageWindowsAppBundle requires resolved output paths")
184
+ }
185
+
186
+ if (!existsSync(outputPaths.frontendEntryFile) && !dryRun) {
187
+ throw new Error(
188
+ `Missing frontend build output at ${outputPaths.frontendEntryFile}. Run the frontend build first.`
189
+ )
190
+ }
191
+
192
+ logger.info(`Staging Windows app into ${outputPaths.appBundlePath}`)
193
+
194
+ if (dryRun) {
195
+ logger.info(`- copy ${runtimeSourceDir} -> ${outputPaths.appBundlePath}`)
196
+ logger.info(`- rename ${runtimeSourceBinary} -> ${outputPaths.appExecutablePath}`)
197
+ logger.info(`- copy ${configPath} -> ${outputPaths.bundledConfigPath}`)
198
+ logger.info(`- copy ${outputPaths.frontendDistDir} -> ${outputPaths.bundledFrontendDir}`)
199
+ if (backendArtifact) {
200
+ logger.info(`- copy ${backendArtifact.bundleEntryPath} -> ${outputPaths.bundledBackendEntryPath}`)
201
+ logger.info(`- copy ${backendArtifact.runnerPath} -> ${outputPaths.bundledBackendRunnerPath}`)
202
+ logger.info(`- copy ${backendArtifact.supportModulePath} -> ${outputPaths.bundledBackendSupportModulePath}`)
203
+ logger.info(`- copy ${backendArtifact.runtimeExecutablePath} -> ${outputPaths.bundledBackendRuntimePath}`)
204
+ }
205
+ return outputPaths
206
+ }
207
+
208
+ await mkdir(outputPaths.windowsDir, { recursive: true })
209
+ await rm(outputPaths.appBundlePath, { recursive: true, force: true })
210
+ await mkdir(outputPaths.appBundlePath, { recursive: true })
211
+ const runtimeEntries = await readdir(runtimeSourceDir, { withFileTypes: true })
212
+ const runtimeBinaryName = path.basename(runtimeSourceBinary)
213
+
214
+ for (const entry of runtimeEntries) {
215
+ if (!entry.isDirectory() && !shouldCopyWindowsRuntimeArtifact(entry.name)) {
216
+ continue
217
+ }
218
+
219
+ const sourcePath = path.join(runtimeSourceDir, entry.name)
220
+ const targetPath =
221
+ entry.name.toLowerCase() === runtimeBinaryName.toLowerCase()
222
+ ? outputPaths.appExecutablePath
223
+ : path.join(outputPaths.appBundlePath, entry.name)
224
+
225
+ await cp(sourcePath, targetPath, { recursive: true, force: true })
226
+ }
227
+
228
+ await mkdir(outputPaths.resourcesDir, { recursive: true })
229
+ await cp(configPath, outputPaths.bundledConfigPath, { force: true })
230
+ await rm(outputPaths.bundledFrontendDir, { recursive: true, force: true })
231
+ await cp(outputPaths.frontendDistDir, outputPaths.bundledFrontendDir, { recursive: true })
232
+ await stageAppBackend(backendArtifact, outputPaths)
233
+
234
+ return outputPaths
235
+ }
236
+
237
+ export async function createMacOSZipArchive(outputPaths, options = {}) {
238
+ const { dryRun = false } = options
239
+
240
+ logger.info(`Packaging ${outputPaths.appBundlePath} -> ${outputPaths.zipPath}`)
241
+
242
+ if (process.platform !== "darwin") {
243
+ throw new Error("Packaging is only implemented for macOS right now")
244
+ }
245
+
246
+ if (!dryRun) {
247
+ await mkdir(outputPaths.packagesDir, { recursive: true })
248
+ }
249
+
250
+ await runCommand(
251
+ "ditto",
252
+ ["-c", "-k", "--sequesterRsrc", "--keepParent", outputPaths.appBundlePath, outputPaths.zipPath],
253
+ { dryRun }
254
+ )
255
+ }
256
+
257
+ export async function createWindowsZipArchive(outputPaths, options = {}) {
258
+ const { dryRun = false } = options
259
+
260
+ logger.info(`Packaging ${outputPaths.appBundlePath} -> ${outputPaths.zipPath}`)
261
+
262
+ if (process.platform !== "win32") {
263
+ throw new Error("Packaging is only implemented for Windows and macOS right now")
264
+ }
265
+
266
+ if (!dryRun) {
267
+ await mkdir(outputPaths.packagesDir, { recursive: true })
268
+ await rm(outputPaths.zipPath, { force: true })
269
+ }
270
+
271
+ const literalSourcePath = escapePowerShellLiteral(outputPaths.appBundlePath)
272
+ const literalDestinationPath = escapePowerShellLiteral(outputPaths.zipPath)
273
+ const archiveCommand = [
274
+ "$ErrorActionPreference = 'Stop'",
275
+ `Compress-Archive -LiteralPath '${literalSourcePath}' -DestinationPath '${literalDestinationPath}' -CompressionLevel Optimal`
276
+ ].join("; ")
277
+
278
+ await runCommand(
279
+ "powershell.exe",
280
+ ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", archiveCommand],
281
+ { dryRun }
282
+ )
283
+ }