cli-node: refactor to move getDependencyHash to Lockfile

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-10-06 13:36:31 +02:00
parent fede18031d
commit e1bc9cc97d
5 changed files with 82 additions and 66 deletions
+1 -2
View File
@@ -92,7 +92,7 @@ export function isMonoRepo(): Promise<boolean>;
export class Lockfile {
createSimplifiedDependencyGraph(): Map<string, Set<string>>;
diff(otherLockfile: Lockfile): LockfileDiff;
getVersions(name: string): string[];
getDependencyTreeHash(startName: string): string;
static load(path: string): Promise<Lockfile>;
static parse(content: string): Lockfile;
}
@@ -117,7 +117,6 @@ export class PackageGraph extends Map<string, PackageGraphNode> {
collectFn: (pkg: PackageGraphNode) => Iterable<string> | undefined,
): Set<string>;
static fromPackages(packages: Package[]): PackageGraph;
getDependencyHash(name: string): Promise<string>;
listChangedPackages(options: {
ref: string;
analyzeLockfile?: boolean;
+60 -8
View File
@@ -15,6 +15,7 @@
*/
import { parseSyml } from '@yarnpkg/parsers';
import crypto from 'node:crypto';
import fs from 'fs-extra';
const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/;
@@ -133,14 +134,6 @@ export class Lockfile {
private readonly data: LockfileData,
) {}
/**
* Returns all versions of a package in the lockfile.
*/
getVersions(name: string): string[] {
const queries = this.packages.get(name);
return queries ? queries.map(q => q.version) : [];
}
/**
* Creates a simplified dependency graph from the lockfile data, where each
* key is a package, and the value is a set of all packages that it depends on
@@ -223,4 +216,63 @@ export class Lockfile {
return diff;
}
/**
* Generates a sha1 hex hash of the dependency graph for a package.
*/
getDependencyTreeHash(startName: string): string {
if (!this.packages.has(startName)) {
throw new Error(`Package '${startName}' not found in lockfile`);
}
const hash = crypto.createHash('sha1');
const queue = [startName];
const seen = new Set<string>();
while (queue.length > 0) {
const name = queue.pop()!;
if (seen.has(name)) {
continue;
}
seen.add(name);
const entries = this.packages.get(name);
if (!entries) {
continue; // In case of missing optional peer dependencies
}
hash.update(`pkg:${name}`);
hash.update('\0');
// TODO(Rugvip): This uses the same simplified lookup as createSimplifiedDependencyGraph()
// we could match version queries to make the resulting tree a bit smaller.
const deps = new Array<string>();
for (const entry of entries) {
// We're not being particular about stable ordering here. If the lockfile ordering changes, so will likely hash.
hash.update(entry.version);
const data = this.data[entry.dataKey];
if (!data) {
continue;
}
const checksum = data.checksum || data.integrity;
if (checksum) {
hash.update('#');
hash.update(checksum);
}
hash.update(' ');
deps.push(...Object.keys(data.dependencies ?? {}));
deps.push(...Object.keys(data.peerDependencies ?? {}));
}
queue.push(...new Set(deps));
}
return hash.digest('hex');
}
}
+3 -48
View File
@@ -15,7 +15,6 @@
*/
import path from 'path';
import crypto from 'node:crypto';
import { getPackages, Package } from '@manypkg/get-packages';
import { paths } from '../paths';
import { PackageRole } from '../roles';
@@ -284,43 +283,6 @@ export class PackageGraph extends Map<string, PackageGraphNode> {
return targets;
}
/**
* Generates a sha1 hex hash of the dependency graph for a package.
*/
async getDependencyHash(name: string): Promise<string> {
const pkg = this.get(name);
if (!pkg) {
throw new Error(`Package '${name}' not found`);
}
const lockfile = await this.#getLockfile();
const depGraph = lockfile.createSimplifiedDependencyGraph();
const seen = new Set<string>();
const queue = [name];
while (queue.length > 0) {
const deps = depGraph.get(queue.pop()!);
if (deps) {
for (const dep of deps) {
if (!seen.has(dep)) {
seen.add(dep);
queue.push(dep);
}
}
}
}
const hash = crypto.createHash('sha1');
for (const dep of Array.from(seen).sort()) {
hash.update(dep);
hash.update('\0');
hash.update(lockfile.getVersions(dep).join(' '));
hash.update('\0');
}
return hash.digest('hex');
}
/**
* Lists all packages that have changed since a given git ref.
*
@@ -380,7 +342,9 @@ export class PackageGraph extends Map<string, PackageGraphNode> {
let thisLockfile: Lockfile;
let otherLockfile: Lockfile;
try {
thisLockfile = await this.#getLockfile();
thisLockfile = await Lockfile.load(
paths.resolveTargetRoot('yarn.lock'),
);
otherLockfile = Lockfile.parse(
await GitUtils.readFileAtRef('yarn.lock', options.ref),
);
@@ -446,13 +410,4 @@ export class PackageGraph extends Map<string, PackageGraphNode> {
return result;
}
#lockfilePromise?: Promise<Lockfile>;
#getLockfile(): Promise<Lockfile> {
if (this.#lockfilePromise) {
return this.#lockfilePromise;
}
this.#lockfilePromise = Lockfile.load(paths.resolveTargetRoot('yarn.lock'));
return this.#lockfilePromise;
}
}