nx/packages/nx/src/plugins/js/index.ts
Jordan Hall 80702b59c7
feat(core): add bun package manager (#22602)
Bun uses yarn lock for it's binary file. Running the binary will produce
the content of a yarn lock file (v1)

Other option is to use the -y command on add and install. This will
create a yarn lock file and then createLockFile can just modify the
yarn.lock file instead?

This is the PR made from #19113 and pushed due to #22402 being closed.

PS Bun feels more stable since the PR was first created!

This PR will resolve #22283 and start of #21075
2024-05-22 16:51:21 -04:00

164 lines
4.8 KiB
TypeScript

import { readFileSync, writeFileSync } from 'fs';
import { ensureDirSync } from 'fs-extra';
import { dirname, join } from 'path';
import { performance } from 'perf_hooks';
import { ProjectGraph } from '../../config/project-graph';
import { projectGraphCacheDirectory } from '../../utils/cache-directory';
import { combineGlobPatterns } from '../../utils/globs';
import {
CreateDependencies,
CreateDependenciesContext,
CreateNodes,
} from '../../project-graph/plugins';
import {
getLockFileDependencies,
getLockFileName,
getLockFileNodes,
lockFileExists,
LOCKFILES,
} from './lock-file/lock-file';
import { buildExplicitDependencies } from './project-graph/build-dependencies/build-dependencies';
import { jsPluginConfig } from './utils/config';
import { RawProjectGraphDependency } from '../../project-graph/project-graph-builder';
import { hashArray } from '../../hasher/file-hasher';
import { detectPackageManager } from '../../utils/package-manager';
import { workspaceRoot } from '../../utils/workspace-root';
import { nxVersion } from '../../utils/versions';
import { execSync } from 'child_process';
export const name = 'nx/js/dependencies-and-lockfile';
interface ParsedLockFile {
externalNodes?: ProjectGraph['externalNodes'];
dependencies?: RawProjectGraphDependency[];
}
let parsedLockFile: ParsedLockFile = {};
export const createNodes: CreateNodes = [
// Look for all lockfiles
combineGlobPatterns(LOCKFILES),
(lockFile, _, context) => {
const pluginConfig = jsPluginConfig(context.nxJsonConfiguration);
if (!pluginConfig.analyzeLockfile) {
return {};
}
const packageManager = detectPackageManager(workspaceRoot);
// Only process the correct lockfile
if (lockFile !== getLockFileName(packageManager)) {
return {};
}
const lockFilePath = join(workspaceRoot, lockFile);
const lockFileContents =
packageManager !== 'bun'
? readFileSync(lockFilePath).toString()
: execSync(`bun ${lockFilePath}`).toString();
const lockFileHash = getLockFileHash(lockFileContents);
if (!lockFileNeedsReprocessing(lockFileHash)) {
const nodes = readCachedParsedLockFile().externalNodes;
parsedLockFile.externalNodes = nodes;
return {
externalNodes: nodes,
};
}
const externalNodes = getLockFileNodes(
packageManager,
lockFileContents,
lockFileHash,
context
);
parsedLockFile.externalNodes = externalNodes;
return {
externalNodes,
};
},
];
export const createDependencies: CreateDependencies = (
_,
ctx: CreateDependenciesContext
) => {
const pluginConfig = jsPluginConfig(ctx.nxJsonConfiguration);
const packageManager = detectPackageManager(workspaceRoot);
let lockfileDependencies: RawProjectGraphDependency[] = [];
// lockfile may not exist yet
if (
pluginConfig.analyzeLockfile &&
lockFileExists(packageManager) &&
parsedLockFile.externalNodes
) {
const lockFilePath = join(workspaceRoot, getLockFileName(packageManager));
const lockFileContents =
packageManager !== 'bun'
? readFileSync(lockFilePath).toString()
: execSync(`bun ${lockFilePath}`).toString();
const lockFileHash = getLockFileHash(lockFileContents);
if (!lockFileNeedsReprocessing(lockFileHash)) {
lockfileDependencies = readCachedParsedLockFile().dependencies ?? [];
} else {
lockfileDependencies = getLockFileDependencies(
packageManager,
lockFileContents,
lockFileHash,
ctx
);
parsedLockFile.dependencies = lockfileDependencies;
writeLastProcessedLockfileHash(lockFileHash, parsedLockFile);
}
}
performance.mark('build typescript dependencies - start');
const explicitProjectDependencies = buildExplicitDependencies(
pluginConfig,
ctx
);
performance.mark('build typescript dependencies - end');
performance.measure(
'build typescript dependencies',
'build typescript dependencies - start',
'build typescript dependencies - end'
);
return lockfileDependencies.concat(explicitProjectDependencies);
};
function getLockFileHash(lockFileContents: string) {
return hashArray([nxVersion, lockFileContents]);
}
function lockFileNeedsReprocessing(lockHash: string) {
try {
return readFileSync(lockFileHashFile).toString() !== lockHash;
} catch {
return true;
}
}
function writeLastProcessedLockfileHash(
hash: string,
lockFile: ParsedLockFile
) {
ensureDirSync(dirname(lockFileHashFile));
writeFileSync(cachedParsedLockFile, JSON.stringify(lockFile, null, 2));
writeFileSync(lockFileHashFile, hash);
}
function readCachedParsedLockFile(): ParsedLockFile {
return JSON.parse(readFileSync(cachedParsedLockFile).toString());
}
const lockFileHashFile = join(projectGraphCacheDirectory, 'lockfile.hash');
const cachedParsedLockFile = join(
projectGraphCacheDirectory,
'parsed-lock-file.json'
);