angular-cli/lib/packages.ts
Keen Yee Liau 90a01968f4 build: determine version to publish from package.json
Currently, the version of a release is determined by the git tag.
This PR changes release script to determine the release version from the
`version` property in the root `package.json`.

Release docs have also been updated.
2021-03-24 09:17:24 -04:00

248 lines
7.1 KiB
TypeScript

/**
* @license
* Copyright Google Inc. 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
*/
// tslint:disable-next-line: no-global-tslint-disable
// tslint:disable: no-implicit-dependencies
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.13.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 _findAllPackageJson(dir: string, exclude: RegExp): string[] {
const result: string[] = [];
fs.readdirSync(dir)
.forEach(fileName => {
const p = path.join(dir, fileName);
if (exclude.test(p)) {
return;
} else if (/[\/\\]node_modules[\/\\]/.test(p)) {
return;
} else if (fileName == 'package.json') {
result.push(p);
} else if (fs.statSync(p).isDirectory() && fileName != 'node_modules') {
result.push(..._findAllPackageJson(p, exclude));
}
});
return result;
}
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 = _findAllPackageJson(path.join(__dirname, '..'), excludeRe)
// Remove the root package.json.
.filter(p => p != path.join(__dirname, '../package.json'));
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));
}