mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-21 22:34:21 +08:00
The new public API tooling searches for nested package.json files to determine the location of secondary entrypoints. All secondary entrypoints for the CLI related packages now contain a secondary entrypoint package.json file. The internal monorepo package discovery script was also updated to support the presence of the nested package.json files.
255 lines
7.2 KiB
TypeScript
255 lines
7.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import { JsonObject } from '@angular-devkit/core';
|
|
import { execSync } from 'child_process';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as ts from 'typescript';
|
|
|
|
const distRoot = path.join(__dirname, '../dist');
|
|
const { packages: monorepoPackages } = require('../.monorepo.json');
|
|
|
|
export interface PackageInfo {
|
|
name: string;
|
|
root: string;
|
|
bin: { [name: string]: string };
|
|
relative: string;
|
|
main: string;
|
|
dist: string;
|
|
build: string;
|
|
tar: string;
|
|
private: boolean;
|
|
experimental: boolean;
|
|
packageJson: JsonObject;
|
|
dependencies: string[];
|
|
reverseDependencies: string[];
|
|
|
|
snapshot: boolean;
|
|
snapshotRepo: string;
|
|
snapshotHash: string;
|
|
|
|
version: string;
|
|
}
|
|
export type PackageMap = { [name: string]: PackageInfo };
|
|
|
|
export function loadRootPackageJson() {
|
|
return require('../package.json');
|
|
}
|
|
|
|
function loadPackageJson(p: string) {
|
|
const root = loadRootPackageJson();
|
|
const pkg = require(p);
|
|
|
|
for (const key of Object.keys(root)) {
|
|
switch (key) {
|
|
// Keep the following keys from the package.json of the package itself.
|
|
case 'bin':
|
|
case 'description':
|
|
case 'dependencies':
|
|
case 'name':
|
|
case 'main':
|
|
case 'peerDependencies':
|
|
case 'optionalDependencies':
|
|
case 'typings':
|
|
case 'version':
|
|
case 'private':
|
|
case 'workspaces':
|
|
case 'resolutions':
|
|
case 'scripts':
|
|
continue;
|
|
|
|
// Remove the following keys from the package.json.
|
|
case 'devDependencies':
|
|
delete pkg[key];
|
|
continue;
|
|
|
|
// Merge the following keys with the root package.json.
|
|
case 'keywords':
|
|
const a = pkg[key] || [];
|
|
const b = Object.keys(
|
|
root[key].concat(a).reduce((acc: { [k: string]: boolean }, curr: string) => {
|
|
acc[curr] = true;
|
|
|
|
return acc;
|
|
}, {}),
|
|
);
|
|
pkg[key] = b;
|
|
break;
|
|
|
|
// Overwrite engines to a common default.
|
|
case 'engines':
|
|
pkg['engines'] = {
|
|
'node': '^12.14.1 || >=14.0.0',
|
|
'npm': '^6.11.0 || ^7.5.6',
|
|
'yarn': '>= 1.13.0',
|
|
};
|
|
break;
|
|
|
|
// Overwrite the package's key with to root one.
|
|
default:
|
|
pkg[key] = root[key];
|
|
}
|
|
}
|
|
|
|
return pkg;
|
|
}
|
|
|
|
function* _findPrimaryPackageJsonFiles(dir: string, exclude: RegExp): Iterable<string> {
|
|
const subdirectories = [];
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const p = path.join(dir, entry.name);
|
|
|
|
if (exclude.test(p)) {
|
|
continue;
|
|
}
|
|
|
|
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
|
subdirectories.push(p);
|
|
continue;
|
|
}
|
|
|
|
if (entry.name === 'package.json') {
|
|
yield p;
|
|
|
|
// First package.json found will be the package's root package.json
|
|
// Secondary entrypoint package.json files should not be found here
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (const directory of subdirectories) {
|
|
yield* _findPrimaryPackageJsonFiles(directory, exclude);
|
|
}
|
|
}
|
|
|
|
const tsConfigPath = path.join(__dirname, '../tsconfig.json');
|
|
const tsConfig = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
|
|
const pattern =
|
|
'^(' +
|
|
(tsConfig.config.exclude as string[])
|
|
.map((ex) => path.join(path.dirname(tsConfigPath), ex))
|
|
.map(
|
|
(ex) =>
|
|
'(' +
|
|
ex
|
|
.replace(/[\-\[\]{}()+?./\\^$|]/g, '\\$&')
|
|
.replace(/(\\\\|\\\/)\*\*/g, '((/|\\\\).+?)?')
|
|
.replace(/\*/g, '[^/\\\\]*') +
|
|
')',
|
|
)
|
|
.join('|') +
|
|
')($|/|\\\\)';
|
|
const excludeRe = new RegExp(pattern);
|
|
|
|
// Find all the package.json that aren't excluded from tsconfig.
|
|
const packageJsonPaths = [
|
|
..._findPrimaryPackageJsonFiles(path.join(__dirname, '..', 'packages'), excludeRe),
|
|
];
|
|
|
|
function _exec(cmd: string) {
|
|
return execSync(cmd).toString().trim();
|
|
}
|
|
|
|
let gitShaCache: string;
|
|
function _getSnapshotHash(_pkg: PackageInfo): string {
|
|
if (!gitShaCache) {
|
|
gitShaCache = _exec('git log --format=%h -n1');
|
|
}
|
|
|
|
return gitShaCache;
|
|
}
|
|
|
|
const stableVersion = loadRootPackageJson().version;
|
|
const experimentalVersion = stableToExperimentalVersion(stableVersion);
|
|
|
|
/**
|
|
* Convert a stable version to its experimental equivalent. For example,
|
|
* stable = 10.2.3, experimental = 0.1002.3
|
|
* @param stable Must begin with d+.d+ where d is a 0-9 digit.
|
|
*/
|
|
export function stableToExperimentalVersion(stable: string): string {
|
|
return `0.${stable.replace(/^(\d+)\.(\d+)/, (_, major, minor) => {
|
|
return '' + (parseInt(major, 10) * 100 + parseInt(minor, 10));
|
|
})}`;
|
|
}
|
|
|
|
// All the supported packages. Go through the packages directory and create a map of
|
|
// name => PackageInfo. This map is partial as it lacks some information that requires the
|
|
// map itself to finish building.
|
|
export const packages: PackageMap = packageJsonPaths
|
|
.map((pkgPath) => ({ root: path.dirname(pkgPath) }))
|
|
.reduce((packages: PackageMap, pkg) => {
|
|
const pkgRoot = pkg.root;
|
|
const packageJson = loadPackageJson(path.join(pkgRoot, 'package.json'));
|
|
const name = packageJson['name'];
|
|
if (!name) {
|
|
// Only build the entry if there's a package name.
|
|
return packages;
|
|
}
|
|
if (!(name in monorepoPackages)) {
|
|
throw new Error(
|
|
`Package ${name} found in ${JSON.stringify(pkg.root)}, not found in .monorepo.json.`,
|
|
);
|
|
}
|
|
|
|
const bin: { [name: string]: string } = {};
|
|
Object.keys(packageJson['bin'] || {}).forEach((binName) => {
|
|
let p = path.resolve(pkg.root, packageJson['bin'][binName]);
|
|
if (!fs.existsSync(p)) {
|
|
p = p.replace(/\.js$/, '.ts');
|
|
}
|
|
bin[binName] = p;
|
|
});
|
|
|
|
const experimental = !!packageJson.private || !!packageJson.experimental;
|
|
|
|
packages[name] = {
|
|
build: path.join(distRoot, pkgRoot.substr(path.dirname(__dirname).length)),
|
|
dist: path.join(distRoot, name),
|
|
root: pkgRoot,
|
|
relative: path.relative(path.dirname(__dirname), pkgRoot),
|
|
main: path.resolve(pkgRoot, 'src/index.ts'),
|
|
private: !!packageJson.private,
|
|
experimental,
|
|
// yarn doesn't take kindly to @ in tgz filenames
|
|
// https://github.com/yarnpkg/yarn/issues/6339
|
|
tar: path.join(distRoot, name.replace(/\/|@/g, '_') + '.tgz'),
|
|
bin,
|
|
name,
|
|
packageJson,
|
|
|
|
snapshot: !!monorepoPackages[name].snapshotRepo,
|
|
snapshotRepo: monorepoPackages[name].snapshotRepo,
|
|
get snapshotHash() {
|
|
return _getSnapshotHash(this);
|
|
},
|
|
|
|
dependencies: [],
|
|
reverseDependencies: [],
|
|
version: experimental ? experimentalVersion : stableVersion,
|
|
};
|
|
|
|
return packages;
|
|
}, {});
|
|
|
|
// Update with dependencies.
|
|
for (const pkgName of Object.keys(packages)) {
|
|
const pkg = packages[pkgName];
|
|
const pkgJson = require(path.join(pkg.root, 'package.json'));
|
|
pkg.dependencies = Object.keys(packages).filter((name) => {
|
|
return name in (pkgJson.dependencies || {}) || name in (pkgJson.devDependencies || {});
|
|
});
|
|
pkg.dependencies.forEach((depName) => packages[depName].reverseDependencies.push(pkgName));
|
|
}
|
|
|
|
/** The set of packages which are built and released. */
|
|
export const releasePackages = Object.fromEntries(
|
|
Object.entries(packages).filter(([_, pkg]) => !pkg.private),
|
|
);
|