reset-framework-cli 1.1.4 → 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 +52 -40
- package/src/commands/doctor.js +83 -35
- package/src/commands/init.js +26 -96
- 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 +134 -44
- 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 } 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 = ""
|
|
@@ -138,13 +138,14 @@ export function spawnCommand(command, args = [], options = {}) {
|
|
|
138
138
|
return null
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
return spawn(command, args, {
|
|
142
|
-
cwd,
|
|
143
|
-
env: env ? { ...process.env, ...env } : process.env,
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
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
|
+
}
|
|
148
149
|
|
|
149
150
|
export function waitForProcessExit(child, label) {
|
|
150
151
|
return new Promise((resolve, reject) => {
|
|
@@ -168,27 +169,116 @@ export function waitForProcessExit(child, label) {
|
|
|
168
169
|
|
|
169
170
|
export function registerChildCleanup(children) {
|
|
170
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
|
+
}
|
|
171
180
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
181
|
+
const handleExit = () => {
|
|
182
|
+
clearTimeout(timeout)
|
|
183
|
+
child.off("error", handleError)
|
|
184
|
+
resolve(true)
|
|
176
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
|
|
177
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
|
|
178
267
|
}
|
|
179
268
|
|
|
180
269
|
const handleSignal = () => {
|
|
181
|
-
cleanup()
|
|
182
|
-
|
|
270
|
+
void cleanup().finally(() => {
|
|
271
|
+
process.exit(0)
|
|
272
|
+
})
|
|
183
273
|
}
|
|
184
274
|
|
|
185
275
|
process.on("SIGINT", handleSignal)
|
|
186
276
|
process.on("SIGTERM", handleSignal)
|
|
187
277
|
|
|
188
|
-
return () => {
|
|
278
|
+
return async () => {
|
|
189
279
|
process.off("SIGINT", handleSignal)
|
|
190
280
|
process.off("SIGTERM", handleSignal)
|
|
191
|
-
cleanup()
|
|
281
|
+
await cleanup()
|
|
192
282
|
}
|
|
193
283
|
}
|
|
194
284
|
|