reset-framework-cli 1.2.1 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +25 -25
  3. package/package.json +8 -6
  4. package/src/commands/build.js +144 -126
  5. package/src/commands/dev.js +195 -174
  6. package/src/commands/doctor.js +140 -133
  7. package/src/commands/init.js +946 -866
  8. package/src/commands/package.js +68 -68
  9. package/src/index.js +195 -195
  10. package/src/lib/backend.js +123 -0
  11. package/src/lib/context.js +66 -66
  12. package/src/lib/framework.js +57 -57
  13. package/src/lib/logger.js +11 -11
  14. package/src/lib/output.js +283 -268
  15. package/src/lib/process.js +303 -303
  16. package/src/lib/project.js +897 -833
  17. package/src/lib/toolchain.js +62 -62
  18. package/src/lib/ui.js +244 -244
  19. package/templates/basic/README.md +15 -15
  20. package/templates/basic/frontend/README.md +73 -73
  21. package/templates/basic/frontend/eslint.config.js +23 -23
  22. package/templates/basic/frontend/index.html +13 -13
  23. package/templates/basic/frontend/package.json +31 -31
  24. package/templates/basic/frontend/public/icons.svg +24 -24
  25. package/templates/basic/frontend/src/App.css +216 -216
  26. package/templates/basic/frontend/src/App.tsx +77 -77
  27. package/templates/basic/frontend/src/assets/vite.svg +1 -1
  28. package/templates/basic/frontend/src/index.css +111 -111
  29. package/templates/basic/frontend/src/lib/reset.ts +1 -1
  30. package/templates/basic/frontend/src/main.tsx +10 -10
  31. package/templates/basic/frontend/tsconfig.app.json +28 -28
  32. package/templates/basic/frontend/tsconfig.json +7 -7
  33. package/templates/basic/frontend/tsconfig.node.json +26 -26
  34. package/templates/basic/frontend/vite.config.ts +16 -16
  35. package/templates/basic/reset.config.json +58 -58
package/src/lib/output.js CHANGED
@@ -1,268 +1,283 @@
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
-
120
- export async function stageMacOSAppBundle(frameworkPaths, frameworkBuildPaths, config, options = {}) {
121
- const { dryRun = false, outputPaths, configPath, preferSource = false } = options
122
- const runtimeSourceBundle = resolveFrameworkRuntimeOutputDir(frameworkPaths, frameworkBuildPaths, "release", {
123
- mustExist: !dryRun,
124
- preferSource
125
- })
126
-
127
- if (!outputPaths) {
128
- throw new Error("stageMacOSAppBundle requires resolved output paths")
129
- }
130
-
131
- if (!existsSync(outputPaths.frontendEntryFile) && !dryRun) {
132
- throw new Error(
133
- `Missing frontend build output at ${outputPaths.frontendEntryFile}. Run the frontend build first.`
134
- )
135
- }
136
-
137
- logger.info(`Staging app bundle into ${outputPaths.appBundlePath}`)
138
-
139
- if (dryRun) {
140
- logger.info(`- copy ${runtimeSourceBundle} -> ${outputPaths.appBundlePath}`)
141
- logger.info(`- copy ${configPath} -> ${outputPaths.bundledConfigPath}`)
142
- logger.info(`- copy ${outputPaths.frontendDistDir} -> ${outputPaths.bundledFrontendDir}`)
143
- return outputPaths
144
- }
145
-
146
- await mkdir(outputPaths.macosDir, { recursive: true })
147
- await rm(outputPaths.appBundlePath, { recursive: true, force: true })
148
- 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
-
163
- export async function stageWindowsAppBundle(frameworkPaths, frameworkBuildPaths, config, options = {}) {
164
- const { dryRun = false, outputPaths, configPath, preferSource = false } = options
165
- const runtimeSourceBinary = resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPaths, "release", {
166
- mustExist: !dryRun,
167
- preferSource
168
- })
169
- const runtimeSourceDir = resolveFrameworkRuntimeOutputDir(frameworkPaths, frameworkBuildPaths, "release", {
170
- mustExist: !dryRun,
171
- preferSource
172
- })
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
- }
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
+
121
+ export async function stageMacOSAppBundle(frameworkPaths, frameworkBuildPaths, config, options = {}) {
122
+ const { dryRun = false, outputPaths, configPath, preferSource = false, backendArtifact = null } = options
123
+ const runtimeSourceBundle = resolveFrameworkRuntimeOutputDir(frameworkPaths, frameworkBuildPaths, "release", {
124
+ mustExist: !dryRun,
125
+ preferSource
126
+ })
127
+
128
+ if (!outputPaths) {
129
+ throw new Error("stageMacOSAppBundle requires resolved output paths")
130
+ }
131
+
132
+ if (!existsSync(outputPaths.frontendEntryFile) && !dryRun) {
133
+ throw new Error(
134
+ `Missing frontend build output at ${outputPaths.frontendEntryFile}. Run the frontend build first.`
135
+ )
136
+ }
137
+
138
+ logger.info(`Staging app bundle into ${outputPaths.appBundlePath}`)
139
+
140
+ if (dryRun) {
141
+ logger.info(`- copy ${runtimeSourceBundle} -> ${outputPaths.appBundlePath}`)
142
+ logger.info(`- copy ${configPath} -> ${outputPaths.bundledConfigPath}`)
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
+ }
150
+ return outputPaths
151
+ }
152
+
153
+ await mkdir(outputPaths.macosDir, { recursive: true })
154
+ await rm(outputPaths.appBundlePath, { recursive: true, force: true })
155
+ await cp(runtimeSourceBundle, outputPaths.appBundlePath, { recursive: true })
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
+
171
+ export async function stageWindowsAppBundle(frameworkPaths, frameworkBuildPaths, config, options = {}) {
172
+ const { dryRun = false, outputPaths, configPath, preferSource = false, backendArtifact = null } = options
173
+ const runtimeSourceBinary = resolveFrameworkRuntimeBinary(frameworkPaths, frameworkBuildPaths, "release", {
174
+ mustExist: !dryRun,
175
+ preferSource
176
+ })
177
+ const runtimeSourceDir = resolveFrameworkRuntimeOutputDir(frameworkPaths, frameworkBuildPaths, "release", {
178
+ mustExist: !dryRun,
179
+ preferSource
180
+ })
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
+ }