
























@@ -932,6 +932,92 @@ export function parseConfigJson5(
932932}
933933}
934934935+function findJsonRootSuffix(
936+raw: string,
937+json5: { parse: (value: string) => unknown } = JSON5,
938+): { raw: string; parsed: unknown } | null {
939+if (/^\s*(?:\{|\[)/.test(raw)) {
940+return null;
941+}
942+let offset = 0;
943+while (offset < raw.length) {
944+const nextNewline = raw.indexOf("\n", offset);
945+const lineEnd = nextNewline === -1 ? raw.length : nextNewline + 1;
946+const line = raw.slice(offset, lineEnd);
947+if (/^\s*(?:\{|\[)/.test(line)) {
948+const candidate = raw.slice(offset);
949+const parsed = parseConfigJson5(candidate, json5);
950+return parsed.ok ? { raw: candidate, parsed: parsed.parsed } : null;
951+}
952+offset = lineEnd;
953+}
954+return null;
955+}
956+957+async function persistPrefixedConfigRecovery(params: {
958+deps: Required<ConfigIoDeps>;
959+configPath: string;
960+originalRaw: string;
961+recoveredRaw: string;
962+}): Promise<void> {
963+const observedAt = new Date().toISOString();
964+const clobberedPath = await persistClobberedConfigSnapshot({
965+deps: params.deps,
966+configPath: params.configPath,
967+raw: params.originalRaw,
968+ observedAt,
969+});
970+await params.deps.fs.promises.writeFile(params.configPath, params.recoveredRaw, {
971+encoding: "utf-8",
972+mode: 0o600,
973+});
974+await params.deps.fs.promises.chmod?.(params.configPath, 0o600).catch(() => {});
975+params.deps.logger.warn(
976+`Config auto-stripped non-JSON prefix: ${params.configPath}` +
977+(clobberedPath ? ` (original saved as ${clobberedPath})` : ""),
978+);
979+}
980+981+async function recoverConfigFromJsonRootSuffixWithDeps(params: {
982+deps: Required<ConfigIoDeps>;
983+configPath: string;
984+snapshot: ConfigFileSnapshot;
985+}): Promise<boolean> {
986+if (!params.snapshot.exists || params.snapshot.valid || typeof params.snapshot.raw !== "string") {
987+return false;
988+}
989+const suffixRecovery = findJsonRootSuffix(params.snapshot.raw, params.deps.json5);
990+if (!suffixRecovery) {
991+return false;
992+}
993+994+let resolved: unknown;
995+try {
996+resolved = resolveConfigIncludesForRead(suffixRecovery.parsed, params.configPath, params.deps);
997+} catch {
998+return false;
999+}
1000+const readResolution = resolveConfigForRead(resolved, params.deps.env);
1001+const legacyResolution = resolveLegacyConfigForRead(
1002+readResolution.resolvedConfigRaw,
1003+suffixRecovery.parsed,
1004+);
1005+const validated = validateConfigObjectWithPlugins(legacyResolution.effectiveConfigRaw, {
1006+env: params.deps.env,
1007+});
1008+if (!validated.ok) {
1009+return false;
1010+}
1011+1012+await persistPrefixedConfigRecovery({
1013+deps: params.deps,
1014+configPath: params.configPath,
1015+originalRaw: params.snapshot.raw,
1016+recoveredRaw: suffixRecovery.raw,
1017+});
1018+return true;
1019+}
1020+9351021type ConfigReadResolution = {
9361022resolvedConfigRaw: unknown;
9371023envSnapshotForRestore: Record<string, string | undefined>;
@@ -1446,6 +1532,14 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
14461532});
14471533}
144815341535+async function recoverConfigFromJsonRootSuffix(snapshot: ConfigFileSnapshot): Promise<boolean> {
1536+return await recoverConfigFromJsonRootSuffixWithDeps({
1537+ deps,
1538+ configPath,
1539+ snapshot,
1540+});
1541+}
1542+14491543async function readConfigFileSnapshotForWrite(): Promise<ReadConfigFileSnapshotForWriteResult> {
14501544const result = await readConfigFileSnapshotInternal();
14511545return {
@@ -1793,6 +1887,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
17931887 readConfigFileSnapshotForWrite,
17941888 promoteConfigSnapshotToLastKnownGood,
17951889 recoverConfigFromLastKnownGood,
1890+ recoverConfigFromJsonRootSuffix,
17961891 writeConfigFile,
17971892};
17981893}
@@ -1906,6 +2001,12 @@ export async function recoverConfigFromLastKnownGood(params: {
19062001return await createConfigIO().recoverConfigFromLastKnownGood(params);
19072002}
190820032004+export async function recoverConfigFromJsonRootSuffix(
2005+snapshot: ConfigFileSnapshot,
2006+): Promise<boolean> {
2007+return await createConfigIO().recoverConfigFromJsonRootSuffix(snapshot);
2008+}
2009+19092010export async function readSourceConfigSnapshot(): Promise<ConfigFileSnapshot> {
19102011return await readConfigFileSnapshot();
19112012}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。