reset-framework-cli 1.1.5 → 1.2.0
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.
- package/README.md +39 -29
- package/package.json +8 -4
- package/src/commands/build.js +56 -44
- package/src/commands/dev.js +61 -49
- package/src/commands/doctor.js +83 -35
- package/src/commands/init.js +44 -44
- package/src/commands/package.js +27 -27
- package/src/index.js +20 -18
- package/src/lib/framework.js +169 -82
- package/src/lib/output.js +142 -142
- package/src/lib/process.js +155 -155
- package/src/lib/project.js +497 -206
package/src/lib/output.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
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"
|
|
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
11
|
|
|
12
12
|
function escapePlistString(value) {
|
|
13
13
|
return value
|
|
@@ -60,7 +60,7 @@ function serializePlistValue(value, indentLevel = 1) {
|
|
|
60
60
|
return `${indent}<string>${escapePlistString(String(value))}</string>\n`
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function buildMacOSInfoPlist(config) {
|
|
63
|
+
function buildMacOSInfoPlist(config) {
|
|
64
64
|
const urlTypes = Array.isArray(config.protocols?.schemes)
|
|
65
65
|
? config.protocols.schemes.map((schemeConfig) => ({
|
|
66
66
|
CFBundleTypeRole: schemeConfig.role ?? "Viewer",
|
|
@@ -95,39 +95,84 @@ function buildMacOSInfoPlist(config) {
|
|
|
95
95
|
`<plist version="1.0">\n` +
|
|
96
96
|
serializePlistValue(plist, 0) +
|
|
97
97
|
`</plist>\n`
|
|
98
|
-
)
|
|
99
|
-
}
|
|
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
|
+
})
|
|
100
126
|
|
|
101
|
-
|
|
102
|
-
|
|
127
|
+
if (!outputPaths) {
|
|
128
|
+
throw new Error("stageMacOSAppBundle requires resolved output paths")
|
|
129
|
+
}
|
|
103
130
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
".pak",
|
|
112
|
-
".pri"
|
|
113
|
-
].includes(extension)
|
|
114
|
-
}
|
|
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}`)
|
|
115
138
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
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
|
+
}
|
|
119
145
|
|
|
120
|
-
|
|
121
|
-
|
|
146
|
+
await mkdir(outputPaths.macosDir, { recursive: true })
|
|
147
|
+
await rm(outputPaths.appBundlePath, { recursive: true, force: true })
|
|
148
|
+
await cp(runtimeSourceBundle, outputPaths.appBundlePath, { recursive: true })
|
|
122
149
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
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
|
+
)
|
|
126
159
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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")
|
|
131
176
|
}
|
|
132
177
|
|
|
133
178
|
if (!existsSync(outputPaths.frontendEntryFile) && !dryRun) {
|
|
@@ -136,91 +181,46 @@ export async function stageMacOSAppBundle(frameworkBuildPaths, appPaths, config,
|
|
|
136
181
|
)
|
|
137
182
|
}
|
|
138
183
|
|
|
139
|
-
logger.info(`Staging app
|
|
184
|
+
logger.info(`Staging Windows app into ${outputPaths.appBundlePath}`)
|
|
140
185
|
|
|
141
186
|
if (dryRun) {
|
|
142
|
-
logger.info(`- copy ${
|
|
187
|
+
logger.info(`- copy ${runtimeSourceDir} -> ${outputPaths.appBundlePath}`)
|
|
188
|
+
logger.info(`- rename ${runtimeSourceBinary} -> ${outputPaths.appExecutablePath}`)
|
|
143
189
|
logger.info(`- copy ${configPath} -> ${outputPaths.bundledConfigPath}`)
|
|
144
190
|
logger.info(`- copy ${outputPaths.frontendDistDir} -> ${outputPaths.bundledFrontendDir}`)
|
|
145
191
|
return outputPaths
|
|
146
192
|
}
|
|
147
193
|
|
|
148
|
-
await mkdir(outputPaths.
|
|
194
|
+
await mkdir(outputPaths.windowsDir, { recursive: true })
|
|
149
195
|
await rm(outputPaths.appBundlePath, { recursive: true, force: true })
|
|
150
|
-
await
|
|
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
|
+
}
|
|
151
213
|
|
|
152
214
|
await mkdir(outputPaths.resourcesDir, { recursive: true })
|
|
153
215
|
await cp(configPath, outputPaths.bundledConfigPath, { force: true })
|
|
154
216
|
await rm(outputPaths.bundledFrontendDir, { recursive: true, force: true })
|
|
155
217
|
await cp(outputPaths.frontendDistDir, outputPaths.bundledFrontendDir, { recursive: true })
|
|
156
|
-
await writeFile(
|
|
157
|
-
path.join(outputPaths.appBundlePath, "Contents", "Info.plist"),
|
|
158
|
-
buildMacOSInfoPlist(config),
|
|
159
|
-
"utf8"
|
|
160
|
-
)
|
|
161
218
|
|
|
162
|
-
return outputPaths
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export async function stageWindowsAppBundle(frameworkBuildPaths, appPaths, config, options = {}) {
|
|
166
|
-
const { dryRun = false, outputPaths, configPath } = options
|
|
167
|
-
const runtimeSourceBinary = resolveFrameworkRuntimeBinary(frameworkBuildPaths, "release", {
|
|
168
|
-
mustExist: !dryRun
|
|
169
|
-
})
|
|
170
|
-
const runtimeSourceDir = resolveFrameworkRuntimeOutputDir(frameworkBuildPaths, "release", {
|
|
171
|
-
mustExist: !dryRun
|
|
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
|
-
}
|
|
219
|
+
return outputPaths
|
|
220
|
+
}
|
|
221
221
|
|
|
222
|
-
export async function createMacOSZipArchive(outputPaths, options = {}) {
|
|
223
|
-
const { dryRun = false } = options
|
|
222
|
+
export async function createMacOSZipArchive(outputPaths, options = {}) {
|
|
223
|
+
const { dryRun = false } = options
|
|
224
224
|
|
|
225
225
|
logger.info(`Packaging ${outputPaths.appBundlePath} -> ${outputPaths.zipPath}`)
|
|
226
226
|
|
|
@@ -232,37 +232,37 @@ export async function createMacOSZipArchive(outputPaths, options = {}) {
|
|
|
232
232
|
await mkdir(outputPaths.packagesDir, { recursive: true })
|
|
233
233
|
}
|
|
234
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
|
-
}
|
|
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
|
+
}
|
package/src/lib/process.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn, spawnSync } from "node:child_process"
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process"
|
|
2
2
|
|
|
3
3
|
import { logger } from "./logger.js"
|
|
4
4
|
|
|
@@ -44,32 +44,32 @@ export function getInstallCommandArgs(packageManager = "npm") {
|
|
|
44
44
|
return ["install"]
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export function formatCommand(command, args = []) {
|
|
48
|
-
return [command, ...args].join(" ")
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function shouldUseShell(command) {
|
|
52
|
-
return process.platform === "win32" && typeof command === "string" && /\.(cmd|bat)$/i.test(command)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function runCommand(command, args = [], options = {}) {
|
|
56
|
-
const { cwd, dryRun = false, env } = options
|
|
57
|
-
|
|
58
|
-
logger.info(`$ ${formatCommand(command, args)}`)
|
|
47
|
+
export function formatCommand(command, args = []) {
|
|
48
|
+
return [command, ...args].join(" ")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function shouldUseShell(command) {
|
|
52
|
+
return process.platform === "win32" && typeof command === "string" && /\.(cmd|bat)$/i.test(command)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function runCommand(command, args = [], options = {}) {
|
|
56
|
+
const { cwd, dryRun = false, env } = options
|
|
57
|
+
|
|
58
|
+
logger.info(`$ ${formatCommand(command, args)}`)
|
|
59
59
|
|
|
60
60
|
if (dryRun) {
|
|
61
61
|
return
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
await new Promise((resolve, reject) => {
|
|
65
|
-
const child = spawn(command, args, {
|
|
66
|
-
cwd,
|
|
67
|
-
env: env ? { ...process.env, ...env } : process.env,
|
|
68
|
-
stdio: "inherit",
|
|
69
|
-
shell: shouldUseShell(command)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
child.once("error", reject)
|
|
64
|
+
await new Promise((resolve, reject) => {
|
|
65
|
+
const child = spawn(command, args, {
|
|
66
|
+
cwd,
|
|
67
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
68
|
+
stdio: "inherit",
|
|
69
|
+
shell: shouldUseShell(command)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
child.once("error", reject)
|
|
73
73
|
child.once("exit", (code, signal) => {
|
|
74
74
|
if (code === 0) {
|
|
75
75
|
resolve(undefined)
|
|
@@ -92,13 +92,13 @@ export async function captureCommandOutput(command, args = [], options = {}) {
|
|
|
92
92
|
|
|
93
93
|
await Promise.resolve()
|
|
94
94
|
|
|
95
|
-
return await new Promise((resolve, reject) => {
|
|
96
|
-
const child = spawn(command, args, {
|
|
97
|
-
cwd,
|
|
98
|
-
env: env ? { ...process.env, ...env } : process.env,
|
|
99
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
100
|
-
shell: shouldUseShell(command)
|
|
101
|
-
})
|
|
95
|
+
return await new Promise((resolve, reject) => {
|
|
96
|
+
const child = spawn(command, args, {
|
|
97
|
+
cwd,
|
|
98
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
99
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
100
|
+
shell: shouldUseShell(command)
|
|
101
|
+
})
|
|
102
102
|
|
|
103
103
|
let stdout = ""
|
|
104
104
|
let stderr = ""
|
|
@@ -129,23 +129,23 @@ export async function captureCommandOutput(command, args = [], options = {}) {
|
|
|
129
129
|
})
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
export function spawnCommand(command, args = [], options = {}) {
|
|
133
|
-
const { cwd, dryRun = false, env } = options
|
|
134
|
-
|
|
135
|
-
logger.info(`$ ${formatCommand(command, args)}`)
|
|
132
|
+
export function spawnCommand(command, args = [], options = {}) {
|
|
133
|
+
const { cwd, dryRun = false, env } = options
|
|
134
|
+
|
|
135
|
+
logger.info(`$ ${formatCommand(command, args)}`)
|
|
136
136
|
|
|
137
137
|
if (dryRun) {
|
|
138
138
|
return null
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
return spawn(command, args, {
|
|
142
|
-
cwd,
|
|
143
|
-
env: env ? { ...process.env, ...env } : process.env,
|
|
144
|
-
detached: process.platform !== "win32",
|
|
145
|
-
stdio: "inherit",
|
|
146
|
-
shell: shouldUseShell(command)
|
|
147
|
-
})
|
|
148
|
-
}
|
|
141
|
+
return spawn(command, args, {
|
|
142
|
+
cwd,
|
|
143
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
144
|
+
detached: process.platform !== "win32",
|
|
145
|
+
stdio: "inherit",
|
|
146
|
+
shell: shouldUseShell(command)
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
149
|
|
|
150
150
|
export function waitForProcessExit(child, label) {
|
|
151
151
|
return new Promise((resolve, reject) => {
|
|
@@ -167,120 +167,120 @@ export function waitForProcessExit(child, label) {
|
|
|
167
167
|
})
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
export function registerChildCleanup(children) {
|
|
171
|
-
const activeChildren = children.filter(Boolean)
|
|
172
|
-
let cleanupPromise = null
|
|
173
|
-
|
|
174
|
-
const waitForChildExit = (child, timeoutMs = 3000) =>
|
|
175
|
-
new Promise((resolve) => {
|
|
176
|
-
if (!child || child.exitCode !== null || child.signalCode !== null) {
|
|
177
|
-
resolve(true)
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const handleExit = () => {
|
|
182
|
-
clearTimeout(timeout)
|
|
183
|
-
child.off("error", handleError)
|
|
184
|
-
resolve(true)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const handleError = () => {
|
|
188
|
-
clearTimeout(timeout)
|
|
189
|
-
child.off("exit", handleExit)
|
|
190
|
-
resolve(true)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const timeout = setTimeout(() => {
|
|
194
|
-
child.off("exit", handleExit)
|
|
195
|
-
child.off("error", handleError)
|
|
196
|
-
resolve(false)
|
|
197
|
-
}, timeoutMs)
|
|
198
|
-
|
|
199
|
-
child.once("exit", handleExit)
|
|
200
|
-
child.once("error", handleError)
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
const terminateChild = async (child) => {
|
|
204
|
-
if (!child || child.pid == null || child.exitCode !== null || child.signalCode !== null) {
|
|
205
|
-
return
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (process.platform === "win32") {
|
|
209
|
-
const result = spawnSync("taskkill.exe", ["/pid", String(child.pid), "/t", "/f"], {
|
|
210
|
-
stdio: "ignore",
|
|
211
|
-
windowsHide: true
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
if (result.status !== 0 && child.exitCode === null && child.signalCode === null) {
|
|
215
|
-
try {
|
|
216
|
-
child.kill("SIGTERM")
|
|
217
|
-
} catch {
|
|
218
|
-
// Ignore late shutdown races.
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
await waitForChildExit(child, 2000)
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
process.kill(-child.pid, "SIGTERM")
|
|
228
|
-
} catch (error) {
|
|
229
|
-
if (error?.code !== "ESRCH") {
|
|
230
|
-
try {
|
|
231
|
-
child.kill("SIGTERM")
|
|
232
|
-
} catch {
|
|
233
|
-
// Ignore late shutdown races.
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (await waitForChildExit(child, 3000)) {
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
try {
|
|
243
|
-
process.kill(-child.pid, "SIGKILL")
|
|
244
|
-
} catch (error) {
|
|
245
|
-
if (error?.code !== "ESRCH") {
|
|
246
|
-
try {
|
|
247
|
-
child.kill("SIGKILL")
|
|
248
|
-
} catch {
|
|
249
|
-
// Ignore late shutdown races.
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
await waitForChildExit(child, 1000)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const cleanup = async () => {
|
|
258
|
-
if (cleanupPromise) {
|
|
259
|
-
return cleanupPromise
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
cleanupPromise = (async () => {
|
|
263
|
-
await Promise.all(activeChildren.map((child) => terminateChild(child)))
|
|
264
|
-
})()
|
|
265
|
-
|
|
266
|
-
return cleanupPromise
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const handleSignal = () => {
|
|
270
|
-
void cleanup().finally(() => {
|
|
271
|
-
process.exit(0)
|
|
272
|
-
})
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
process.on("SIGINT", handleSignal)
|
|
276
|
-
process.on("SIGTERM", handleSignal)
|
|
277
|
-
|
|
278
|
-
return async () => {
|
|
279
|
-
process.off("SIGINT", handleSignal)
|
|
280
|
-
process.off("SIGTERM", handleSignal)
|
|
281
|
-
await cleanup()
|
|
282
|
-
}
|
|
283
|
-
}
|
|
170
|
+
export function registerChildCleanup(children) {
|
|
171
|
+
const activeChildren = children.filter(Boolean)
|
|
172
|
+
let cleanupPromise = null
|
|
173
|
+
|
|
174
|
+
const waitForChildExit = (child, timeoutMs = 3000) =>
|
|
175
|
+
new Promise((resolve) => {
|
|
176
|
+
if (!child || child.exitCode !== null || child.signalCode !== null) {
|
|
177
|
+
resolve(true)
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const handleExit = () => {
|
|
182
|
+
clearTimeout(timeout)
|
|
183
|
+
child.off("error", handleError)
|
|
184
|
+
resolve(true)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const handleError = () => {
|
|
188
|
+
clearTimeout(timeout)
|
|
189
|
+
child.off("exit", handleExit)
|
|
190
|
+
resolve(true)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const timeout = setTimeout(() => {
|
|
194
|
+
child.off("exit", handleExit)
|
|
195
|
+
child.off("error", handleError)
|
|
196
|
+
resolve(false)
|
|
197
|
+
}, timeoutMs)
|
|
198
|
+
|
|
199
|
+
child.once("exit", handleExit)
|
|
200
|
+
child.once("error", handleError)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
const terminateChild = async (child) => {
|
|
204
|
+
if (!child || child.pid == null || child.exitCode !== null || child.signalCode !== null) {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (process.platform === "win32") {
|
|
209
|
+
const result = spawnSync("taskkill.exe", ["/pid", String(child.pid), "/t", "/f"], {
|
|
210
|
+
stdio: "ignore",
|
|
211
|
+
windowsHide: true
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
if (result.status !== 0 && child.exitCode === null && child.signalCode === null) {
|
|
215
|
+
try {
|
|
216
|
+
child.kill("SIGTERM")
|
|
217
|
+
} catch {
|
|
218
|
+
// Ignore late shutdown races.
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
await waitForChildExit(child, 2000)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
process.kill(-child.pid, "SIGTERM")
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (error?.code !== "ESRCH") {
|
|
230
|
+
try {
|
|
231
|
+
child.kill("SIGTERM")
|
|
232
|
+
} catch {
|
|
233
|
+
// Ignore late shutdown races.
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (await waitForChildExit(child, 3000)) {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
process.kill(-child.pid, "SIGKILL")
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (error?.code !== "ESRCH") {
|
|
246
|
+
try {
|
|
247
|
+
child.kill("SIGKILL")
|
|
248
|
+
} catch {
|
|
249
|
+
// Ignore late shutdown races.
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await waitForChildExit(child, 1000)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const cleanup = async () => {
|
|
258
|
+
if (cleanupPromise) {
|
|
259
|
+
return cleanupPromise
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
cleanupPromise = (async () => {
|
|
263
|
+
await Promise.all(activeChildren.map((child) => terminateChild(child)))
|
|
264
|
+
})()
|
|
265
|
+
|
|
266
|
+
return cleanupPromise
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const handleSignal = () => {
|
|
270
|
+
void cleanup().finally(() => {
|
|
271
|
+
process.exit(0)
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
process.on("SIGINT", handleSignal)
|
|
276
|
+
process.on("SIGTERM", handleSignal)
|
|
277
|
+
|
|
278
|
+
return async () => {
|
|
279
|
+
process.off("SIGINT", handleSignal)
|
|
280
|
+
process.off("SIGTERM", handleSignal)
|
|
281
|
+
await cleanup()
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
284
|
|
|
285
285
|
export async function waitForUrl(url, options = {}) {
|
|
286
286
|
const { intervalMs = 250, timeoutMs = 15000 } = options
|