build(bazel): refactor aio example e2es to fix windows performance

Use the same config flag to enable local vs npm deps as aio.
This commit is contained in:
Derek Cormier
2022-08-11 12:01:31 -07:00
committed by Joey Perrott
parent a36034a135
commit 8d24a91c6a
27 changed files with 190 additions and 1084 deletions
+21
View File
@@ -136,6 +136,27 @@ yarn_install(
yarn_lock = "//aio:yarn.lock",
)
yarn_install(
name = "aio_example_deps",
data = [
YARN_LABEL,
"//:.yarnrc",
],
# Disabled because, when False, yarn_install preserves the node_modules folder
# with bin symlinks in the external repository. This is needed to link the shared
# set of deps for example e2es.
exports_directories_only = False,
manual_build_file_contents = """\
filegroup(
name = "node_modules_with_bins",
srcs = ["node_modules", "node_modules/.bin"],
)
""",
package_json = "//aio/tools/examples/shared:package.json",
yarn = YARN_LABEL,
yarn_lock = "//aio/tools/examples/shared:yarn.lock",
)
load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies")
aspect_bazel_lib_dependencies()
+40 -32
View File
@@ -2,6 +2,7 @@ load("@build_bazel_rules_nodejs//:index.bzl", "npm_package_bin")
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
load("//aio/tools:defaults.bzl", "nodejs_test")
load("//:yarn.bzl", "YARN_LABEL")
load("//:packages.bzl", "ALL_PACKAGES", "to_package_label")
# This map controls which examples are included and whether or not to generate
# a stackblitz live examples and zip archives. Keys are the example name, and values
@@ -177,36 +178,43 @@ def docs_example(name, test = True, test_tags = []):
)
if test:
# These node_modules deps are symlinked into each example. These tree
# artifact folder names must still be "node_modules" despite the symlink
# being named node_modules. Otherwise, some deps will fail to resolve.
node_modules_deps = {
"local": "//aio/tools/examples/shared:local/node_modules",
"npm": "//aio/tools/examples/shared:node_modules",
}
EXAMPLE_DEPS_WORKSPACE_NAME = "aio_example_deps"
for [node_modules_source, node_modules_label] in node_modules_deps.items():
nodejs_test(
name = "e2e_%s" % node_modules_source,
data = [
":%s" % name,
YARN_LABEL,
node_modules_label,
"@aio_npm//@angular/dev-infra-private/bazel/browsers/chromium",
],
args = [
"$(rootpath :%s)" % name,
"$(rootpath %s)" % node_modules_label,
"$(rootpath %s)" % YARN_LABEL,
],
configuration_env_vars = ["NG_BUILD_CACHE"],
entry_point = "//aio/tools/examples:run-example-e2e",
env = {
"CHROME_BIN": "$(CHROMIUM)",
"CHROMEDRIVER_BIN": "$(CHROMEDRIVER)",
},
toolchains = [
"@aio_npm//@angular/dev-infra-private/bazel/browsers/chromium:toolchain_alias",
],
tags = test_tags,
)
LOCAL_PACKAGE_DEPS = [to_package_label(dep) for dep in ALL_PACKAGES]
# Local package deps are passed as args to the test script in the form "@package/name#path/to/package"
# for the script's convenience.
LOCAL_PACKAGE_ARGS = ["%s#$(rootpath %s)" % (dep, to_package_label(dep)) for dep in ALL_PACKAGES]
nodejs_test(
name = "e2e",
data = [
":%s" % name,
YARN_LABEL,
"@aio_npm//@angular/dev-infra-private/bazel/browsers/chromium",
"//aio/tools/examples:run-example-e2e",
# We install the whole node modules for runtime deps of e2e tests
"@{workspace}//:node_modules_with_bins".format(workspace = EXAMPLE_DEPS_WORKSPACE_NAME),
] + select({
"//aio:aio_local_deps": LOCAL_PACKAGE_DEPS,
"//conditions:default": [],
}),
args = [
"$(rootpath :%s)" % name,
"$(rootpath %s)" % YARN_LABEL,
EXAMPLE_DEPS_WORKSPACE_NAME,
] + select({
"//aio:aio_local_deps": LOCAL_PACKAGE_ARGS,
"//conditions:default": [],
}),
configuration_env_vars = ["NG_BUILD_CACHE"],
entry_point = "//aio/tools/examples:run-example-e2e",
env = {
"CHROME_BIN": "$(CHROMIUM)",
"CHROMEDRIVER_BIN": "$(CHROMEDRIVER)",
},
toolchains = [
"@aio_npm//@angular/dev-infra-private/bazel/browsers/chromium:toolchain_alias",
],
tags = test_tags,
)
+1 -1
View File
@@ -117,8 +117,8 @@
"@bazel/bazelisk": "^1.7.5",
"@bazel/buildozer": "^5.1.0",
"@bazel/jasmine": "^5.4.1",
"@bazel/typescript": "5.3.1",
"@bazel/runfiles": "5.4.2",
"@bazel/typescript": "5.3.1",
"@types/jasmine": "~4.3.0",
"@types/lunr": "^2.3.3",
"@types/node": "^12.7.9",
+10 -1
View File
@@ -17,7 +17,12 @@ export async function resolve(specifier, context, defaultResolve) {
return defaultResolve(specifier, context, defaultResolve);
}
const nodeModules = path.resolve('external', process.env.NODE_MODULES_WORKSPACE_NAME, 'node_modules');
let nodeModules;
if (isBazelRunOrTestAction()) {
nodeModules = path.resolve('../', process.env.NODE_MODULES_WORKSPACE_NAME, 'node_modules');
} else {
nodeModules = path.resolve('external', process.env.NODE_MODULES_WORKSPACE_NAME, 'node_modules');
}
const packageImport = parsePackageImport(specifier);
const pathToNodeModule = path.join(nodeModules, packageImport.packageName);
@@ -54,3 +59,7 @@ function resolvePackageLocalFilepath(packageImport, packageJson) {
return packageImport.pathInPackage || packageJson.module || packageJson.main || 'index.js';
}
function isBazelRunOrTestAction() {
return process.env.TEST_WORKSPACE || process.env.BUILD_WORKSPACE_DIRECTORY;
}
+1
View File
@@ -65,6 +65,7 @@ js_library(
deps = [
"@aio_npm//@bazel/runfiles",
"@aio_npm//canonical-path",
"@aio_npm//cjson",
"@aio_npm//cross-spawn",
"@aio_npm//fs-extra",
"@aio_npm//globby",
+102 -9
View File
@@ -1,7 +1,8 @@
import path from 'canonical-path';
import {spawn} from 'cross-spawn';
import fs from 'fs-extra';
import {sync as globbySync} from 'globby';
import {globbySync} from 'globby';
import jsonc from 'cjson';
import os from 'os';
import shelljs from 'shelljs';
import treeKill from 'tree-kill';
@@ -20,27 +21,42 @@ process.env.CHROMEDRIVER_BIN = path.resolve(process.env.CHROMEDRIVER_BIN);
const {argv} = yargs(hideBin(process.argv));
const EXAMPLE_PATH = path.resolve(argv._[0]);
const NODE_MODULES_PATH = path.resolve(argv._[1]);
const NODE = process.execPath;
const VENDORED_YARN = path.resolve(argv._[2]);
const VENDORED_YARN = path.resolve(argv._[1]);
const EXAMPLE_DEPS_WORKSPACE_NAME = argv._[2];
const LOCAL_PACKAGES = argv._.slice(3).reduce((pkgs, pkgNameAndPath) => {
const [pkgName, pkgPath] = pkgNameAndPath.split('#');
pkgs[pkgName] = path.resolve(pkgPath);
return pkgs;
}, {});
const SJS_SPEC_FILENAME = 'e2e-spec.ts';
const CLI_SPEC_FILENAME = 'e2e/src/app.e2e-spec.ts';
const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
const MAX_NO_OUTPUT_TIMEOUT = 1000 * 60 * 5; // 5 minutes
/**
* Run Protractor End-to-End Tests for Doc Samples
* Run Protractor End-to-End Tests for a Docs Example
*
* Usage: node run-example-e2e.mjs <examplePath> <yarnPath> <exampleDepsWorkspaceName> [localPackage...]
*
* Args:
* examplePath: path to the example
* yarnPath: path to a vendored version of yarn
* exampleDepsWorkspaceName: name of bazel workspace containing example node_omodules
* localPackages: a vararg of local packages to substitute in place npm deps, in the form @package/name#pathToPackage.
*
* Flags
* --retry to retry failed tests (useful for overcoming flakes)
* e.g. --retry 3 // To try each test up to 3 times.
*/
async function runE2e(examplePath, nodeModulesPath) {
async function runE2e(examplePath) {
const exampleName = path.basename(examplePath);
const maxAttempts = argv.retry || 1;
try {
examplePath = createCopyOfExampleForTest(exampleName, examplePath);
symlinkNodeModules(examplePath, nodeModulesPath);
await constructNodeModules(examplePath);
let testFn;
if (isSystemJsTest(examplePath)) {
@@ -165,8 +181,74 @@ function runProtractorAoT(exampleName, appDir) {
return runProtractorSystemJS(exampleName, promise, appDir, aotRunSpawnInfo);
}
function symlinkNodeModules(examplePath, nodeModulesPath) {
fs.ensureSymlinkSync(nodeModulesPath, path.join(examplePath, 'node_modules'), 'dir');
async function constructNodeModules(examplePath) {
const linkedNodeModules = path.resolve(examplePath, 'node_modules');
const exampleDepsNodeModules = path.resolve('..', EXAMPLE_DEPS_WORKSPACE_NAME, 'node_modules');
fs.ensureDirSync(linkedNodeModules);
await Promise.all([
linkExampleDeps(exampleDepsNodeModules, linkedNodeModules),
linkLocalDeps(exampleDepsNodeModules, linkedNodeModules)
]);
fs.copySync(path.join(exampleDepsNodeModules, '.bin'), path.join(linkedNodeModules, '.bin'));
pointBinSymlinksToLocalPackages(linkedNodeModules);
}
// The .bin folder is copied over from the original yarn_install repository, so the
// bin symlinks point there. When we link local packages in place of their npm equivalent,
// we need to alter those symlinks to point into the local package.
function pointBinSymlinksToLocalPackages(linkedNodeModules) {
if (os.platform() === 'win32') {
// Bins on Windows are not symlinks; they are scripts that will invoke the bin
// relative to their location. The relative path will already point to the symlinked
// local package, so no further action is required.
return;
}
const allNodeModuleBins = globbySync(['**'], {cwd: path.join(linkedNodeModules, '.bin'), onlyFiles: true});
allNodeModuleBins.forEach(bin => {
const symlinkTarget = fs.readlinkSync(path.join(linkedNodeModules, '.bin', bin));
for (const pkgName of Object.keys(LOCAL_PACKAGES)) {
const binMightBeInLocalPackage = symlinkTarget.includes(path.join(EXAMPLE_DEPS_WORKSPACE_NAME, 'node_modules', pkgName) + path.sep);
if (binMightBeInLocalPackage) {
const pathToBinWithinPackage = symlinkTarget.substring(symlinkTarget.indexOf(pkgName) + pkgName.length + path.sep.length);
const binExistsInLocalPackage = fs.existsSync(path.join(linkedNodeModules, pkgName, pathToBinWithinPackage));
if (binExistsInLocalPackage) {
// Replace the copied bin symlink with one that points to the symlinked local package.
fs.rmSync(path.join(linkedNodeModules, '.bin', bin));
fs.ensureSymlinkSync(path.join('..', pkgName, pathToBinWithinPackage), path.join(linkedNodeModules, '.bin', bin));
}
break;
}
}
});
}
function linkExampleDeps(exampleDepsNodeModules, linkedNodeModules) {
const exampleDepsPackages = getPackageNamesFromNodeModules(exampleDepsNodeModules);
return Promise.all(exampleDepsPackages
.filter(pkgName => !(pkgName in LOCAL_PACKAGES))
.map(pkgName => fs.ensureSymlink(path.join(exampleDepsNodeModules, pkgName), path.join(linkedNodeModules, pkgName), 'dir'))
);
}
async function linkLocalDeps(exampleDepsNodeModules, linkedNodeModules) {
const hasNpmDepForPkg = await Promise.all(Object.keys(LOCAL_PACKAGES).map(pkgName => fs.pathExists(path.join(exampleDepsNodeModules, pkgName))));
return Promise.all(Object.keys(LOCAL_PACKAGES).filter((pkgName, i) => hasNpmDepForPkg[i])
.map(pkgName => fs.ensureSymlinkSync(LOCAL_PACKAGES[pkgName], path.join(linkedNodeModules, pkgName), 'dir')));
}
function getPackageNamesFromNodeModules(nodeModulesPath) {
return globbySync([
'@*/*',
'!@*$', // Exclude a namespace folder itself
'(?!@)*',
'!.bin',
'!.yarn-integrity',
'!_*'
], {cwd: nodeModulesPath, onlyDirectories: true, dot: true});
}
// Start the example in appDir; then run protractor with the specified
@@ -188,6 +270,16 @@ function runE2eTestsCLI(exampleName, appDir) {
}
}
// When local packages are symlinked in, node has trouble resolving some peer deps. Setting
// preserveSymlinks: true in angular.json fixes this. This isn't required without local
// packages because in the worst case we would leak into the original Bazel repository
// and it would still find a node_modules folder for resolution.
if (Object.keys(LOCAL_PACKAGES).length > 0) {
const angularJson = jsonc.load(path.join(appDir, 'angular.json'), {encoding: 'utf-8'});
angularJson.projects['angular.io-example'].architect.build.options.preserveSymlinks = true;
fs.writeFileSync(path.join(appDir, 'angular.json'), JSON.stringify(angularJson));
}
// `--no-webdriver-update` is needed to preserve the ChromeDriver version already installed.
const testCommands = config.tests || [{
cmd: NODE,
@@ -306,4 +398,5 @@ function adjustChromeBinPathForWindows() {
return process.env.CHROME_BIN;
}
runE2e(EXAMPLE_PATH, NODE_MODULES_PATH);
runE2e(EXAMPLE_PATH);
-12
View File
@@ -1,6 +1,3 @@
load("//aio/tools/ng-packages-installer:node_modules.bzl", "node_modules")
load("//:packages.bzl", "AIO_EXAMPLE_PACKAGES", "to_package_label")
package(default_visibility = ["//visibility:public"])
filegroup(
@@ -10,12 +7,3 @@ filegroup(
exclude = ["BUILD.bazel"],
),
)
node_modules(
name = "node_modules",
)
node_modules(
name = "local/node_modules",
local_package_substitutions = [to_package_label(pkg) for pkg in AIO_EXAMPLE_PACKAGES],
)
@@ -1,23 +0,0 @@
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:jasmine/recommended',
],
env: {
es2020: true,
jasmine: true,
node: true,
},
plugins: [
'jasmine',
],
rules: {
'indent': ['error', 2],
'linebreak-style': ['error', 'unix'],
'max-len': ['error', 120],
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
'jasmine/new-line-before-expect': ['off'],
},
};
@@ -1,25 +0,0 @@
load("//aio/tools:defaults.bzl", "nodejs_binary")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
package(default_visibility = ["//visibility:public"])
js_library(
name = "lib",
srcs = ["index.js"],
deps = [
"@aio_npm//@yarnpkg/lockfile",
"@aio_npm//canonical-path",
"@aio_npm//fs-extra",
"@aio_npm//semver",
"@aio_npm//shelljs",
"@aio_npm//yargs",
],
)
nodejs_binary(
name = "ng-packages-installer",
data = [
":lib",
],
entry_point = ":lib",
)
-336
View File
@@ -1,336 +0,0 @@
'use strict';
const fs = require('fs-extra');
const lockfile = require('@yarnpkg/lockfile');
const path = require('canonical-path');
const semver = require('semver');
const shelljs = require('shelljs');
const yargs = require('yargs');
const PACKAGE_JSON = 'package.json';
const YARN_LOCK = 'yarn.lock';
/**
* A tool that creates a node_modules folder with optional dependencies from locally
* built distributables.
*
* This tool is used to change dependencies of the `aio` application and the example
* applications to point to locally build angular packages.
*/
class NgPackagesInstaller {
/**
* Install node_modules for a project in the specified directory.
*
* @param {string} outputDir - path to output the node_modules directory from the bazel execroot.
* @param {string} packageJson - path to package.json file from the bazel execroot.
* @param {string} yarnLock - path to the yarn lockfile from the bazel execroot.
* @param {object} options - a hash of options for the install:
* * `debug` (`boolean`) - whether to display debug messages.
* * `localPackages` (`string[]`) - list of paths to local packages to substitute for their third-party equivalents
* * `modulesFolder` (`string`) - name of the resulting node_modules folder
*/
constructor(outputDir, packageJson, yarnLock, options = {}) {
this.debug = this._parseBooleanArg(options.debug);
this.buildPackages = this._parseBooleanArg(options.buildPackages);
this.modulesFolder = options.modulesFolder;
this.localPackages = options.localPackages || [];
this.outputDir = path.resolve(outputDir);
this.packageJson = path.resolve(packageJson);
this.yarnLock = path.resolve(yarnLock);
this._log('Output directory:', this.outputDir);
}
/**
* Install locally built dependencies, overriding the dependencies in the `package.json`.
*/
installLocalDependencies() {
// Copy package.json, yarn.lock, and the locally-built packages to a temporary directory
// to form node_modules.
//
// 1. We cannot use Yarn --modules-folder (it causes issues when resolving bins for postinstall scripts)
// so we always need to put into $CWD/node_modules`
// 2. To avoid conflicts with multiple such targets in the same Bazel package --> we construct
// the folder in a temporary directory and copy it over to the destination directory.
const tempDir = path.join(this.outputDir, this.modulesFolder + '_tmp');
const pathToPackageConfig = path.resolve(tempDir, PACKAGE_JSON);
fs.copySync(this.packageJson, pathToPackageConfig)
fs.chmodSync(path.join(tempDir, PACKAGE_JSON), '755');
const pathToLockfile = path.resolve(tempDir, YARN_LOCK);
fs.copySync(this.yarnLock, pathToLockfile)
fs.chmodSync(path.join(tempDir, YARN_LOCK), '755');
const packageConfigFile = fs.readFileSync(pathToPackageConfig, 'utf8');
const packageConfig = JSON.parse(packageConfigFile);
const parsedLockfile = this._parseLockfile(pathToLockfile);
try {
if (this.localPackages.length) {
const localPackagesDir = path.join(tempDir, 'local_packages');
this._copyLocalPackagesTo(localPackagesDir);
const packages = this._getDistPackages(localPackagesDir);
// Overwrite local packages dependencies to other packages with local files.
Object.keys(packages).forEach(key => {
const pkg = packages[key];
const tmpConfig = JSON.parse(JSON.stringify(pkg.config));
// Prevent accidental publishing of the package, if something goes wrong.
tmpConfig.private = true;
// Overwrite project dependencies/devDependencies to packages with local files.
['dependencies', 'devDependencies'].forEach(prop => {
const deps = tmpConfig[prop] || {};
Object.keys(deps).forEach(key2 => {
const pkg2 = packages[key2];
if (pkg2) {
// point the local packages at the distributable folder
deps[key2] = `file:${pkg2.packageDir}`;
this._log(`Overriding dependency of local ${key} with local package: ${key2}: ${deps[key2]}`);
}
});
});
// Overwrite the package's version to avoid version mismatch errors with the CLI.
this._overwritePackageVersion(key, tmpConfig, packageConfig, parsedLockfile);
fs.writeFileSync(pkg.packageJsonPath, JSON.stringify(tmpConfig, null, 2));
});
const [dependencies, peers] = this._collectDependencies(packageConfig.dependencies || {}, packages);
const [devDependencies, devPeers] = this._collectDependencies(packageConfig.devDependencies || {}, packages);
this._assignPeerDependencies(peers, dependencies, devDependencies, parsedLockfile);
this._assignPeerDependencies(devPeers, dependencies, devDependencies, parsedLockfile);
const localPackageConfig = Object.assign(Object.create(null), packageConfig, { dependencies, devDependencies });
localPackageConfig.__angular = { local: true };
const localPackageConfigJson = JSON.stringify(localPackageConfig, null, 2);
this._log(`Writing temporary local ${PACKAGE_JSON} to ${pathToPackageConfig}`);
fs.writeFileSync(pathToPackageConfig, localPackageConfigJson);
}
this._installDeps('--pure-lockfile', '--cwd', tempDir);
// We change the name of the node_modules folder manually rather than using yarn's --modules-folder argument because it
// doesn't work well with invoking .bin executables in yarn scripts (https://github.com/yarnpkg/yarn/issues/8134).
fs.moveSync(path.join(tempDir, 'node_modules'), path.join(this.outputDir, this.modulesFolder), {overwrite: true});
} finally {
fs.rmSync(tempDir, {recursive: true});
}
}
/**
* Reinstall the original package.json dependencies
* Yarn will also delete the local marker file for us.
*/
restoreNpmDependencies() {
this._installDeps('--frozen-lockfile', '--check-files');
}
_copyLocalPackagesTo(dest) {
for (let pkg of this.localPackages) {
// Get the package name from the path: .../{packageName}/npm_package.
const name = path.basename(path.resolve(pkg, ".."))
fs.copySync(pkg, path.join(dest, name))
fs.chmodSync(path.join(dest, name, PACKAGE_JSON), '755')
}
}
_assignPeerDependencies(peerDependencies, dependencies, devDependencies, parsedLockfile) {
Object.keys(peerDependencies).forEach(key => {
const peerDepRange = peerDependencies[key];
// Ignore peerDependencies whose range is already satisfied by current version in lockfile.
const originalRange = dependencies[key] || devDependencies[key];
const lockfileVersion = originalRange && parsedLockfile[`${key}@${originalRange}`].version;
if (lockfileVersion && semver.satisfies(lockfileVersion, peerDepRange)) return;
// If there is already an equivalent dependency then override it - otherwise assign/override the devDependency
if (dependencies[key]) {
this._log(`Overriding dependency with peerDependency: ${key}: ${peerDepRange}`);
dependencies[key] = peerDepRange;
} else {
this._log(
`${devDependencies[key] ? 'Overriding' : 'Assigning'} devDependency with peerDependency: ` +
`${key}: ${peerDepRange}`);
devDependencies[key] = peerDepRange;
}
});
}
_collectDependencies(dependencies, packages) {
const peerDependencies = Object.create(null);
const mergedDependencies = Object.assign(Object.create(null), dependencies);
Object.keys(dependencies).forEach(key => {
const sourcePackage = packages[key];
if (sourcePackage) {
// point the core Angular packages at the distributable folder
mergedDependencies[key] = `file:${sourcePackage.packageDir}`;
this._log(`Overriding dependency with local package: ${key}: ${mergedDependencies[key]}`);
// grab peer dependencies
const sourcePackagePeerDeps = sourcePackage.config.peerDependencies || {};
Object.keys(sourcePackagePeerDeps)
// ignore peerDependencies which are already core Angular/Zone.js packages
.filter(key => !packages[key])
.forEach(key => peerDependencies[key] = sourcePackagePeerDeps[key]);
}
});
return [mergedDependencies, peerDependencies];
}
/**
* A hash of local package configs included with --local-packages
*/
_getDistPackages(containingDir) {
const collectPackages = containingDir => {
const packages = {};
for (const dirName of shelljs.ls(containingDir)) {
const packageDir = path.resolve(containingDir, dirName);
const packageJsonPath = path.join(packageDir, PACKAGE_JSON);
const packageConfig = fs.existsSync(packageJsonPath) ? require(packageJsonPath) : null;
const packageName = packageConfig && packageConfig.name;
if (!packageConfig) {
// No `package.json` found - this directory is not a package.
continue;
} else if (!packageName) {
// No `name` property in `package.json`. (This should never happen.)
throw new Error(`Package '${packageDir}' specifies no name in its '${PACKAGE_JSON}'.`);
}
packages[packageName] = {
packageDir,
packageJsonPath,
config: packageConfig,
};
}
return packages;
};
const packageConfigs = {
...collectPackages(containingDir)
};
this._log('Found the following distributables:', ...Object.keys(packageConfigs).map(key => `\n - ${key}`));
return packageConfigs;
}
_installDeps(...options) {
const command = `${process.execPath} ${process.env.YARN} install ${options.join(' ')}`;
this._log('Installing dependencies with:', command);
shelljs.exec(command);
}
/**
* Log a message if the `debug` property is set to true.
* @param {string[]} messages - The messages to be logged.
*/
_log(...messages) {
if (this.debug) {
const header = ` [${NgPackagesInstaller.name}]: `;
const indent = ' '.repeat(header.length);
const message = messages.join(' ');
console.info(`${header}${message.split('\n').join(`\n${indent}`)}`);
}
}
/**
* Update a package's version with the fake version based on the package's original version in the projects's
* lockfile.
*
* **Background:**
* This helps avoid version mismatch errors with the CLI.
* Since the version set by bazel on the locally built packages is determined based on the latest tag for a commit on
* the current branch, it is often the case that this version is older than what the current `@angular/cli` version is
* compatible with (e.g. if the user has not fetched the latest tags from `angular/angular` or the branch has not been
* rebased recently.
*
* @param {string} packageName - The name of the package we are updating (e.g. `'@angular/core'`).
* @param {{[key: string]: any}} packageConfig - The package's parsed `package.json`.
* @param {{[key: string]: any}} projectConfig - The project's parsed `package.json`.
* @param {import('@yarnpkg/lockfile').LockFileObject} projectLockfile - The projects's parsed `yarn.lock`.
*/
_overwritePackageVersion(packageName, packageConfig, projectConfig, projectLockfile) {
const projectVersionRange = (projectConfig.dependencies || {})[packageName] ||
(projectConfig.devDependencies || {})[packageName];
const projectVersion = (projectLockfile[`${packageName}@${projectVersionRange}`] || {}).version;
if (projectVersion !== undefined) {
const newVersion = `${projectVersion}+locally-overwritten-by-ngPackagesInstaller`;
this._log(`Overwriting the version of '${packageName}': ${packageConfig.version} --> ${newVersion}`);
packageConfig.version = newVersion;
}
}
/**
* Extract the value for a boolean cli argument/option. When passing an option multiple times, `yargs` parses it as an
* array of boolean values. In that case, we only care about the last occurrence.
*
* This can be useful, for example, when one has a base command with the option turned on and another command
* (building on top of the first one) turning the option off:
* ```
* "base-command": "my-script --foo --bar",
* "no-bar-command": "yarn base-command --no-bar",
* ```
*/
_parseBooleanArg(value) {
return Array.isArray(value) ? value.pop() : value;
}
/**
* Parse and return a `yarn.lock` file.
*/
_parseLockfile(lockfilePath) {
const lockfileContent = fs.readFileSync(lockfilePath, 'utf8');
const parsed = lockfile.parse(lockfileContent);
if (parsed.type !== 'success') {
throw new Error(
`[${NgPackagesInstaller.name}]: Error parsing lockfile '${lockfilePath}' (result type: ${parsed.type}).`);
}
return parsed.object;
}
}
function main() {
shelljs.set('-e');
const createInstaller = argv => {
const {outputDir, packageJson, yarnLock, ...options} = argv;
return new NgPackagesInstaller(outputDir, packageJson, yarnLock, options);
};
/* eslint-disable max-len */
yargs
.usage('$0 [args]')
.option('debug', { describe: 'Print additional debug information.', default: false })
.option('local-packages', { describe: 'List of locally built packages that should be substituted in place of their npm equivalent.', default: [], array: true })
.option('modules-folder', { describe: 'Name of the node_modules folder.', default: 'node_modules'})
.command('* <outputDir> <packageJson> <yarnLock> [--debug] [--local-packages package1Path package2Path] [--modules-folder node_modules]', 'Install dependencies from the locally built Angular/Zone.js distributables.', () => {}, argv => {
createInstaller(argv).installLocalDependencies();
})
.strict()
.wrap(yargs.terminalWidth())
.argv;
/* eslint-enable max-len */
}
module.exports = NgPackagesInstaller;
if (require.main === module) {
main();
}
@@ -1,587 +0,0 @@
'use strict';
const fs = require('fs-extra');
const lockfile = require('@yarnpkg/lockfile');
const path = require('canonical-path');
const shelljs = require('shelljs');
const NgPackagesInstaller = require('./index');
describe('NgPackagesInstaller', () => {
const projectDir = 'root/dir';
const absoluteProjectDir = path.resolve(projectDir);
const nodeModulesDir = path.resolve(absoluteProjectDir, 'node_modules');
const packageJsonPath = path.resolve(absoluteProjectDir, 'package.json');
const yarnLockPath = path.resolve(absoluteProjectDir, 'yarn.lock');
const ngRootDir = path.resolve(__dirname, '../../..');
const packagesDir = path.join(ngRootDir, 'dist/packages-dist');
const aimwaDir = path.join(ngRootDir, 'dist/angular-in-memory-web-api-dist');
const zoneJsDir = path.join(ngRootDir, 'dist/zone.js-dist');
const toolsDir = path.join(ngRootDir, 'dist/tools/@angular');
let installer;
beforeEach(() => {
spyOn(fs, 'existsSync');
spyOn(fs, 'readFileSync');
spyOn(fs, 'writeFileSync');
spyOn(shelljs, 'exec');
spyOn(shelljs, 'rm');
spyOn(console, 'log');
spyOn(console, 'warn');
installer = new NgPackagesInstaller(projectDir);
});
describe('checkDependencies()', () => {
beforeEach(() => {
spyOn(installer, '_printWarning');
});
it('should not print a warning if there is no _local_.json file', () => {
fs.existsSync.and.returnValue(false);
installer.checkDependencies();
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(projectDir, 'node_modules/_local_.json'));
expect(installer._printWarning).not.toHaveBeenCalled();
});
it('should print a warning if there is a _local_.json file', () => {
fs.existsSync.and.returnValue(true);
installer.checkDependencies();
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(projectDir, 'node_modules/_local_.json'));
expect(installer._printWarning).toHaveBeenCalledWith();
});
});
describe('installLocalDependencies()', () => {
const copyJsonObj = obj => JSON.parse(JSON.stringify(obj));
let dummyLocalPackages, dummyPackage, dummyPackageJson, expectedModifiedPackage, expectedModifiedPackageJson;
beforeEach(() => {
spyOn(installer, '_checkLocalMarker');
spyOn(installer, '_installDeps');
spyOn(installer, '_setLocalMarker');
spyOn(installer, '_parseLockfile').and.returnValue({
'rxjs@^6.3.0': {version: '6.3.3'},
'rxjs-dev@^6.3.0': {version: '6.4.2'}
});
// These are the packages that are "found" in the dist directory
dummyLocalPackages = {
'@angular/core': {
packageDir: `${packagesDir}/core`,
packageJsonPath: `${packagesDir}/core/package.json`,
config: {
peerDependencies: {
'rxjs': '^6.4.0',
'rxjs-dev': '^6.4.0',
'some-package': '5.0.1',
'zone.js': '~0.8.26'
}
}
},
'@angular/common': {
packageDir: `${packagesDir}/common`,
packageJsonPath: `${packagesDir}/common/package.json`,
config: { peerDependencies: { '@angular/core': '4.4.4-1ab23cd4' } }
},
'@angular/compiler': {
packageDir: `${packagesDir}/compiler`,
packageJsonPath: `${packagesDir}/compiler/package.json`,
config: { peerDependencies: { '@angular/common': '4.4.4-1ab23cd4' } }
},
'@angular/compiler-cli': {
packageDir: `${toolsDir}/compiler-cli`,
packageJsonPath: `${toolsDir}/compiler-cli/package.json`,
config: {
dependencies: { '@angular/tsc-wrapped': '4.4.4-1ab23cd4' },
peerDependencies: { typescript: '^2.4.2', '@angular/compiler': '4.4.4-1ab23cd4' }
}
},
'@angular/tsc-wrapped': {
packageDir: `${toolsDir}/tsc-wrapped`,
packageJsonPath: `${toolsDir}/tsc-wrapped/package.json`,
config: {
devDependencies: { '@angular/common': '4.4.4-1ab23cd4' },
peerDependencies: { tsickle: '^1.4.0' }
}
},
'angular-in-memory-web-api': {
packageDir: `${aimwaDir}/angular-in-memory-web-api`,
packageJsonPath: `${aimwaDir}/angular-in-memory-web-api/package.json`,
config: {
dependencies: { rxjs: '^6.3.0' }
}
},
'zone.js': {
packageDir: `${zoneJsDir}/zone.js`,
packageJsonPath: `${zoneJsDir}/zone.js/package.json`,
config: {
devDependencies: { typescript: '^2.4.2' }
}
},
};
spyOn(installer, '_getDistPackages').and.callFake(() => copyJsonObj(dummyLocalPackages));
// This is the package.json in the "test" folder
dummyPackage = {
dependencies: {
'@angular/core': '4.4.1',
'@angular/common': '4.4.1',
rxjs: '^6.3.0',
'zone.js': '^0.8.26'
},
devDependencies: {
'@angular/compiler-cli': '4.4.1',
'angular-in-memory-web-api': '^0.11.0',
'rxjs-dev': '^6.3.0'
}
};
dummyPackageJson = JSON.stringify(dummyPackage);
fs.readFileSync.and.returnValue(dummyPackageJson);
// This is the package.json that is temporarily written to the "test" folder
// Note that the Angular/Zone.js (dev)dependencies have been modified to use a "file:" path
// and that the peerDependencies from `dummyLocalPackages` have been updated or added as
// (dev)dependencies (unless the current version in lockfile satisfies semver).
//
// For example, `rxjs-dev@6.4.2` (from lockfile) satisfies `rxjs-dev@^6.4.0` (from
// `@angular/core`), thus `rxjs-dev: ^6.3.0` (from original `package.json`) is retained.
// In contrast, `rxjs@6.3.3` (from lockfile) does not satisfy `rxjs@^6.4.0 (from
// `@angular/core`), thus `rxjs: ^6.3.0` (from original `package.json`) is replaced with
// `rxjs: ^6.4.0` (from `@angular/core`).
expectedModifiedPackage = {
dependencies: {
'@angular/core': `file:${packagesDir}/core`,
'@angular/common': `file:${packagesDir}/common`,
'rxjs': '^6.4.0',
'zone.js': `file:${zoneJsDir}/zone.js`,
},
devDependencies: {
'@angular/compiler-cli': `file:${toolsDir}/compiler-cli`,
'angular-in-memory-web-api': `file:${aimwaDir}/angular-in-memory-web-api`,
'rxjs-dev': '^6.3.0',
'some-package': '5.0.1',
typescript: '^2.4.2'
},
__angular: { local: true }
};
expectedModifiedPackageJson = JSON.stringify(expectedModifiedPackage, null, 2);
});
describe('when there is a local package marker', () => {
beforeEach(() => installer._checkLocalMarker.and.returnValue(true));
it('should not continue processing', () => {
installer.installLocalDependencies();
expect(installer._checkLocalMarker).toHaveBeenCalledWith();
expect(installer._getDistPackages).not.toHaveBeenCalled();
});
it('should continue processing (without checking for local marker) if `force` is true', () => {
installer.force = true;
installer.installLocalDependencies();
expect(installer._checkLocalMarker).not.toHaveBeenCalled();
expect(installer._getDistPackages).toHaveBeenCalledWith();
});
});
describe('when there is no local package marker', () => {
let log;
beforeEach(() => {
log = [];
fs.writeFileSync.and.callFake((filePath, contents) =>
filePath === packageJsonPath && log.push(`writeFile: ${contents}`));
installer._installDeps.and.callFake((...args) => log.push(`installDeps: ${args.join(' ')}`));
installer._checkLocalMarker.and.returnValue(false);
installer.installLocalDependencies();
});
it('should parse the lockfile and get the dist packages', () => {
expect(installer._checkLocalMarker).toHaveBeenCalledWith();
expect(installer._parseLockfile).toHaveBeenCalledWith(yarnLockPath);
expect(installer._getDistPackages).toHaveBeenCalledWith();
});
it('should temporarily overwrite the package.json files of local Angular packages', () => {
const pkgJsonPathFor = pkgName => dummyLocalPackages[pkgName].packageJsonPath;
const pkgConfigFor = pkgName => copyJsonObj(dummyLocalPackages[pkgName].config);
const overwriteConfigFor = (pkgName, newProps) => Object.assign(pkgConfigFor(pkgName), newProps);
const stringifyConfig = config => JSON.stringify(config, null, 2);
const allArgs = fs.writeFileSync.calls.allArgs();
const firstSevenArgs = allArgs.slice(0, 7);
const lastSevenArgs = allArgs.slice(-7);
expect(firstSevenArgs).toEqual([
[
pkgJsonPathFor('@angular/core'),
stringifyConfig(overwriteConfigFor('@angular/core', {private: true})),
],
[
pkgJsonPathFor('@angular/common'),
stringifyConfig(overwriteConfigFor('@angular/common', {private: true})),
],
[
pkgJsonPathFor('@angular/compiler'),
stringifyConfig(overwriteConfigFor('@angular/compiler', {private: true})),
],
[
pkgJsonPathFor('@angular/compiler-cli'),
stringifyConfig(overwriteConfigFor('@angular/compiler-cli', {
private: true,
dependencies: { '@angular/tsc-wrapped': `file:${toolsDir}/tsc-wrapped` },
})),
],
[
pkgJsonPathFor('@angular/tsc-wrapped'),
stringifyConfig(overwriteConfigFor('@angular/tsc-wrapped', {
private: true,
devDependencies: { '@angular/common': `file:${packagesDir}/common` },
})),
],
[
pkgJsonPathFor('angular-in-memory-web-api'),
stringifyConfig(overwriteConfigFor('angular-in-memory-web-api', {private: true})),
],
[
pkgJsonPathFor('zone.js'),
stringifyConfig(overwriteConfigFor('zone.js', {private: true})),
],
]);
expect(lastSevenArgs).toEqual([
'@angular/core',
'@angular/common',
'@angular/compiler',
'@angular/compiler-cli',
'@angular/tsc-wrapped',
'angular-in-memory-web-api',
'zone.js',
].map(pkgName => [pkgJsonPathFor(pkgName), stringifyConfig(pkgConfigFor(pkgName))]));
});
it('should load the package.json', () => {
expect(fs.readFileSync).toHaveBeenCalledWith(packageJsonPath, 'utf8');
});
it('should overwrite package.json with modified config', () => {
expect(fs.writeFileSync).toHaveBeenCalledWith(packageJsonPath, expectedModifiedPackageJson);
});
it('should restore original package.json', () => {
expect(fs.writeFileSync).toHaveBeenCalledWith(packageJsonPath, dummyPackageJson);
});
it('should overwrite package.json, then install deps, then restore original package.json', () => {
expect(log).toEqual([
`writeFile: ${expectedModifiedPackageJson}`,
'installDeps: --pure-lockfile --check-files',
`writeFile: ${dummyPackageJson}`
]);
});
it('should set the local marker file with the contents of the modified package.json', () => {
expect(installer._setLocalMarker).toHaveBeenCalledWith(expectedModifiedPackageJson);
});
});
});
describe('restoreNpmDependencies()', () => {
it('should run `yarn install` in the specified directory, with the correct options', () => {
spyOn(installer, '_installDeps');
installer.restoreNpmDependencies();
expect(installer._installDeps).toHaveBeenCalledWith('--frozen-lockfile', '--check-files');
});
});
describe('_buildDistPackages()', () => {
// Call `_buildDistPackages()` with a mock `process.platform` value.
const buildDistPackagesOnPlatform = platform => {
const originalDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
Object.defineProperty(process, 'platform', {...originalDescriptor, value: platform});
installer._buildDistPackages();
Object.defineProperty(process, 'platform', originalDescriptor);
};
it('should build the local packages, when not on Windows', () => {
const buildCmd = 'yarn -s build';
buildDistPackagesOnPlatform('linux');
expect(shelljs.exec).toHaveBeenCalledWith(buildCmd, {cwd: ngRootDir});
shelljs.exec.calls.reset();
buildDistPackagesOnPlatform('darwin');
expect(shelljs.exec).toHaveBeenCalledWith(buildCmd, {cwd: ngRootDir});
shelljs.exec.calls.reset();
buildDistPackagesOnPlatform('anythingButWindows :(');
expect(shelljs.exec).toHaveBeenCalledWith(buildCmd, {cwd: ngRootDir});
});
it('should print a warning, when on Windows', () => {
buildDistPackagesOnPlatform('win32');
const warning = console.warn.calls.argsFor(0)[0];
expect(shelljs.exec).not.toHaveBeenCalled();
expect(warning).toContain(
'Automatically building the local Angular/angular-in-memory-web-api/zone.js packages is currently not ' +
'supported on Windows.');
expect(warning).toContain('Git Bash for Windows');
expect(warning).toContain('Windows Subsystem for Linux');
expect(warning).toContain('Linux docker container or VM');
});
});
describe('_getDistPackages()', () => {
beforeEach(() => {
fs.existsSync.and.callThrough();
spyOn(NgPackagesInstaller.prototype, '_buildDistPackages');
});
it('should not build the local packages by default', () => {
installer._getDistPackages();
expect(installer._buildDistPackages).not.toHaveBeenCalled();
});
it('should build the local packages, if `buildPackages` is true', () => {
installer = new NgPackagesInstaller(projectDir, {buildPackages: true});
installer._getDistPackages();
expect(installer._buildDistPackages).toHaveBeenCalledTimes(1);
});
it('should not build the local packages, if `buildPackages` is false', () => {
installer = new NgPackagesInstaller(projectDir, {buildPackages: false});
installer._getDistPackages();
expect(installer._buildDistPackages).not.toHaveBeenCalled();
});
it('should include top level Angular and Zone.js packages', () => {
const localPackages = installer._getDistPackages();
const expectedValue = jasmine.objectContaining({
packageDir: jasmine.any(String),
packageJsonPath: jasmine.any(String),
config: jasmine.any(Object),
});
// For example...
expect(localPackages['@angular/common']).toEqual(expectedValue);
expect(localPackages['@angular/core']).toEqual(expectedValue);
expect(localPackages['@angular/router']).toEqual(expectedValue);
expect(localPackages['@angular/upgrade']).toEqual(expectedValue);
expect(localPackages['zone.js']).toEqual(expectedValue);
expect(localPackages['@angular/upgrade/static']).not.toBeDefined();
});
it('should store each package\'s directory', () => {
const localPackages = installer._getDistPackages();
// For example...
expect(localPackages['@angular/core'].packageDir).toBe(path.join(packagesDir, 'core'));
expect(localPackages['@angular/router'].packageDir).toBe(path.join(packagesDir, 'router'));
expect(localPackages['zone.js'].packageDir).toBe(path.join(zoneJsDir, 'zone.js'));
});
it('should not include packages that have been ignored', () => {
installer = new NgPackagesInstaller(projectDir, { ignorePackages: ['@angular/router'] });
const localPackages = installer._getDistPackages();
expect(localPackages['@angular/common']).toBeDefined();
expect(localPackages['@angular/router']).toBeUndefined();
});
});
describe('_log()', () => {
beforeEach(() => {
spyOn(console, 'info');
});
it('should assign the debug property from the options', () => {
installer = new NgPackagesInstaller(projectDir, { debug: true });
expect(installer.debug).toBe(true);
installer = new NgPackagesInstaller(projectDir, { });
expect(installer.debug).toBe(undefined);
});
it('should log a message to the console if the `debug` property is true', () => {
installer._log('foo');
expect(console.info).not.toHaveBeenCalled();
installer.debug = true;
installer._log('bar');
expect(console.info).toHaveBeenCalledWith(' [NgPackagesInstaller]: bar');
});
});
describe('_overwritePackageVersion()', () => {
it('should do nothing if the specified package is not a dependency', () => {
const pkgConfig = {name: '@scope/missing', version: 'local-version'};
const lockFile = {
[`${pkgConfig.name}@project-range`]: {version: 'project-version'},
};
let projectConfig;
// No `dependencies`/`devDependencies` at all.
projectConfig = {};
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
expect(pkgConfig.version).toBe('local-version');
// Not listed in `dependencies`/`devDependencies`.
projectConfig = {
dependencies: {otherPackage: 'foo'},
devDependencies: {yetAnotherPackage: 'bar'},
};
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
expect(pkgConfig.version).toBe('local-version');
});
it('should do nothing if the specified package cannot be found in the lockfile', () => {
const pkgConfig = {name: '@scope/missing', version: 'local-version'};
const projectConfig = {
dependencies: {[pkgConfig.name]: 'project-range'},
};
let lockFile;
// Package missing from lockfile.
lockFile = {
'otherPackage@someRange': {version: 'some-version'},
};
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
expect(pkgConfig.version).toBe('local-version');
// Package present in lockfile, but for a different version range.
lockFile = {
[`${pkgConfig.name}@other-range`]: {version: 'project-version'},
};
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
expect(pkgConfig.version).toBe('local-version');
});
it('should overwrite the package version if it is a dependency and found in the lockfile', () => {
const pkgConfig = {name: '@scope/found', version: 'local-version'};
const lockFile = {
[`${pkgConfig.name}@project-range-prod`]: {version: 'project-version-prod'},
[`${pkgConfig.name}@project-range-dev`]: {version: 'project-version-dev'},
};
let projectConfig;
// Package in `dependencies`.
projectConfig = {
dependencies: {[pkgConfig.name]: 'project-range-prod'},
};
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
expect(pkgConfig.version).toBe('project-version-prod+locally-overwritten-by-ngPackagesInstaller');
// // Package in `devDependencies`.
projectConfig = {
devDependencies: {[pkgConfig.name]: 'project-range-dev'},
};
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
expect(pkgConfig.version).toBe('project-version-dev+locally-overwritten-by-ngPackagesInstaller');
// // Package in both `dependencies` and `devDependencies` (the former takes precedence).
projectConfig = {
devDependencies: {[pkgConfig.name]: 'project-range-dev'},
dependencies: {[pkgConfig.name]: 'project-range-prod'},
};
installer._overwritePackageVersion(pkgConfig.name, pkgConfig, projectConfig, lockFile);
expect(pkgConfig.version).toBe('project-version-prod+locally-overwritten-by-ngPackagesInstaller');
});
});
describe('_parseLockfile()', () => {
let originalLockfileParseDescriptor;
beforeEach(() => {
// Workaround for `lockfile.parse()` being non-writable.
let parse = lockfile.parse;
originalLockfileParseDescriptor = Object.getOwnPropertyDescriptor(lockfile, 'parse');
Object.defineProperty(lockfile, 'parse', {
get() { return parse; },
set(newParse) { parse = newParse; },
});
fs.readFileSync.and.returnValue('mock content');
spyOn(lockfile, 'parse').and.returnValue({type: 'success', object: {foo: {version: 'bar'}}});
});
afterEach(() => Object.defineProperty(lockfile, 'parse', originalLockfileParseDescriptor));
it('should parse the specified lockfile', () => {
installer._parseLockfile('/foo/bar/yarn.lock');
expect(fs.readFileSync).toHaveBeenCalledWith('/foo/bar/yarn.lock', 'utf8');
expect(lockfile.parse).toHaveBeenCalledWith('mock content');
});
it('should throw if parsing the lockfile fails', () => {
lockfile.parse.and.returnValue({type: 'not success'});
expect(() => installer._parseLockfile('/foo/bar/yarn.lock')).toThrowError(
'[NgPackagesInstaller]: Error parsing lockfile \'/foo/bar/yarn.lock\' (result type: not success).');
});
it('should return the parsed lockfile content as an object', () => {
const parsed = installer._parseLockfile('/foo/bar/yarn.lock');
expect(parsed).toEqual({foo: {version: 'bar'}});
});
});
describe('_printWarning()', () => {
it('should mention the message passed in the warning', () => {
installer._printWarning();
expect(console.warn.calls.argsFor(0)[0]).toContain('is running against the local Angular/Zone.js build');
});
it('should mention the command to restore the Angular packages in any warning', () => {
// When run for the current working directory...
const dir1 = '.';
const restoreCmdRe1 = RegExp('\\bnode .*?ng-packages-installer/index restore ' + path.resolve(dir1));
installer = new NgPackagesInstaller(dir1);
installer._printWarning('');
expect(console.warn.calls.argsFor(0)[0]).toMatch(restoreCmdRe1);
// When run for a different directory...
const dir2 = projectDir;
const restoreCmdRe2 = RegExp(`\\bnode .*?ng-packages-installer/index restore .*?${path.resolve(dir1)}\\b`);
installer = new NgPackagesInstaller(dir2);
installer._printWarning('');
expect(console.warn.calls.argsFor(1)[0]).toMatch(restoreCmdRe2);
});
});
describe('_installDeps()', () => {
it('should run yarn install with the given options', () => {
installer._installDeps('option-1', 'option-2');
expect(shelljs.exec).toHaveBeenCalledWith('yarn install option-1 option-2', { cwd: absoluteProjectDir });
});
});
describe('local marker helpers', () => {
let installer;
beforeEach(() => {
installer = new NgPackagesInstaller(projectDir);
});
describe('_checkLocalMarker', () => {
it ('should return true if the local marker file exists', () => {
fs.existsSync.and.returnValue(true);
expect(installer._checkLocalMarker()).toEqual(true);
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(nodeModulesDir, '_local_.json'));
fs.existsSync.calls.reset();
fs.existsSync.and.returnValue(false);
expect(installer._checkLocalMarker()).toEqual(false);
expect(fs.existsSync).toHaveBeenCalledWith(path.resolve(nodeModulesDir, '_local_.json'));
});
});
describe('_setLocalMarker', () => {
it('should create a local marker file', () => {
installer._setLocalMarker('test contents');
expect(fs.writeFileSync).toHaveBeenCalledWith(path.resolve(nodeModulesDir, '_local_.json'), 'test contents');
});
});
});
});
@@ -1,43 +0,0 @@
# 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
"""Utilities to run yarn install as a build target"""
load("@build_bazel_rules_nodejs//:index.bzl", "npm_package_bin")
load("//:yarn.bzl", "YARN_LABEL", "YARN_PATH")
def node_modules(name = "node_modules", package_json = "package.json", yarn_lock = "yarn.lock", local_package_substitutions = []):
"""Install node modules as a tree artifact.
Args:
name: Optional name of the node_modules folder (default is node_modules)
package_json: Optional label to package.json if not in the same package
yarn_lock: Optional label to yarn.lock if not in the same package
local_package_substitutions: Optional list of locally built packages to substitute
for their npm equivalent
"""
npm_package_bin(
name = name,
args = [
"$(RULEDIR)",
"$(execpath %s)" % package_json,
"$(execpath %s)" % yarn_lock,
"--modules-folder",
name,
"--local-packages",
] + [
"$(execpath %s)" % pkg
for pkg in local_package_substitutions
],
data = [
YARN_LABEL,
package_json,
yarn_lock,
] + local_package_substitutions,
env = {
"YARN": YARN_PATH,
},
output_dir = True,
tool = "//aio/tools/ng-packages-installer",
)
+1 -1
View File
@@ -28,7 +28,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
"//packages/compiler-cli/integrationtest:__pkg__",
"//packages/compiler/test:__pkg__",
+1 -1
View File
@@ -32,7 +32,7 @@ pkg_npm(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
],
deps = [
+1 -1
View File
@@ -47,7 +47,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
"//packages/bazel/test/ng_package:__pkg__",
"//packages/compiler-cli/integrationtest:__pkg__",
+1 -1
View File
@@ -132,7 +132,7 @@ pkg_npm(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
"//packages/compiler-cli/integrationtest:__pkg__",
],
+1 -1
View File
@@ -30,7 +30,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
"//packages/compiler-cli/integrationtest:__pkg__",
"//packages/language-service/test:__pkg__",
+1 -1
View File
@@ -61,7 +61,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
"//packages/bazel/test/ng_package:__pkg__",
"//packages/compiler-cli/integrationtest:__pkg__",
+1 -1
View File
@@ -28,7 +28,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
],
deps = [
+1 -1
View File
@@ -28,7 +28,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
"//packages/compiler-cli/integrationtest:__pkg__",
"//packages/compiler-cli/test/diagnostics:__pkg__",
+1 -1
View File
@@ -27,7 +27,7 @@ pkg_npm(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
],
deps = [
@@ -32,7 +32,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
"//packages/compiler-cli/integrationtest:__pkg__",
],
+1 -1
View File
@@ -38,7 +38,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
"//packages/compiler-cli/integrationtest:__pkg__",
"//packages/compiler-cli/test:__pkg__",
+1 -1
View File
@@ -49,7 +49,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
"//packages/compiler-cli/integrationtest:__pkg__",
],
+1 -1
View File
@@ -31,7 +31,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
"//packages/compiler-cli/integrationtest:__pkg__",
"//packages/compiler-cli/test:__pkg__",
+1 -1
View File
@@ -50,7 +50,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
],
deps = [
+1 -1
View File
@@ -31,7 +31,7 @@ ng_package(
# Dependencies on the full npm_package cause long re-builds.
visibility = [
"//aio:__pkg__",
"//aio/tools/examples/shared:__pkg__",
"//aio/content/examples:__subpackages__",
"//integration:__subpackages__",
],
deps = [