On Windows, child_process.spawn() and execFile() open a visible console window by default. The web server spawn, RPC bridge, browser opener, and all 15 web service subprocess calls were missing windowsHide: true, causing constant console window flashing when running gsd --web. Closes #2628 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
97 lines
3.6 KiB
TypeScript
97 lines
3.6 KiB
TypeScript
import { execFile } from "node:child_process"
|
|
import { existsSync } from "node:fs"
|
|
import { join } from "node:path"
|
|
import { pathToFileURL } from "node:url"
|
|
|
|
import { resolveBridgeRuntimeConfig } from "./bridge-service.ts"
|
|
import { resolveTypeStrippingFlag, resolveSubprocessModule, buildSubprocessPrefixArgs } from "./ts-subprocess-flags.ts"
|
|
import type { ExportResult } from "../../web/lib/remaining-command-types.ts"
|
|
|
|
const EXPORT_MAX_BUFFER = 4 * 1024 * 1024
|
|
const EXPORT_MODULE_ENV = "GSD_EXPORT_MODULE"
|
|
|
|
function resolveTsLoaderPath(packageRoot: string): string {
|
|
return join(packageRoot, "src", "resources", "extensions", "gsd", "tests", "resolve-ts.mjs")
|
|
}
|
|
|
|
/**
|
|
* Generates an export file via a child process and returns its content.
|
|
* The child calls writeExportFile() which creates a timestamped file in .gsd/,
|
|
* then reads its content back for browser display.
|
|
*/
|
|
export async function collectExportData(
|
|
format: "markdown" | "json" = "markdown",
|
|
projectCwdOverride?: string,
|
|
): Promise<ExportResult> {
|
|
const config = resolveBridgeRuntimeConfig(undefined, projectCwdOverride)
|
|
const { packageRoot, projectCwd } = config
|
|
|
|
const resolveTsLoader = resolveTsLoaderPath(packageRoot)
|
|
const moduleResolution = resolveSubprocessModule(packageRoot, "resources/extensions/gsd/export.ts")
|
|
const exportModulePath = moduleResolution.modulePath
|
|
|
|
if (!moduleResolution.useCompiledJs && (!existsSync(resolveTsLoader) || !existsSync(exportModulePath))) {
|
|
throw new Error(
|
|
`export data provider not found; checked=${resolveTsLoader},${exportModulePath}`,
|
|
)
|
|
}
|
|
if (moduleResolution.useCompiledJs && !existsSync(exportModulePath)) {
|
|
throw new Error(`export data provider not found; checked=${exportModulePath}`)
|
|
}
|
|
|
|
const script = [
|
|
'const { pathToFileURL } = await import("node:url");',
|
|
`const mod = await import(pathToFileURL(process.env.${EXPORT_MODULE_ENV}).href);`,
|
|
'const format = process.env.GSD_EXPORT_FORMAT || "markdown";',
|
|
'const basePath = process.env.GSD_EXPORT_BASE;',
|
|
'const filePath = mod.writeExportFile(basePath, format);',
|
|
'if (filePath) {',
|
|
' const { readFileSync } = await import("node:fs");',
|
|
' const { basename } = await import("node:path");',
|
|
' const content = readFileSync(filePath, "utf-8");',
|
|
' process.stdout.write(JSON.stringify({ content, format, filename: basename(filePath) }));',
|
|
'} else {',
|
|
' process.stdout.write(JSON.stringify({ content: "No metrics data available for export.", format, filename: "export." + (format === "json" ? "json" : "md") }));',
|
|
'}',
|
|
].join(" ")
|
|
|
|
const prefixArgs = buildSubprocessPrefixArgs(packageRoot, moduleResolution, pathToFileURL(resolveTsLoader).href)
|
|
|
|
return await new Promise<ExportResult>((resolveResult, reject) => {
|
|
execFile(
|
|
process.execPath,
|
|
[
|
|
...prefixArgs,
|
|
"--eval",
|
|
script,
|
|
],
|
|
{
|
|
cwd: packageRoot,
|
|
env: {
|
|
...process.env,
|
|
[EXPORT_MODULE_ENV]: exportModulePath,
|
|
GSD_EXPORT_BASE: projectCwd,
|
|
GSD_EXPORT_FORMAT: format,
|
|
},
|
|
maxBuffer: EXPORT_MAX_BUFFER,
|
|
windowsHide: true,
|
|
},
|
|
(error, stdout, stderr) => {
|
|
if (error) {
|
|
reject(new Error(`export data subprocess failed: ${stderr || error.message}`))
|
|
return
|
|
}
|
|
|
|
try {
|
|
resolveResult(JSON.parse(stdout) as ExportResult)
|
|
} catch (parseError) {
|
|
reject(
|
|
new Error(
|
|
`export data subprocess returned invalid JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
|
|
),
|
|
)
|
|
}
|
|
},
|
|
)
|
|
})
|
|
}
|