mirror of
https://github.com/whyour/qinglong.git
synced 2026-11-02 15:56:59 +08:00
75 lines
3.2 KiB
JavaScript
75 lines
3.2 KiB
JavaScript
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { createHash, randomUUID } = require('node:crypto');
|
|
|
|
function restorePackageLock(root = path.resolve(__dirname, '..')) {
|
|
const copy = path.join(root, 'shell/upgrade-lock.yaml');
|
|
const destination = path.join(root, 'pnpm-lock.yaml');
|
|
if (!fs.existsSync(copy)) {
|
|
// Source checkouts already have the canonical lock; no built artifacts yet.
|
|
if (fs.existsSync(destination)) return;
|
|
throw new Error('Missing npm package upgrade lockfile');
|
|
}
|
|
const bytes = fs.readFileSync(copy);
|
|
const manifest = JSON.parse(
|
|
fs.readFileSync(path.join(root, 'static/build-info.json'), 'utf8'),
|
|
);
|
|
if (
|
|
manifest.dirty !== false ||
|
|
manifest.lockfileSha256 !== createHash('sha256').update(bytes).digest('hex')
|
|
)
|
|
throw new Error('npm package lockfile does not match its build manifest');
|
|
const temporary = path.join(root, `.upgrade-lock-${randomUUID()}`);
|
|
try {
|
|
fs.writeFileSync(temporary, bytes, { flag: 'wx', mode: 0o644 });
|
|
fs.renameSync(temporary, destination);
|
|
} finally {
|
|
fs.rmSync(temporary, { force: true });
|
|
}
|
|
}
|
|
|
|
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();
|