246 lines
8.0 KiB
JavaScript
246 lines
8.0 KiB
JavaScript
'use strict';
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
// Memoize results of `existsSync(...) && lstatSync(...).isFile()` for
|
|
// resolution probes inside `packages/*`. Each spec file with
|
|
// `jest.mock('@nx/devkit', ...)` (or any cross-package require) makes Jest
|
|
// re-run resolution from scratch, which calls `existsSync` and `lstatSync`
|
|
// on the same workspace files repeatedly. Those probes show up in the
|
|
// sandbox report as "unexpected reads" even though they don't read any
|
|
// file content. The map is process-wide and bounded by the set of
|
|
// workspace package entry points, so memory is trivial.
|
|
const isFileCache = new Map();
|
|
const isWorkspaceFile = (p) => {
|
|
if (isFileCache.has(p)) return isFileCache.get(p);
|
|
let result = false;
|
|
try {
|
|
result = fs.existsSync(p) && fs.lstatSync(p).isFile();
|
|
} catch (_) {}
|
|
isFileCache.set(p, result);
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Custom resolver which will respect package exports (until Jest supports it natively
|
|
* by resolving https://github.com/facebook/jest/issues/9771)
|
|
*
|
|
* Also needed for TypeScript project references support:
|
|
* - ts-jest doesn't support TypeScript project references (https://github.com/kulshekhar/ts-jest/issues/1648)
|
|
* - When using TS project references, Jest needs to resolve imports like '@nx/devkit' to TypeScript source files
|
|
* - Without this resolver, Jest will fail to resolve these imports correctly
|
|
*/
|
|
const enhancedResolver = require('enhanced-resolve').create.sync({
|
|
conditionNames: ['@nx/nx-source', 'require', 'node', 'default'],
|
|
extensions: ['.js', '.json', '.node', '.ts', '.tsx'],
|
|
});
|
|
|
|
if (
|
|
process.argv[1].indexOf('jest-worker') > -1 ||
|
|
(process.argv.length >= 4 && process.argv[3].split(':')[1] === 'test')
|
|
) {
|
|
const root = path.join(__dirname, '..', 'tmp', 'unit');
|
|
try {
|
|
if (!fs.existsSync(root)) {
|
|
fs.mkdirSync(root);
|
|
}
|
|
} catch (_err) {}
|
|
process.env.NX_WORKSPACE_ROOT_PATH = root;
|
|
}
|
|
|
|
const excludedPackages = [
|
|
'@nx/conformance',
|
|
'@nx/owners',
|
|
'@nx/key',
|
|
'@nx/s3-cache',
|
|
'@nx/azure-cache',
|
|
'@nx/gcs-cache',
|
|
'@nx/shared-fs-cache',
|
|
];
|
|
|
|
module.exports = function (modulePath, options) {
|
|
// Skip sequencer
|
|
if (modulePath === 'jest-sequencer-@jest/test-sequencer') return;
|
|
|
|
// Only use custom resolution for workspace packages
|
|
// Let default resolver handle published packages and everything else
|
|
const workspacePackages = [
|
|
'@nx/rollup',
|
|
'@nx/eslint',
|
|
'@nx/vite',
|
|
'@nx/vitest',
|
|
'@nx/jest',
|
|
'@nx/docker',
|
|
'@nx/dotnet',
|
|
'@nx/js',
|
|
'@nx/maven',
|
|
'@nx/next',
|
|
'@nx/storybook',
|
|
'@nx/rsbuild',
|
|
'@nx/react-native',
|
|
'@nx/express',
|
|
'@nx/web',
|
|
'@nx/vue',
|
|
'@nx/workspace',
|
|
'@nx/module-federation',
|
|
'@nx/rspack',
|
|
'@nx/eslint-plugin',
|
|
'@nx/angular',
|
|
'@nx/create-nx-plugin',
|
|
'@nx/create-nx-workspace',
|
|
'@nx/cypress',
|
|
'@nx/detox',
|
|
'@nx/devkit',
|
|
'@nx/esbuild',
|
|
'@nx/expo',
|
|
'@nx/gradle',
|
|
'@nx/nest',
|
|
'@nx/node',
|
|
'@nx/nuxt',
|
|
'@nx/playwright',
|
|
'@nx/plugin',
|
|
'@nx/react',
|
|
'@nx/remix',
|
|
'@nx/webpack',
|
|
];
|
|
|
|
const isWorkspacePackage =
|
|
workspacePackages.some((pkg) => modulePath.startsWith(pkg)) ||
|
|
modulePath.startsWith('nx/');
|
|
|
|
if (!isWorkspacePackage) {
|
|
// Global modules which must be resolved by defaultResolver
|
|
if (['child_process', 'fs', 'http', 'path'].includes(modulePath)) {
|
|
return options.defaultResolver(modulePath, options);
|
|
}
|
|
|
|
return enhancedResolver(path.resolve(options.basedir), modulePath);
|
|
}
|
|
|
|
const ext = path.extname(modulePath);
|
|
|
|
// Handle CSS imports
|
|
if (ext === '.css' || ext === '.scss' || ext === '.sass' || ext === '.less') {
|
|
return require.resolve('identity-obj-proxy');
|
|
}
|
|
|
|
try {
|
|
// For nx/* imports, use @nx/nx-source condition to resolve to .ts
|
|
// source files instead of dist/*.js files.
|
|
if (modulePath.startsWith('nx/') || modulePath === 'nx') {
|
|
return options.defaultResolver(modulePath, {
|
|
...options,
|
|
conditions: ['@nx/nx-source', 'require', 'node', 'default'],
|
|
});
|
|
}
|
|
|
|
// Find workspace root - avoid filesystem lookups inside node_modules
|
|
// For PNPM workspaces, we know the structure: workspace/packages/packageName
|
|
let workspaceRoot = options.rootDir;
|
|
|
|
// If we're in a packages subdirectory, go up two levels to workspace root
|
|
if (workspaceRoot.includes('/packages/')) {
|
|
const packagesIndex = workspaceRoot.lastIndexOf('/packages/');
|
|
workspaceRoot = workspaceRoot.substring(0, packagesIndex);
|
|
} else {
|
|
// Fallback: go up directories until we find packages/ (but check pnpm-lock.yaml for validation)
|
|
while (workspaceRoot && workspaceRoot !== path.dirname(workspaceRoot)) {
|
|
const pnpmLock = path.join(workspaceRoot, 'pnpm-lock.yaml');
|
|
const packagesDir = path.join(workspaceRoot, 'packages');
|
|
if (fs.existsSync(pnpmLock) && fs.existsSync(packagesDir)) {
|
|
break;
|
|
}
|
|
workspaceRoot = path.dirname(workspaceRoot);
|
|
}
|
|
}
|
|
|
|
const packagesPath = path.join(workspaceRoot, 'packages');
|
|
|
|
// Handle main @nx/* package imports (e.g., '@nx/devkit', '@nx/js')
|
|
const nxPackageMatch = modulePath.match(/^@nx\/([^/]+)$/);
|
|
if (nxPackageMatch) {
|
|
const packageName = nxPackageMatch[1];
|
|
|
|
// Check if this package exists in workspace
|
|
const packageDir = path.join(packagesPath, packageName);
|
|
|
|
// Try different entry points based on package structure
|
|
const possibleEntries = [
|
|
path.join(packageDir, 'index.ts'),
|
|
path.join(packageDir, 'src', 'index.ts'),
|
|
];
|
|
|
|
for (const entry of possibleEntries) {
|
|
if (isWorkspaceFile(entry)) {
|
|
return entry;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle @nx/*/src/* subpath imports (e.g., '@nx/devkit/src/utils/something')
|
|
const nxSubpathMatch = modulePath.match(/^@nx\/([^/]+)\/src\/(.+)$/);
|
|
if (nxSubpathMatch) {
|
|
const packageName = nxSubpathMatch[1];
|
|
const subpath = nxSubpathMatch[2];
|
|
|
|
// Try different patterns for subpath resolution
|
|
const possiblePaths = [
|
|
path.join(packagesPath, packageName, 'src', subpath + '.ts'), // Direct file
|
|
path.join(packagesPath, packageName, 'src', subpath, 'index.ts'), // Directory with index.ts
|
|
];
|
|
|
|
for (const possiblePath of possiblePaths) {
|
|
if (isWorkspaceFile(possiblePath)) {
|
|
return possiblePath;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle @nx/* other subpaths (e.g., '@nx/devkit/testing', '@nx/devkit/package.json')
|
|
const nxOtherMatch = modulePath.match(/^@nx\/([^/]+)\/(.+)$/);
|
|
if (nxOtherMatch) {
|
|
const packageName = nxOtherMatch[1];
|
|
const subpath = nxOtherMatch[2];
|
|
|
|
const packageDir = path.join(packagesPath, packageName);
|
|
|
|
// Try different patterns for subpath resolution
|
|
const possiblePaths = [
|
|
path.join(packageDir, subpath), // For files like package.json
|
|
path.join(packageDir, subpath + '.ts'),
|
|
path.join(packageDir, 'src', subpath + '.ts'),
|
|
];
|
|
|
|
for (const possiblePath of possiblePaths) {
|
|
if (isWorkspaceFile(possiblePath)) {
|
|
return possiblePath;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Block excluded Nx packages from auto-resolution
|
|
if (
|
|
modulePath.startsWith('@nx/') &&
|
|
!modulePath.startsWith('@nx/powerpack-') &&
|
|
!excludedPackages.some((pkg) => modulePath.startsWith(pkg))
|
|
) {
|
|
// If we get here, it means the workspace package couldn't be resolved above
|
|
// This might indicate a missing file or incorrect import
|
|
console.warn(
|
|
`[resolver] Could not resolve workspace package: ${modulePath}`
|
|
);
|
|
}
|
|
|
|
return enhancedResolver(path.resolve(options.basedir), modulePath);
|
|
} catch (e) {
|
|
// Final fallback: use default resolver for packages we can't handle
|
|
// This preserves the old behavior where packages that couldn't be resolved
|
|
// by the custom resolver would fall back to default resolution
|
|
try {
|
|
return options.defaultResolver(modulePath, options);
|
|
} catch (defaultResolverError) {
|
|
throw new Error(`[resolver] Could not resolve module: ${modulePath}`);
|
|
}
|
|
}
|
|
};
|