diff --git a/WORKSPACE b/WORKSPACE index 665c577c730..7e2eb66d4a2 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -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() diff --git a/aio/content/examples/examples.bzl b/aio/content/examples/examples.bzl index 2412cfae1e7..ce9192d5b80 100644 --- a/aio/content/examples/examples.bzl +++ b/aio/content/examples/examples.bzl @@ -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, + ) diff --git a/aio/package.json b/aio/package.json index 5afbd34c156..feda5b938f0 100644 --- a/aio/package.json +++ b/aio/package.json @@ -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", diff --git a/aio/tools/esm-loader/esm-loader.mjs b/aio/tools/esm-loader/esm-loader.mjs index 3f955ab42a1..f0c0451b0f6 100644 --- a/aio/tools/esm-loader/esm-loader.mjs +++ b/aio/tools/esm-loader/esm-loader.mjs @@ -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; +} diff --git a/aio/tools/examples/BUILD.bazel b/aio/tools/examples/BUILD.bazel index 8d21005923c..9e6efb8f894 100644 --- a/aio/tools/examples/BUILD.bazel +++ b/aio/tools/examples/BUILD.bazel @@ -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", diff --git a/aio/tools/examples/run-example-e2e.mjs b/aio/tools/examples/run-example-e2e.mjs index b77d4e9849d..8b6616e2e67 100644 --- a/aio/tools/examples/run-example-e2e.mjs +++ b/aio/tools/examples/run-example-e2e.mjs @@ -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 [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); + diff --git a/aio/tools/examples/shared/BUILD.bazel b/aio/tools/examples/shared/BUILD.bazel index 9b146582850..e8fdf3bb7d6 100644 --- a/aio/tools/examples/shared/BUILD.bazel +++ b/aio/tools/examples/shared/BUILD.bazel @@ -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], -) diff --git a/aio/tools/ng-packages-installer/.eslintrc.js b/aio/tools/ng-packages-installer/.eslintrc.js deleted file mode 100644 index 95fcef6b503..00000000000 --- a/aio/tools/ng-packages-installer/.eslintrc.js +++ /dev/null @@ -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'], - }, -}; diff --git a/aio/tools/ng-packages-installer/BUILD.bazel b/aio/tools/ng-packages-installer/BUILD.bazel deleted file mode 100644 index a31c59c96ab..00000000000 --- a/aio/tools/ng-packages-installer/BUILD.bazel +++ /dev/null @@ -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", -) diff --git a/aio/tools/ng-packages-installer/index.js b/aio/tools/ng-packages-installer/index.js deleted file mode 100644 index c5d7bdea9e2..00000000000 --- a/aio/tools/ng-packages-installer/index.js +++ /dev/null @@ -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('* [--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(); -} diff --git a/aio/tools/ng-packages-installer/index.spec.js b/aio/tools/ng-packages-installer/index.spec.js deleted file mode 100644 index d9d59813622..00000000000 --- a/aio/tools/ng-packages-installer/index.spec.js +++ /dev/null @@ -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'); - }); - }); - }); -}); diff --git a/aio/tools/ng-packages-installer/node_modules.bzl b/aio/tools/ng-packages-installer/node_modules.bzl deleted file mode 100644 index ac761eaafb2..00000000000 --- a/aio/tools/ng-packages-installer/node_modules.bzl +++ /dev/null @@ -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", - ) diff --git a/packages/animations/BUILD.bazel b/packages/animations/BUILD.bazel index 321893f38f1..3f8bd91d4ba 100644 --- a/packages/animations/BUILD.bazel +++ b/packages/animations/BUILD.bazel @@ -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__", diff --git a/packages/bazel/BUILD.bazel b/packages/bazel/BUILD.bazel index 23fe091ab94..99e1bf4080e 100644 --- a/packages/bazel/BUILD.bazel +++ b/packages/bazel/BUILD.bazel @@ -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 = [ diff --git a/packages/common/BUILD.bazel b/packages/common/BUILD.bazel index 952a17924c9..6d77fb7035f 100644 --- a/packages/common/BUILD.bazel +++ b/packages/common/BUILD.bazel @@ -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__", diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index 9e04786d833..8810e163da5 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -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__", ], diff --git a/packages/compiler/BUILD.bazel b/packages/compiler/BUILD.bazel index 67602c6e348..30cf72e74fd 100644 --- a/packages/compiler/BUILD.bazel +++ b/packages/compiler/BUILD.bazel @@ -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__", diff --git a/packages/core/BUILD.bazel b/packages/core/BUILD.bazel index 4ba4ce3e58b..086010af6ed 100644 --- a/packages/core/BUILD.bazel +++ b/packages/core/BUILD.bazel @@ -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__", diff --git a/packages/elements/BUILD.bazel b/packages/elements/BUILD.bazel index c4ad40a9930..71ae65ecd36 100644 --- a/packages/elements/BUILD.bazel +++ b/packages/elements/BUILD.bazel @@ -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 = [ diff --git a/packages/forms/BUILD.bazel b/packages/forms/BUILD.bazel index 56e1c964640..a1cc0f5224c 100644 --- a/packages/forms/BUILD.bazel +++ b/packages/forms/BUILD.bazel @@ -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__", diff --git a/packages/language-service/BUILD.bazel b/packages/language-service/BUILD.bazel index b1dab138d85..f3770d59e78 100644 --- a/packages/language-service/BUILD.bazel +++ b/packages/language-service/BUILD.bazel @@ -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 = [ diff --git a/packages/platform-browser-dynamic/BUILD.bazel b/packages/platform-browser-dynamic/BUILD.bazel index 1a24852332d..b02279c6f66 100644 --- a/packages/platform-browser-dynamic/BUILD.bazel +++ b/packages/platform-browser-dynamic/BUILD.bazel @@ -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__", ], diff --git a/packages/platform-browser/BUILD.bazel b/packages/platform-browser/BUILD.bazel index 5645ea24765..3a8fa52eb46 100644 --- a/packages/platform-browser/BUILD.bazel +++ b/packages/platform-browser/BUILD.bazel @@ -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__", diff --git a/packages/platform-server/BUILD.bazel b/packages/platform-server/BUILD.bazel index 2626c71d44a..099bb4f1c97 100644 --- a/packages/platform-server/BUILD.bazel +++ b/packages/platform-server/BUILD.bazel @@ -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__", ], diff --git a/packages/router/BUILD.bazel b/packages/router/BUILD.bazel index a7aa96929f7..365a1bc880f 100644 --- a/packages/router/BUILD.bazel +++ b/packages/router/BUILD.bazel @@ -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__", diff --git a/packages/service-worker/BUILD.bazel b/packages/service-worker/BUILD.bazel index 14285c425b1..51ae73d70da 100644 --- a/packages/service-worker/BUILD.bazel +++ b/packages/service-worker/BUILD.bazel @@ -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 = [ diff --git a/packages/upgrade/BUILD.bazel b/packages/upgrade/BUILD.bazel index 11083d14502..12d99c0875a 100644 --- a/packages/upgrade/BUILD.bazel +++ b/packages/upgrade/BUILD.bazel @@ -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 = [