mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
246 lines
7.1 KiB
JavaScript
246 lines
7.1 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const yaml = require('js-yaml');
|
|
|
|
const repositoryRoot = path.resolve(__dirname, '..');
|
|
const lock = yaml.load(
|
|
fs.readFileSync(path.join(repositoryRoot, 'pnpm-lock.yaml'), 'utf8'),
|
|
);
|
|
|
|
function packageKey(name, version) {
|
|
const exact = `/${name}@${version}`;
|
|
if (lock.packages[exact]) return exact;
|
|
const matches = Object.keys(lock.packages).filter(
|
|
(candidate) =>
|
|
candidate.startsWith(`${exact}(`) ||
|
|
candidate.startsWith(`${exact}_`),
|
|
);
|
|
assert.equal(
|
|
matches.length,
|
|
1,
|
|
`expected one pnpm lock entry for ${name}@${version}`,
|
|
);
|
|
return matches[0];
|
|
}
|
|
|
|
function exactVersion(value) {
|
|
assert.equal(typeof value, 'string');
|
|
const version = value.replace(/\(.+$/, '').replace(/_.+$/, '');
|
|
assert.match(version, /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/);
|
|
return version;
|
|
}
|
|
|
|
function installedPackageJson(name, version) {
|
|
const virtualStoreName = `${name.replace('/', '+')}@${version}`;
|
|
const packageJsonPath = path.join(
|
|
repositoryRoot,
|
|
'node_modules',
|
|
'.pnpm',
|
|
virtualStoreName,
|
|
'node_modules',
|
|
name,
|
|
'package.json',
|
|
);
|
|
assert.equal(
|
|
fs.existsSync(packageJsonPath),
|
|
true,
|
|
`installed metadata is missing for ${name}@${version}`,
|
|
);
|
|
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
}
|
|
|
|
function resolvedTarball(name, version) {
|
|
const leaf = name.includes('/') ? name.slice(name.lastIndexOf('/') + 1) : name;
|
|
return `https://registry.npmjs.org/${name}/-/${leaf}-${version}.tgz`;
|
|
}
|
|
|
|
function graphNode(name, version) {
|
|
const pnpm = lock.packages[packageKey(name, version)];
|
|
const manifest = installedPackageJson(name, version);
|
|
assert.equal(manifest.name, name);
|
|
assert.equal(manifest.version, version);
|
|
return {
|
|
name,
|
|
version,
|
|
pnpm,
|
|
manifest,
|
|
dependencies: Object.fromEntries(
|
|
Object.entries({
|
|
...(pnpm.dependencies ?? {}),
|
|
...(pnpm.optionalDependencies ?? {}),
|
|
}).map(([dependencyName, dependencyVersion]) => [
|
|
dependencyName,
|
|
exactVersion(dependencyVersion),
|
|
]),
|
|
),
|
|
};
|
|
}
|
|
|
|
function createLock(manifestPath, includeDev) {
|
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
const productionRoots = { ...(manifest.dependencies ?? {}) };
|
|
const roots = {
|
|
...productionRoots,
|
|
...(includeDev ? manifest.devDependencies ?? {} : {}),
|
|
};
|
|
for (const [name, version] of Object.entries(roots)) {
|
|
assert.match(version, /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/);
|
|
packageKey(name, version);
|
|
}
|
|
|
|
const nodes = new Map();
|
|
const incoming = new Map();
|
|
const visit = (name, version, production) => {
|
|
const id = `${name}@${version}`;
|
|
const current = nodes.get(id);
|
|
if (current) {
|
|
if (production) current.production = true;
|
|
return;
|
|
}
|
|
const node = { ...graphNode(name, version), production };
|
|
nodes.set(id, node);
|
|
for (const [dependencyName, dependencyVersion] of Object.entries(
|
|
node.dependencies,
|
|
)) {
|
|
const dependencyId = `${dependencyName}@${dependencyVersion}`;
|
|
incoming.set(dependencyId, (incoming.get(dependencyId) ?? 0) + 1);
|
|
visit(dependencyName, dependencyVersion, production);
|
|
}
|
|
};
|
|
for (const [name, version] of Object.entries(roots)) {
|
|
visit(name, version, Object.hasOwn(productionRoots, name));
|
|
}
|
|
|
|
const versionsByName = new Map();
|
|
for (const node of nodes.values()) {
|
|
const versions = versionsByName.get(node.name) ?? [];
|
|
versions.push(node.version);
|
|
versionsByName.set(node.name, versions);
|
|
}
|
|
const rootVersion = new Map();
|
|
for (const [name, versions] of versionsByName) {
|
|
if (Object.hasOwn(roots, name)) {
|
|
rootVersion.set(name, roots[name]);
|
|
continue;
|
|
}
|
|
versions.sort((left, right) => {
|
|
const rightCount = incoming.get(`${name}@${right}`) ?? 0;
|
|
const leftCount = incoming.get(`${name}@${left}`) ?? 0;
|
|
return rightCount - leftCount || right.localeCompare(left);
|
|
});
|
|
rootVersion.set(name, versions[0]);
|
|
}
|
|
|
|
const packages = {
|
|
'': {
|
|
name: manifest.name,
|
|
version: manifest.version,
|
|
license: manifest.license,
|
|
...(manifest.dependencies
|
|
? { dependencies: manifest.dependencies }
|
|
: {}),
|
|
...(includeDev && manifest.devDependencies
|
|
? { devDependencies: manifest.devDependencies }
|
|
: {}),
|
|
...(manifest.engines ? { engines: manifest.engines } : {}),
|
|
},
|
|
};
|
|
const emitted = new Set();
|
|
const emit = (name, version, installPath) => {
|
|
if (emitted.has(installPath)) return;
|
|
emitted.add(installPath);
|
|
const node = nodes.get(`${name}@${version}`);
|
|
assert(node, `graph node is missing for ${name}@${version}`);
|
|
const { manifest: dependencyManifest, pnpm } = node;
|
|
const record = {
|
|
version,
|
|
resolved: resolvedTarball(name, version),
|
|
integrity: pnpm.resolution?.integrity,
|
|
...(node.production ? {} : { dev: true }),
|
|
...(pnpm.optional ? { optional: true } : {}),
|
|
...(dependencyManifest.license
|
|
? { license: dependencyManifest.license }
|
|
: {}),
|
|
...(dependencyManifest.dependencies
|
|
? { dependencies: dependencyManifest.dependencies }
|
|
: {}),
|
|
...(dependencyManifest.optionalDependencies
|
|
? { optionalDependencies: dependencyManifest.optionalDependencies }
|
|
: {}),
|
|
...(dependencyManifest.peerDependencies
|
|
? { peerDependencies: dependencyManifest.peerDependencies }
|
|
: {}),
|
|
...(dependencyManifest.peerDependenciesMeta
|
|
? { peerDependenciesMeta: dependencyManifest.peerDependenciesMeta }
|
|
: {}),
|
|
...(dependencyManifest.engines
|
|
? { engines: dependencyManifest.engines }
|
|
: {}),
|
|
...(dependencyManifest.bin ? { bin: dependencyManifest.bin } : {}),
|
|
};
|
|
assert.equal(typeof record.integrity, 'string');
|
|
packages[installPath] = record;
|
|
for (const [dependencyName, dependencyVersion] of Object.entries(
|
|
node.dependencies,
|
|
)) {
|
|
if (rootVersion.get(dependencyName) === dependencyVersion) continue;
|
|
emit(
|
|
dependencyName,
|
|
dependencyVersion,
|
|
`${installPath}/node_modules/${dependencyName}`,
|
|
);
|
|
}
|
|
};
|
|
|
|
for (const [name, version] of rootVersion) {
|
|
emit(name, version, `node_modules/${name}`);
|
|
}
|
|
return {
|
|
name: manifest.name,
|
|
version: manifest.version,
|
|
lockfileVersion: 3,
|
|
requires: true,
|
|
packages,
|
|
};
|
|
}
|
|
|
|
const buildManifest = path.join(
|
|
repositoryRoot,
|
|
'deploy',
|
|
'containers',
|
|
'ql3-worker',
|
|
'package.json',
|
|
);
|
|
const runtimeManifest = path.join(
|
|
repositoryRoot,
|
|
'deploy',
|
|
'containers',
|
|
'ql3-worker',
|
|
'runtime-dependencies',
|
|
'package.json',
|
|
);
|
|
|
|
for (const [manifestPath, outputPath, includeDev] of [
|
|
[
|
|
buildManifest,
|
|
path.join(path.dirname(buildManifest), 'package-lock.json'),
|
|
true,
|
|
],
|
|
[
|
|
runtimeManifest,
|
|
path.join(path.dirname(runtimeManifest), 'package-lock.json'),
|
|
false,
|
|
],
|
|
]) {
|
|
fs.writeFileSync(
|
|
outputPath,
|
|
`${JSON.stringify(createLock(manifestPath, includeDev), null, 2)}\n`,
|
|
{ mode: 0o644 },
|
|
);
|
|
}
|