fix: preserve npm environment during package replacement

This commit is contained in:
whyour
2026-10-19 15:41:42 +08:00
parent 3f02b04dff
commit 2669f251ac
5 changed files with 146 additions and 9 deletions
+44 -2
View File
@@ -28,5 +28,47 @@ function restorePackageLock(root = path.resolve(__dirname, '..')) {
}
}
module.exports = { restorePackageLock };
if (require.main === module) restorePackageLock();
function restoreEnvironment(root = path.resolve(__dirname, '..')) {
// npm's Arborist keeps this exact retirement directory until lifecycle scripts
// succeed, then deletes it. Never search other installations or sibling backups.
const suffix = createHash('sha1').update(root).digest('base64')
.replace(/[^a-zA-Z0-9]+/g, '').slice(0, 8);
const retired = path.join(path.dirname(root), `.${path.basename(root)}-${suffix}`);
const destination = path.join(root, '.env');
const exists = (file) => {
try { return fs.lstatSync(file); }
catch (error) { if (error.code === 'ENOENT') return; throw error; }
};
if (exists(destination)) return;
const directory = exists(retired);
if (!directory) return;
if (!directory.isDirectory()) throw new Error('Invalid npm retirement directory');
const metadata = path.join(retired, 'package.json');
if (!exists(metadata)?.isFile()) throw new Error('Invalid retired npm package');
if (JSON.parse(fs.readFileSync(metadata, 'utf8')).name !== '@whyour/qinglong')
throw new Error('Unexpected retired npm package');
const source = path.join(retired, '.env');
const stat = exists(source);
if (!stat) return;
if (!stat.isFile()) throw new Error('Retired npm .env must be a regular file');
const descriptor = fs.openSync(source, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
const temporary = path.join(root, `.upgrade-env-${randomUUID()}`);
try {
if (!fs.fstatSync(descriptor).isFile()) throw new Error('Invalid retired npm .env');
fs.writeFileSync(temporary, fs.readFileSync(descriptor), { flag: 'wx', mode: 0o600 });
// Linking publishes a complete file without replacing a concurrently created .env.
try { fs.linkSync(temporary, destination); }
catch (error) { if (error.code !== 'EEXIST') throw error; }
} finally {
fs.closeSync(descriptor);
fs.rmSync(temporary, { force: true });
}
}
function installPackage(root = path.resolve(__dirname, '..')) {
restorePackageLock(root);
restoreEnvironment(root);
}
module.exports = { restorePackageLock, restoreEnvironment, installPackage };
if (require.main === module) installPackage();