nx/packages/nx/src/utils/package-json.ts
Craigory Coppola 5d56e21163
fix(core): read project name from package json if not set in project json (#26386)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
If `project.json` exists without a name, we infer one based on the root
path. This can be confusing when there exists a `package.json` alongside
it that contains a name which doesn't match our inferred name.

## Expected Behavior
If `project.json` and `package.json` both exist, the name from
`package.json` will be used if `project.json` contains no name.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #26347
2024-06-12 11:18:29 -04:00

268 lines
7.4 KiB
TypeScript

import { existsSync } from 'fs';
import { dirname, join } from 'path';
import {
InputDefinition,
ProjectMetadata,
TargetConfiguration,
} from '../config/workspace-json-project-json';
import { mergeTargetConfigurations } from '../project-graph/utils/project-configuration-utils';
import { readJsonFile } from './fileutils';
import { getNxRequirePaths } from './installation-directory';
import {
PackageManagerCommands,
getPackageManagerCommand,
} from './package-manager';
export interface NxProjectPackageJsonConfiguration {
name?: string;
implicitDependencies?: string[];
tags?: string[];
namedInputs?: { [inputName: string]: (string | InputDefinition)[] };
targets?: Record<string, TargetConfiguration>;
includedScripts?: string[];
}
export type ArrayPackageGroup = { package: string; version: string }[];
export type MixedPackageGroup =
| (string | { package: string; version: string })[]
| Record<string, string>;
export type PackageGroup = MixedPackageGroup | ArrayPackageGroup;
export interface NxMigrationsConfiguration {
migrations?: string;
packageGroup?: PackageGroup;
}
type PackageOverride = { [key: string]: string | PackageOverride };
export interface PackageJson {
// Generic Package.Json Configuration
name: string;
version: string;
license?: string;
private?: boolean;
scripts?: Record<string, string>;
type?: 'module' | 'commonjs';
main?: string;
types?: string;
module?: string;
exports?:
| string
| Record<
string,
string | { types?: string; require?: string; import?: string }
>;
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
optionalDependencies?: Record<string, string>;
peerDependencies?: Record<string, string>;
peerDependenciesMeta?: Record<string, { optional: boolean }>;
resolutions?: Record<string, string>;
overrides?: PackageOverride;
bin?: Record<string, string> | string;
workspaces?:
| string[]
| {
packages: string[];
};
publishConfig?: Record<string, string>;
// Nx Project Configuration
nx?: NxProjectPackageJsonConfiguration;
// Nx Plugin Configuration
generators?: string;
schematics?: string;
builders?: string;
executors?: string;
'nx-migrations'?: string | NxMigrationsConfiguration;
'ng-update'?: string | NxMigrationsConfiguration;
packageManager?: string;
}
export function normalizePackageGroup(
packageGroup: PackageGroup
): ArrayPackageGroup {
return Array.isArray(packageGroup)
? packageGroup.map((x) =>
typeof x === 'string' ? { package: x, version: '*' } : x
)
: Object.entries(packageGroup).map(([pkg, version]) => ({
package: pkg,
version,
}));
}
export function readNxMigrateConfig(
json: Partial<PackageJson>
): NxMigrationsConfiguration & { packageGroup?: ArrayPackageGroup } {
const parseNxMigrationsConfig = (
fromJson?: string | NxMigrationsConfiguration
): NxMigrationsConfiguration & { packageGroup?: ArrayPackageGroup } => {
if (!fromJson) {
return {};
}
if (typeof fromJson === 'string') {
return { migrations: fromJson, packageGroup: [] };
}
return {
...(fromJson.migrations ? { migrations: fromJson.migrations } : {}),
...(fromJson.packageGroup
? { packageGroup: normalizePackageGroup(fromJson.packageGroup) }
: {}),
};
};
return {
...parseNxMigrationsConfig(json['ng-update']),
...parseNxMigrationsConfig(json['nx-migrations']),
// In case there's a `migrations` field in `package.json`
...parseNxMigrationsConfig(json as any),
};
}
export function buildTargetFromScript(
script: string,
scripts: Record<string, string> = {},
packageManagerCommand: PackageManagerCommands
): TargetConfiguration {
return {
executor: 'nx:run-script',
options: {
script,
},
metadata: {
scriptContent: scripts[script],
runCommand: packageManagerCommand.run(script),
},
};
}
let packageManagerCommand: PackageManagerCommands | undefined;
export function getMetadataFromPackageJson(
packageJson: PackageJson
): ProjectMetadata {
const { scripts, nx } = packageJson ?? {};
const includedScripts = nx?.includedScripts || Object.keys(scripts ?? {});
return {
targetGroups: {
'NPM Scripts': includedScripts,
},
};
}
export function readTargetsFromPackageJson(packageJson: PackageJson) {
const { scripts, nx, private: isPrivate } = packageJson ?? {};
const res: Record<string, TargetConfiguration> = {};
const includedScripts = nx?.includedScripts || Object.keys(scripts ?? {});
packageManagerCommand ??= getPackageManagerCommand();
for (const script of includedScripts) {
res[script] = buildTargetFromScript(script, scripts, packageManagerCommand);
}
for (const targetName in nx?.targets) {
res[targetName] = mergeTargetConfigurations(
nx?.targets[targetName],
res[targetName]
);
}
/**
* Add implicit nx-release-publish target for all package.json files that are
* not marked as `"private": true` to allow for lightweight configuration for
* package based repos.
*/
if (!isPrivate && !res['nx-release-publish']) {
res['nx-release-publish'] = {
dependsOn: ['^nx-release-publish'],
executor: '@nx/js:release-publish',
options: {},
};
}
return res;
}
/**
* Uses `require.resolve` to read the package.json for a module.
*
* This will fail if the module doesn't export package.json
*
* @returns package json contents and path
*/
export function readModulePackageJsonWithoutFallbacks(
moduleSpecifier: string,
requirePaths = getNxRequirePaths()
): {
packageJson: PackageJson;
path: string;
} {
const packageJsonPath: string = require.resolve(
`${moduleSpecifier}/package.json`,
{
paths: requirePaths,
}
);
const packageJson: PackageJson = readJsonFile(packageJsonPath);
return {
path: packageJsonPath,
packageJson,
};
}
/**
* Reads the package.json file for a specified module.
*
* Includes a fallback that accounts for modules that don't export package.json
*
* @param {string} moduleSpecifier The module to look up
* @param {string[]} requirePaths List of paths look in. Pass `module.paths` to ensure non-hoisted dependencies are found.
*
* @example
* // Use the caller's lookup paths for non-hoisted dependencies
* readModulePackageJson('http-server', module.paths);
*
* @returns package json contents and path
*/
export function readModulePackageJson(
moduleSpecifier: string,
requirePaths = getNxRequirePaths()
): {
packageJson: PackageJson;
path: string;
} {
let packageJsonPath: string;
let packageJson: PackageJson;
try {
({ path: packageJsonPath, packageJson } =
readModulePackageJsonWithoutFallbacks(moduleSpecifier, requirePaths));
} catch {
const entryPoint = require.resolve(moduleSpecifier, {
paths: requirePaths,
});
let moduleRootPath = dirname(entryPoint);
packageJsonPath = join(moduleRootPath, 'package.json');
while (!existsSync(packageJsonPath)) {
moduleRootPath = dirname(moduleRootPath);
packageJsonPath = join(moduleRootPath, 'package.json');
}
packageJson = readJsonFile(packageJsonPath);
if (packageJson.name && packageJson.name !== moduleSpecifier) {
throw new Error(
`Found module ${packageJson.name} while trying to locate ${moduleSpecifier}/package.json`
);
}
}
return {
packageJson,
path: packageJsonPath,
};
}