




















@@ -136,6 +136,12 @@ type ShippedPluginInstallConfigWriteMigration =
136136};
137137};
138138139+type ShippedPluginInstallConfigReadMigration = {
140+config: unknown;
141+persistedRootParsed?: unknown;
142+persistedRootRaw?: string;
143+};
144+139145const CONFIG_HEALTH_STATE_FILENAME = "config-health.json";
140146const loggedInvalidConfigs = new Set<string>();
141147@@ -1228,21 +1234,104 @@ export function createConfigIO(
12281234return applyConfigOverrides(cfgWithOwnerDisplaySecret);
12291235}
123012361237+function captureFileSnapshotSync(filePath: string):
1238+| {
1239+existed: false;
1240+}
1241+| {
1242+existed: true;
1243+raw: string;
1244+} {
1245+return deps.fs.existsSync(filePath)
1246+ ? ({
1247+existed: true,
1248+raw: deps.fs.readFileSync(filePath, "utf-8"),
1249+} as const)
1250+ : ({ existed: false } as const);
1251+}
1252+1253+function restoreFileSnapshotSync(
1254+filePath: string,
1255+previousFile:
1256+| {
1257+existed: false;
1258+}
1259+| {
1260+existed: true;
1261+raw: string;
1262+},
1263+): void {
1264+if (previousFile.existed) {
1265+deps.fs.writeFileSync(filePath, previousFile.raw, {
1266+encoding: "utf-8",
1267+mode: 0o600,
1268+});
1269+return;
1270+}
1271+try {
1272+deps.fs.unlinkSync(filePath);
1273+} catch (err) {
1274+if ((err as NodeJS.ErrnoException)?.code !== "ENOENT") {
1275+throw err;
1276+}
1277+}
1278+}
1279+1280+function replaceConfigFileSync(raw: string): void {
1281+const dir = path.dirname(configPath);
1282+deps.fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
1283+const tmp = path.join(
1284+dir,
1285+`${path.basename(configPath)}.${process.pid}.${crypto.randomUUID()}.tmp`,
1286+);
1287+try {
1288+deps.fs.writeFileSync(tmp, raw, {
1289+encoding: "utf-8",
1290+mode: 0o600,
1291+});
1292+try {
1293+deps.fs.renameSync(tmp, configPath);
1294+} catch (err) {
1295+const code = (err as NodeJS.ErrnoException)?.code;
1296+if (code !== "EPERM" && code !== "EEXIST") {
1297+throw err;
1298+}
1299+deps.fs.copyFileSync(tmp, configPath);
1300+deps.fs.chmodSync(configPath, 0o600);
1301+deps.fs.unlinkSync(tmp);
1302+}
1303+} catch (err) {
1304+try {
1305+deps.fs.unlinkSync(tmp);
1306+} catch (cleanupErr) {
1307+if ((cleanupErr as NodeJS.ErrnoException)?.code !== "ENOENT") {
1308+deps.logger.warn(`Failed to clean temporary config file ${tmp}: ${String(cleanupErr)}`);
1309+}
1310+}
1311+throw err;
1312+}
1313+}
1314+12311315function migrateAndStripShippedPluginInstallConfigRecords(
12321316configRaw: unknown,
1233-options: { persist?: boolean } = {},
1234-): unknown {
1317+options: { persist?: boolean; rootConfigRaw?: unknown } = {},
1318+): ShippedPluginInstallConfigReadMigration {
12351319const installRecords = extractShippedPluginInstallConfigRecords(configRaw);
12361320const stripped = stripShippedPluginInstallConfigRecords(configRaw);
12371321if (Object.keys(installRecords).length === 0) {
1238-return stripped;
1322+return { config: stripped };
12391323}
12401324if (options.persist === false) {
1241-return stripped;
1325+return { config: stripped };
12421326}
1243132712441328try {
12451329const stateDir = resolveStateDir(deps.env, deps.homedir);
1330+const filePath = resolveInstalledPluginIndexRecordsStorePath({
1331+env: deps.env,
1332+ stateDir,
1333+});
1334+const previousFile = captureFileSnapshotSync(filePath);
12461335const existingRecords = loadInstalledPluginIndexInstallRecordsSync({
12471336env: deps.env,
12481337 stateDir,
@@ -1258,16 +1347,33 @@ export function createConfigIO(
12581347 stateDir,
12591348});
12601349}
1350+const rootConfigRaw = options.rootConfigRaw;
1351+if (
1352+rootConfigRaw !== undefined &&
1353+Object.keys(extractShippedPluginInstallConfigRecords(rootConfigRaw)).length > 0
1354+) {
1355+const persistedRootParsed = stripShippedPluginInstallConfigRecords(rootConfigRaw);
1356+const persistedRootRaw = JSON.stringify(persistedRootParsed, null, 2)
1357+.trimEnd()
1358+.concat("\n");
1359+try {
1360+replaceConfigFileSync(persistedRootRaw);
1361+} catch (err) {
1362+restoreFileSnapshotSync(filePath, previousFile);
1363+throw err;
1364+}
1365+return { config: stripped, persistedRootParsed, persistedRootRaw };
1366+}
12611367} catch (err) {
12621368deps.logger.warn(
12631369`Config (${configPath}): could not migrate shipped plugins.installs records into the plugin index: ${formatErrorMessage(
12641370 err,
12651371 )}`,
12661372);
1267-return configRaw;
1373+return { config: configRaw };
12681374}
126913751270-return stripped;
1376+return { config: stripped };
12711377}
1272137812731379function ensureShippedPluginInstallConfigRecordsMigratedForWrite(
@@ -1374,16 +1480,20 @@ export function createConfigIO(
13741480});
13751481const effectiveRaw = recovered.raw;
13761482const effectiveParsed = recovered.parsed;
1377-const hash = hashConfigRaw(effectiveRaw);
13781483const readResolution = resolveConfigForRead(
13791484resolveConfigIncludesForRead(effectiveParsed, configPath, deps),
13801485deps.env,
13811486);
13821487const resolvedConfig = readResolution.resolvedConfigRaw;
13831488const legacyResolution = resolveLegacyConfigForRead(resolvedConfig, effectiveParsed);
1384-const effectiveConfigRaw = migrateAndStripShippedPluginInstallConfigRecords(
1489+const installMigration = migrateAndStripShippedPluginInstallConfigRecords(
13851490legacyResolution.effectiveConfigRaw,
1491+{ rootConfigRaw: effectiveParsed },
13861492);
1493+const effectiveConfigRaw = installMigration.config;
1494+const snapshotRaw = installMigration.persistedRootRaw ?? effectiveRaw;
1495+const snapshotParsed = installMigration.persistedRootParsed ?? effectiveParsed;
1496+const hash = hashConfigRaw(snapshotRaw);
13871497for (const w of readResolution.envWarnings) {
13881498deps.logger.warn(
13891499`Config (${configPath}): missing env var "${w.varName}" at ${w.configPath} - feature using this value will be unavailable`,
@@ -1395,8 +1505,8 @@ export function createConfigIO(
13951505 ...createConfigFileSnapshot({
13961506path: configPath,
13971507exists: true,
1398-raw: effectiveRaw,
1399-parsed: effectiveParsed,
1508+raw: snapshotRaw,
1509+parsed: snapshotParsed,
14001510sourceConfig: {},
14011511valid: true,
14021512runtimeConfig: {},
@@ -1424,8 +1534,8 @@ export function createConfigIO(
14241534 ...createConfigFileSnapshot({
14251535path: configPath,
14261536exists: true,
1427-raw: effectiveRaw,
1428-parsed: effectiveParsed,
1537+raw: snapshotRaw,
1538+parsed: snapshotParsed,
14291539sourceConfig: coerceConfig(effectiveConfigRaw),
14301540valid: false,
14311541runtimeConfig: coerceConfig(effectiveConfigRaw),
@@ -1457,8 +1567,8 @@ export function createConfigIO(
14571567 ...createConfigFileSnapshot({
14581568path: configPath,
14591569exists: true,
1460-raw: effectiveRaw,
1461-parsed: effectiveParsed,
1570+raw: snapshotRaw,
1571+parsed: snapshotParsed,
14621572sourceConfig: coerceConfig(effectiveConfigRaw),
14631573valid: true,
14641574runtimeConfig: cfg,
@@ -1594,10 +1704,19 @@ export function createConfigIO(
1594170415951705const resolvedConfigRaw = readResolution.resolvedConfigRaw;
15961706const legacyResolution = resolveLegacyConfigForRead(resolvedConfigRaw, effectiveParsed);
1597-const effectiveConfigRaw = migrateAndStripShippedPluginInstallConfigRecords(
1707+const installMigration = migrateAndStripShippedPluginInstallConfigRecords(
15981708legacyResolution.effectiveConfigRaw,
1599-{ persist: options.persistShippedPluginInstallMigration !== false },
1709+{
1710+persist: options.persistShippedPluginInstallMigration !== false,
1711+rootConfigRaw: effectiveParsed,
1712+},
16001713);
1714+const effectiveConfigRaw = installMigration.config;
1715+const snapshotRaw = installMigration.persistedRootRaw ?? effectiveRaw;
1716+const snapshotParsed = installMigration.persistedRootParsed ?? effectiveParsed;
1717+const snapshotHash = installMigration.persistedRootRaw
1718+ ? hashConfigRaw(installMigration.persistedRootRaw)
1719+ : hash;
16011720fallbackSourceConfig = coerceConfig(effectiveConfigRaw);
16021721const validated = validateConfigObjectWithPlugins(effectiveConfigRaw, {
16031722env: deps.env,
@@ -1608,12 +1727,12 @@ export function createConfigIO(
16081727snapshot: createConfigFileSnapshot({
16091728path: configPath,
16101729exists: true,
1611-raw: effectiveRaw,
1612-parsed: effectiveParsed,
1730+raw: snapshotRaw,
1731+parsed: snapshotParsed,
16131732sourceConfig: coerceConfig(effectiveConfigRaw),
16141733valid: false,
16151734runtimeConfig: coerceConfig(effectiveConfigRaw),
1616- hash,
1735+hash: snapshotHash,
16171736issues: validated.issues,
16181737warnings: [...validated.warnings, ...envVarWarnings],
16191738legacyIssues: legacyResolution.sourceLegacyIssues,
@@ -1627,14 +1746,14 @@ export function createConfigIO(
16271746snapshot: createConfigFileSnapshot({
16281747path: configPath,
16291748exists: true,
1630-raw: effectiveRaw,
1631-parsed: effectiveParsed,
1749+raw: snapshotRaw,
1750+parsed: snapshotParsed,
16321751// Use resolvedConfigRaw (after $include and ${ENV} substitution but BEFORE runtime defaults)
16331752// for config set/unset operations (issue #6070)
16341753sourceConfig: coerceConfig(effectiveConfigRaw),
16351754valid: true,
16361755runtimeConfig: snapshotConfig,
1637- hash,
1756+hash: snapshotHash,
16381757issues: [],
16391758warnings: [...validated.warnings, ...envVarWarnings],
16401759legacyIssues: legacyResolution.sourceLegacyIssues,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。