























@@ -0,0 +1,188 @@
1+import { spawnSync } from "node:child_process";
2+import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
3+4+const WINDOWS_CODEPAGE_ENCODING_MAP: Record<number, string> = {
5+65001: "utf-8",
6+54936: "gb18030",
7+936: "gbk",
8+950: "big5",
9+932: "shift_jis",
10+949: "euc-kr",
11+1252: "windows-1252",
12+};
13+14+let cachedWindowsConsoleEncoding: string | null | undefined;
15+16+export function parseWindowsCodePage(raw: string): number | null {
17+if (!raw) {
18+return null;
19+}
20+const match = raw.match(/\b(\d{3,5})\b/);
21+if (!match?.[1]) {
22+return null;
23+}
24+const codePage = Number.parseInt(match[1], 10);
25+if (!Number.isFinite(codePage) || codePage <= 0) {
26+return null;
27+}
28+return codePage;
29+}
30+31+export function resolveWindowsConsoleEncoding(): string | null {
32+if (process.platform !== "win32") {
33+return null;
34+}
35+if (cachedWindowsConsoleEncoding !== undefined) {
36+return cachedWindowsConsoleEncoding;
37+}
38+try {
39+const result = spawnSync("cmd.exe", ["/d", "/s", "/c", "chcp"], {
40+windowsHide: true,
41+encoding: "utf8",
42+stdio: ["ignore", "pipe", "pipe"],
43+});
44+const raw = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
45+const codePage = parseWindowsCodePage(raw);
46+cachedWindowsConsoleEncoding =
47+codePage !== null ? (WINDOWS_CODEPAGE_ENCODING_MAP[codePage] ?? null) : null;
48+} catch {
49+cachedWindowsConsoleEncoding = null;
50+}
51+return cachedWindowsConsoleEncoding;
52+}
53+54+export function decodeWindowsOutputBuffer(params: {
55+buffer: Buffer;
56+platform?: NodeJS.Platform;
57+windowsEncoding?: string | null;
58+}): string {
59+const platform = params.platform ?? process.platform;
60+if (platform !== "win32") {
61+return params.buffer.toString("utf8");
62+}
63+64+const utf8 = decodeStrictUtf8(params.buffer);
65+if (utf8 !== null) {
66+return utf8;
67+}
68+69+const encoding = params.windowsEncoding ?? resolveWindowsConsoleEncoding();
70+if (!encoding || normalizeLowercaseStringOrEmpty(encoding) === "utf-8") {
71+return params.buffer.toString("utf8");
72+}
73+try {
74+return new TextDecoder(encoding).decode(params.buffer);
75+} catch {
76+return params.buffer.toString("utf8");
77+}
78+}
79+80+export function createWindowsOutputDecoder(params?: {
81+platform?: NodeJS.Platform;
82+windowsEncoding?: string | null;
83+}): {
84+decode(chunk: Buffer | string): string;
85+flush(): string;
86+} {
87+const platform = params?.platform ?? process.platform;
88+const encoding =
89+platform === "win32" ? (params?.windowsEncoding ?? resolveWindowsConsoleEncoding()) : null;
90+const normalizedEncoding = normalizeLowercaseStringOrEmpty(encoding);
91+const legacyDecoder =
92+platform === "win32" && encoding && normalizedEncoding !== "utf-8"
93+ ? new TextDecoder(encoding)
94+ : null;
95+const utf8Decoder =
96+platform === "win32" && legacyDecoder ? new TextDecoder("utf-8", { fatal: true }) : null;
97+let useLegacyDecoder = false;
98+let pendingUtf8Bytes = Buffer.alloc(0);
99+100+return {
101+decode(chunk) {
102+const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
103+if (!legacyDecoder || !utf8Decoder) {
104+return buffer.toString("utf8");
105+}
106+if (useLegacyDecoder) {
107+return legacyDecoder.decode(buffer, { stream: true });
108+}
109+const replayBuffer =
110+pendingUtf8Bytes.length > 0 ? Buffer.concat([pendingUtf8Bytes, buffer]) : buffer;
111+try {
112+const decoded = utf8Decoder.decode(buffer, { stream: true });
113+pendingUtf8Bytes = Buffer.from(getTrailingIncompleteUtf8Bytes(replayBuffer));
114+return decoded;
115+} catch {
116+useLegacyDecoder = true;
117+pendingUtf8Bytes = Buffer.alloc(0);
118+return legacyDecoder.decode(replayBuffer, { stream: true });
119+}
120+},
121+flush() {
122+if (!legacyDecoder || !utf8Decoder) {
123+return "";
124+}
125+if (useLegacyDecoder) {
126+return legacyDecoder.decode();
127+}
128+try {
129+const decoded = utf8Decoder.decode();
130+pendingUtf8Bytes = Buffer.alloc(0);
131+return decoded;
132+} catch {
133+useLegacyDecoder = true;
134+const replayBuffer = pendingUtf8Bytes;
135+pendingUtf8Bytes = Buffer.alloc(0);
136+return replayBuffer.length > 0 ? legacyDecoder.decode(replayBuffer) : "";
137+}
138+},
139+};
140+}
141+142+function getTrailingIncompleteUtf8Bytes(buffer: Buffer): Buffer {
143+let index = buffer.length - 1;
144+let continuationBytes = 0;
145+while (
146+index >= 0 &&
147+buffer[index] !== undefined &&
148+buffer[index] >= 0x80 &&
149+buffer[index] <= 0xbf &&
150+continuationBytes < 3
151+) {
152+continuationBytes += 1;
153+index -= 1;
154+}
155+if (index < 0) {
156+return buffer;
157+}
158+159+const leadByte = buffer[index];
160+const sequenceLength = getUtf8SequenceLength(leadByte);
161+if (sequenceLength <= 1) {
162+return Buffer.alloc(0);
163+}
164+165+const availableBytes = continuationBytes + 1;
166+return availableBytes < sequenceLength ? buffer.subarray(index) : Buffer.alloc(0);
167+}
168+169+function getUtf8SequenceLength(byte: number): number {
170+if (byte >= 0xc2 && byte <= 0xdf) {
171+return 2;
172+}
173+if (byte >= 0xe0 && byte <= 0xef) {
174+return 3;
175+}
176+if (byte >= 0xf0 && byte <= 0xf4) {
177+return 4;
178+}
179+return 1;
180+}
181+182+function decodeStrictUtf8(buffer: Buffer): string | null {
183+try {
184+return new TextDecoder("utf-8", { fatal: true }).decode(buffer);
185+} catch {
186+return null;
187+}
188+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。