mirror of
https://github.com/zhu1090093659/dsh-web.git
synced 2026-09-28 14:24:03 +08:00
Display and source layers rename to dsh-web: GitHub slug, docs and prose, aggregate package dir packages/dsh-web-all with npm name @linxin666/dsh-web-all, settings package dir packages/dsh-web-settings, private shared package dsh-web-shared, repo-local skill dirs, and the docs banner asset. Runtime, wire, and storage identifiers are frozen byte-identical so installed profiles keep resolving with zero migration: web-ui-* bundle ids, the dsh-web-ui-market settings section id, /api/dsh-web-ui-settings and its proxy-token header, and the dsh-web-ui-telemetry-* storage keys. Frozen history (docs/archive, docs/release-notes, archived notes), the JAVA-LW fork reference, and local filesystem paths keep the old name. npm migration: the next tag release dual-publishes @linxin666/dsh-web-all alongside the final @linxin666/dsh-web-ui-all version, then the old name is deprecated with a pointer; dual-publish lasts two releases. Decision record: .agents/notes/implemented/architecture/2026-08-24-product-rename-dsh-web.md
124 lines
4.4 KiB
TypeScript
124 lines
4.4 KiB
TypeScript
/**
|
|
* Shared host git subprocess plumbing: the run result shape, the runner seam,
|
|
* the collected-output cap, and the production runner over the subprocess
|
|
* service. Packages receive this file as a generated copy via
|
|
* scripts/sync-shared.mjs; edit the shared source and re-run the sync instead
|
|
* of editing a copy.
|
|
*
|
|
* The context shape is declared structurally so this module stays
|
|
* self-contained (shared/ has no cordis dependency): any context whose
|
|
* `subprocess` satisfies SubprocessServiceLike works, which the plugin
|
|
* contexts do.
|
|
* @module dsh-web-shared/host/git-runner
|
|
*/
|
|
|
|
/** One finished git invocation. */
|
|
export interface GitRunResult {
|
|
exitCode: number | null
|
|
stdout: string
|
|
stderr: string
|
|
}
|
|
|
|
/** The spawn seam the service runs git through (subprocess service in production). */
|
|
export interface GitRunner {
|
|
run(argv: readonly string[], cwd: string, signal?: AbortSignal): Promise<GitRunResult>
|
|
}
|
|
|
|
/** Collected-output cap for one git command. */
|
|
export const OUTPUT_CAP_BYTES = 1 << 20
|
|
|
|
/** The subprocess service surface this runner consumes (structural). */
|
|
export interface SubprocessServiceLike {
|
|
spawn(spec: {
|
|
argv: readonly string[]
|
|
cwd: string
|
|
stdio: {
|
|
stdin: 'ignore'
|
|
stdout: { maxBytes: number }
|
|
stderr: { maxBytes: number }
|
|
}
|
|
graceMs: number
|
|
signal?: AbortSignal
|
|
}): {
|
|
done: Promise<{ exitCode: number | null }>
|
|
collected: {
|
|
stdout?: { readFrom(offset: number): { text: string } }
|
|
stderr?: { readFrom(offset: number): { text: string } }
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Per-package knobs for the shared production runner. */
|
|
export interface GitRunnerOptions {
|
|
/** Build the full spawn argv from the git args (default ['git', ...argv]). */
|
|
spawnArgv?: (argv: readonly string[]) => readonly string[]
|
|
/** degrade turns spawn/run failures into exitCode 127 results instead of throwing. */
|
|
failureMode?: 'throw' | 'degrade'
|
|
/** console.error tag for degrade mode. */
|
|
errorTag?: string
|
|
}
|
|
|
|
/**
|
|
* Production runner over the subprocess service: one managed child per
|
|
* command, bounded collect on both streams. A caller-owned AbortSignal reaches
|
|
* the subprocess tree and remains authoritative even in degrade mode. Degrade
|
|
* mode keeps the SCM tab showing the friendly "not a git repository" state
|
|
* instead of a bare 400 when git is missing or the subprocess service fails.
|
|
* @param ctx - context carrying the subprocess service.
|
|
* @param options - per-package behavior knobs.
|
|
* @returns the runner.
|
|
*/
|
|
export function subprocessRunner(ctx: { subprocess: SubprocessServiceLike }, options: GitRunnerOptions = {}): GitRunner {
|
|
const spawnArgv = options.spawnArgv ?? ((argv) => ['git', ...argv])
|
|
const degrade = options.failureMode === 'degrade'
|
|
const errorTag = options.errorTag ?? 'git'
|
|
const failure = (prefix: string, error: unknown): GitRunResult => ({
|
|
exitCode: 127,
|
|
stdout: '',
|
|
stderr: prefix + (error instanceof Error ? error.message : String(error)),
|
|
})
|
|
return {
|
|
async run(argv, cwd, signal) {
|
|
signal?.throwIfAborted()
|
|
const spec = {
|
|
argv: spawnArgv(argv),
|
|
cwd,
|
|
stdio: {
|
|
stdin: 'ignore' as const,
|
|
stdout: { maxBytes: OUTPUT_CAP_BYTES },
|
|
stderr: { maxBytes: OUTPUT_CAP_BYTES },
|
|
},
|
|
graceMs: 10_000,
|
|
signal,
|
|
}
|
|
if (degrade) {
|
|
let handle
|
|
try {
|
|
handle = ctx.subprocess.spawn(spec)
|
|
} catch (error) {
|
|
signal?.throwIfAborted()
|
|
console.error('[' + errorTag + '] git spawn failed:', error)
|
|
return failure('git: spawn failed: ', error)
|
|
}
|
|
try {
|
|
const outcome = await handle.done
|
|
signal?.throwIfAborted()
|
|
const stdout = handle.collected.stdout?.readFrom(0).text ?? ''
|
|
const stderr = handle.collected.stderr?.readFrom(0).text ?? ''
|
|
return { exitCode: outcome.exitCode, stdout, stderr }
|
|
} catch (error) {
|
|
signal?.throwIfAborted()
|
|
console.error('[' + errorTag + '] git run failed:', error)
|
|
return failure('git: run failed: ', error)
|
|
}
|
|
}
|
|
const handle = ctx.subprocess.spawn(spec)
|
|
const outcome = await handle.done
|
|
signal?.throwIfAborted()
|
|
const stdout = handle.collected.stdout?.readFrom(0).text ?? ''
|
|
const stderr = handle.collected.stderr?.readFrom(0).text ?? ''
|
|
return { exitCode: outcome.exitCode, stdout, stderr }
|
|
},
|
|
}
|
|
}
|