From 500acfcf98071db671f2f052e01f93ded2cf7720 Mon Sep 17 00:00:00 2001 From: Derek Cormier Date: Thu, 29 Sep 2022 11:24:32 -0700 Subject: [PATCH] build(bazel): create AIO example playgrounds for manual testing After the bazel migration, AIO examples are no longer fully formed in the source tree. --- .bazelignore | 1 + aio/.gitignore | 3 + aio/README.md | 2 + aio/content/examples/examples.bzl | 4 +- aio/package.json | 1 + aio/tools/examples/BUILD.bazel | 45 ++++- aio/tools/examples/README.md | 10 + .../create-example-playground-wrapper.mjs | 43 +++++ .../examples/create-example-playground.mjs | 66 +++++++ aio/tools/examples/example-sandbox.mjs | 172 ++++++++++++++++++ aio/tools/examples/run-example-e2e.mjs | 162 +++-------------- packages/animations/BUILD.bazel | 4 +- packages/bazel/BUILD.bazel | 1 + packages/common/BUILD.bazel | 4 +- packages/compiler-cli/BUILD.bazel | 1 + packages/compiler/BUILD.bazel | 1 + packages/core/BUILD.bazel | 4 +- packages/elements/BUILD.bazel | 4 +- packages/forms/BUILD.bazel | 4 +- packages/language-service/BUILD.bazel | 1 + packages/platform-browser-dynamic/BUILD.bazel | 4 +- packages/platform-browser/BUILD.bazel | 4 +- packages/platform-server/BUILD.bazel | 4 +- packages/router/BUILD.bazel | 4 +- packages/service-worker/BUILD.bazel | 4 +- packages/upgrade/BUILD.bazel | 4 +- 26 files changed, 393 insertions(+), 164 deletions(-) create mode 100644 aio/tools/examples/create-example-playground-wrapper.mjs create mode 100644 aio/tools/examples/create-example-playground.mjs create mode 100644 aio/tools/examples/example-sandbox.mjs diff --git a/.bazelignore b/.bazelignore index b96fdea264d..3d772ecfe93 100644 --- a/.bazelignore +++ b/.bazelignore @@ -6,6 +6,7 @@ node_modules dist aio/node_modules +aio/content/example-playground aio/tools/examples/shared/node_modules aio/tools/examples/shared/example-scaffold packages/bazel/node_modules diff --git a/aio/.gitignore b/aio/.gitignore index 94034a8d165..b5d376505f3 100644 --- a/aio/.gitignore +++ b/aio/.gitignore @@ -42,6 +42,9 @@ testem.log # e2e protractor-results*.txt +# Example playground +content/example-playground + # System files .DS_Store Thumbs.db diff --git a/aio/README.md b/aio/README.md index 7e6e03769ce..926e3e41bd3 100644 --- a/aio/README.md +++ b/aio/README.md @@ -30,6 +30,8 @@ Here are the most important tasks you might need to use: * `yarn docs-lint` - check that the doc gen code follows our style rules. * `yarn create-example` - create a new example directory containing initial source files. +* `yarn example-playground ` - set up a playground to manually test an example combined with its boilerplate files + - `--local` - link locally build Angular packages as deps * `yarn example-e2e` - run all e2e tests for examples. Available options: - `--local`: run e2e tests against locally built Angular packages. diff --git a/aio/content/examples/examples.bzl b/aio/content/examples/examples.bzl index d205a05ce24..a229225705a 100644 --- a/aio/content/examples/examples.bzl +++ b/aio/content/examples/examples.bzl @@ -182,7 +182,7 @@ def docs_example(name, test = True, test_tags = [], test_exec_properties = {}): # 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 AIO_EXAMPLE_PACKAGES] + LOCAL_PACKAGE_ARGS = ["--localPackage=%s#$(rootpath %s)" % (dep, to_package_label(dep)) for dep in AIO_EXAMPLE_PACKAGES] nodejs_test( name = "e2e", @@ -206,7 +206,7 @@ def docs_example(name, test = True, test_tags = [], test_exec_properties = {}): "//conditions:default": [], }), configuration_env_vars = ["NG_BUILD_CACHE"], - entry_point = "//aio/tools/examples:run-example-e2e", + entry_point = "//aio/tools/examples:run-example-e2e.mjs", env = { "CHROME_BIN": "$(CHROMIUM)", "CHROMEDRIVER_BIN": "$(CHROMEDRIVER)", diff --git a/aio/package.json b/aio/package.json index c701a833a02..2ee2cbad826 100644 --- a/aio/package.json +++ b/aio/package.json @@ -33,6 +33,7 @@ "example-e2e": "node --experimental-import-meta-resolve tools/examples/run-filtered-example-e2es.mjs", "example-list-overrides": "bazel run //aio/tools/examples:example-boilerplate list-overrides", "example-lint": "eslint content/examples", + "example-playground": "node ./tools/examples/create-example-playground-wrapper.mjs", "deploy-production": "bazel run //aio/scripts/deploy-to-firebase", "check-env": "yarn ~~check-env", "payload-size": "scripts/payload.sh", diff --git a/aio/tools/examples/BUILD.bazel b/aio/tools/examples/BUILD.bazel index 96b8ab57761..a7a9c61eeb9 100644 --- a/aio/tools/examples/BUILD.bazel +++ b/aio/tools/examples/BUILD.bazel @@ -1,9 +1,15 @@ load("//tools:defaults.bzl", "nodejs_binary") load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("@aio_npm//@bazel/jasmine:index.bzl", "jasmine_node_test") +load("//aio/content/examples:examples.bzl", "EXAMPLES") +load("//:packages.bzl", "AIO_EXAMPLE_PACKAGES", "to_package_label") package(default_visibility = ["//visibility:public"]) +exports_files([ + "run-example-e2e.mjs", +]) + EXAMPLE_BOILERPLATE_SRCS = [ "example-boilerplate.js", "constants.js", @@ -58,15 +64,25 @@ jasmine_node_test( CREATE_EXAMPLE_SRCS + CREATE_EXAMPLE_DEPS, ) +js_library( + name = "example-sandbox", + srcs = ["example-sandbox.mjs"], + deps = [ + "@aio_npm//cjson", + "@aio_npm//fs-extra", + "@aio_npm//globby", + ], +) + js_library( name = "run-example-e2e", srcs = [ "run-example-e2e.mjs", ], deps = [ + ":example-sandbox", "@aio_npm//@bazel/runfiles", "@aio_npm//canonical-path", - "@aio_npm//cjson", "@aio_npm//cross-spawn", "@aio_npm//fs-extra", "@aio_npm//get-port", @@ -76,3 +92,30 @@ js_library( "@aio_npm//yargs", ], ) + +EXAMPLES_WITH_BOILERPLATE = ["//aio/content/examples/%s" % example for example in EXAMPLES] + +LOCAL_PACKAGE_DEPS = [to_package_label(dep) for dep in AIO_EXAMPLE_PACKAGES] + +LOCAL_PACKAGE_ARGS = ["--localPackage=%s#$(rootpath %s)" % ( + dep, + to_package_label(dep), +) for dep in AIO_EXAMPLE_PACKAGES] + +nodejs_binary( + name = "create-example-playground", + args = select({ + # Hardcode package names/paths in args + "//aio:aio_local_deps": LOCAL_PACKAGE_ARGS, + "//conditions:default": [], + }), + data = [ + ":example-sandbox", + "@aio_example_deps//:node_modules_files", + "@aio_npm//yargs", + ] + EXAMPLES_WITH_BOILERPLATE + select({ + "//aio:aio_local_deps": LOCAL_PACKAGE_DEPS, + "//conditions:default": [], + }), + entry_point = "create-example-playground.mjs", +) diff --git a/aio/tools/examples/README.md b/aio/tools/examples/README.md index 767f6dbfd16..2af8ce6cc69 100644 --- a/aio/tools/examples/README.md +++ b/aio/tools/examples/README.md @@ -140,6 +140,16 @@ By default the script will place basic scaffold files into the new example (from But you can also specify the path to a separate CLI project, from which the script will copy files that would not be considered "boilerplate". See the [Boilerplate overview](#boilerplate-overview) for more information. +### `create-example-playground.mjs` + +The [create-example-playground.mjs](./create-example-playground.mjs) script combines example sources, boilerplate, and shared node_modules deps into git-ignored playground directory `content/example-playground/{{EXAMPLE}}` that can be used for manual testing. This should be invoked via the yarn script: + +```bash +yarn example-playground [--local] +``` + +The `--local` flag links in locally-built angular packages as dependencies. + ### Updating example dependencies With every major Angular release, we update the examples to be on the latest version. diff --git a/aio/tools/examples/create-example-playground-wrapper.mjs b/aio/tools/examples/create-example-playground-wrapper.mjs new file mode 100644 index 00000000000..fae4c43931c --- /dev/null +++ b/aio/tools/examples/create-example-playground-wrapper.mjs @@ -0,0 +1,43 @@ +import shelljs from 'shelljs'; +import yargs from 'yargs' +import {hideBin} from 'yargs/helpers'; +import {getNativeBinary as getNativeBazelBinary} from '@bazel/bazelisk'; + +shelljs.set('-e') +shelljs.set('-v') + +/** + * Create an example playground with shared example deps and optionally linked local + * angular packages in the source tree under content/examples/example-playground. This + * is a wrapper around the equivalent bazel binary but adds the --local option to link + * local packages. + * + * Usage: node ./tools/examples/create-example-playground-wrapper.mjs [options] + * + * Args: + * example: name of the example + * + * Flags: + * --local: use locally built angular packages + */ + +const options = yargs(hideBin(process.argv)) + .command('$0 ', 'Set up a playground for in the source tree for manual testing') + .option('local', {default: false, type: 'boolean'}) + .version(false) + .strict() + .argv; + +const cmd = [ + getNativeBazelBinary(), + 'run', + '//aio/tools/examples:create-example-playground', + '--', + `--example=${options.example}`, +]; + +if (options.local) { + cmd.splice(2, 0, '--config=aio_local_deps'); +} + +shelljs.exec(cmd.join(' ')); diff --git a/aio/tools/examples/create-example-playground.mjs b/aio/tools/examples/create-example-playground.mjs new file mode 100644 index 00000000000..d3f9c99f794 --- /dev/null +++ b/aio/tools/examples/create-example-playground.mjs @@ -0,0 +1,66 @@ +import path from 'node:path'; +import yargs from 'yargs'; +import {hideBin} from 'yargs/helpers' +import {constructExampleSandbox} from './example-sandbox.mjs'; + +if (!process.env.BUILD_WORKSPACE_DIRECTORY) { + console.error( + 'Not running script as part of `bazel run`.' + ) + process.exit(1); +} +const sourceRoot = process.env.BUILD_WORKSPACE_DIRECTORY; +const runfilesRoot = path.join(process.env.RUNFILES, 'angular'); +const playgroundRoot = path.join(sourceRoot, 'aio', 'content', 'example-playground'); + +/** + * Create an example playground with shared example deps and optionally linked local + * angular packages in the source tree under content/examples/example-playground. This + * script is intended to only be run under bazel as it has the localPackage arguments + * hardcoded into the binary via starlark. + * + * Usage: bazel run //aio/tools/examples:create-example-playground -- --example= + * + * Args: + * example: name of the example + */ + +async function main(args) { + const options = + yargs(args) + // Note: localPackage not listed above in usage as it's hardcoded in by the nodejs_binary + .option('localPackage', { + array: true, + type: 'string', + default: [], + describe: 'Locally built package to substitute, in the form `packageName#packagePath`' + }) + .option('example', { + type: 'string', + describe: 'Name of the example' + }) + .demandOption('example') + .strict() + .version(false) + .argv; + + const localPackages = options.localPackage.reduce((pkgs, pkgNameAndPath) => { + const [pkgName, pkgPath] = pkgNameAndPath.split('#'); + pkgs[pkgName] = path.resolve(pkgPath); + return pkgs; + }, {}); + + const exampleName = options.example; + + // Note: the example sources plus boilerplate are merged into a target named after the example + const fullExamplePath = path.join(runfilesRoot, 'aio', 'content', 'examples', exampleName, exampleName); + + const destPath = path.join(playgroundRoot, exampleName); + const nodeModules = path.join(runfilesRoot, '..', 'aio_example_deps', 'node_modules'); + + await constructExampleSandbox(fullExamplePath, destPath, nodeModules, localPackages); + + console.log(`A playground folder for ${exampleName} has been set up at\n\n ${destPath}\n`); +} + +(async () => await main(hideBin(process.argv)))(); diff --git a/aio/tools/examples/example-sandbox.mjs b/aio/tools/examples/example-sandbox.mjs new file mode 100644 index 00000000000..6af75c3c331 --- /dev/null +++ b/aio/tools/examples/example-sandbox.mjs @@ -0,0 +1,172 @@ +import jsonc from 'cjson'; +import fs from 'fs-extra'; +import {globbySync} from 'globby'; +import path from 'node:path'; +import os from 'node:os'; + +// Construct a sandbox environment for an example, linking in shared example node_modules +// and optionally linking in locally-built angular packages. +export async function constructExampleSandbox(examplePath, destPath, nodeModulesPath, localPackages) { + fs.rmSync(destPath, { + recursive: true, + force: true + }); + fs.copySync(examplePath, destPath); + + // Remove write protection as the example was copied from bazel output tree + chmodSyncRec(destPath); + + // Symlink shared example node_modules, substituting for locally built deps if requested + await constructSymlinkedNodeModules(destPath, nodeModulesPath, localPackages); + + // Add preserveSymlinks fixups to various files --- needed when linkin in local deps + preserveSymlinksWhenUsingLocalPackages(localPackages, destPath); +} + +async function constructSymlinkedNodeModules(examplePath, exampleDepsNodeModules, localPackages) { + const linkedNodeModules = path.resolve(examplePath, 'node_modules'); + fs.ensureDirSync(linkedNodeModules); + + await Promise.all([ + linkExampleDeps(exampleDepsNodeModules, linkedNodeModules, localPackages), + linkLocalDeps(exampleDepsNodeModules, linkedNodeModules, localPackages) + ]); + + fs.copySync(path.join(exampleDepsNodeModules, '.bin'), path.join(linkedNodeModules, '.bin')); + pointBinSymlinksToLocalPackages(linkedNodeModules, exampleDepsNodeModules, localPackages); +} + +function linkExampleDeps(exampleDepsNodeModules, linkedNodeModules, localPackages) { + const exampleDepsPackages = getPackageNamesFromNodeModules(exampleDepsNodeModules); + + return Promise.all(exampleDepsPackages + .filter(pkgName => !(pkgName in localPackages)) + .map(pkgName => fs.ensureSymlink( + path.join(exampleDepsNodeModules, 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 + }); +} + +async function linkLocalDeps(exampleDepsNodeModules, linkedNodeModules, localPackages) { + const hasNpmDepForPkg = await Promise.all(Object.keys(localPackages) + .map(pkgName => fs.pathExists(path.join(exampleDepsNodeModules, pkgName)))); + + return Promise.all(Object.keys(localPackages).filter((pkgName, i) => hasNpmDepForPkg[i]) + .map(pkgName => fs.ensureSymlinkSync(localPackages[pkgName], path.join(linkedNodeModules, pkgName), 'dir'))); +} + +// 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, exampleDepsNodeModules, localPackages) { + 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 => { + // TODO: Swapping the symlink to ngc with the local package equivalent + // doesn't seem to work. When ngc runs in upgrade-phonecat-2-hybrid via + // the build:aot yarn script, node cannot resolve @angular/compiler. + if (bin === 'ngc') { + return; + } + const symlinkTarget = fs.readlinkSync(path.join(linkedNodeModules, '.bin', bin)); + for (const pkgName of Object.keys(localPackages)) { + const binMightBeInLocalPackage = symlinkTarget.includes(path.join(exampleDepsNodeModules, 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; + } + } + }); +} + +// When local packages are symlinked in, node has trouble resolving some peer deps. Setting +// preserveSymlinks in relevant files 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. Add the preserveSymlinks options to various +// files that are used by the cli and systemjs tests (and sometimes both). +function preserveSymlinksWhenUsingLocalPackages(LOCAL_PACKAGES, appDir) { + if (Object.keys(LOCAL_PACKAGES).length === 0) { + return; + } + + // Set preserveSymlinks in angular.json + const angularJsonPath = path.join(appDir, 'angular.json'); + if (fs.existsSync(angularJsonPath)) { + const angularJson = jsonc.load(angularJsonPath, { + encoding: 'utf-8' + }); + angularJson.projects['angular.io-example'].architect.build.options.preserveSymlinks = true; + fs.writeFileSync(angularJsonPath, JSON.stringify(angularJson, undefined, 2)); + } + + // Set preserveSymlinks in any tsconfig.json files + const tsConfigPaths = globbySync([path.join(appDir, 'tsconfig*.json')]); + for (const tsConfigPath of tsConfigPaths) { + const tsConfig = jsonc.load(tsConfigPath, { + encoding: 'utf-8' + }); + const isRootConfig = !tsConfig.extends; + if (isRootConfig) { + tsConfig.compilerOptions.preserveSymlinks = true; + fs.writeFileSync(tsConfigPath, JSON.stringify(tsConfig, undefined, 2)); + } + } + + // Call rollup with --preserveSymlinks + const packageJsonPath = path.join(appDir, 'package.json'); + const packageJson = jsonc.load(packageJsonPath, { + encoding: 'utf-8' + }); + if ('rollup' in packageJson.dependencies || 'rollup' in packageJson.devDependencies) { + packageJson.scripts.rollup = 'rollup --preserveSymlinks'; + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, undefined, 2)); + } +} + +function chmodSyncRec(dest) { + // Glob patterns always use unix-style paths + const allFilesPattern = path.join(dest, '**').replace(/\\/g, '/'); + + globbySync(allFilesPattern, { + dot: true, + onlyFiles: false + }).forEach(file => fs.chmodSync(file, '755')); + fs.chmodSync(dest, '755'); +} \ No newline at end of file diff --git a/aio/tools/examples/run-example-e2e.mjs b/aio/tools/examples/run-example-e2e.mjs index dd6e619d28e..e765c113ad0 100644 --- a/aio/tools/examples/run-example-e2e.mjs +++ b/aio/tools/examples/run-example-e2e.mjs @@ -2,13 +2,13 @@ import path from 'canonical-path'; import {spawn} from 'cross-spawn'; import fs from 'fs-extra'; import {globbySync} from 'globby'; -import jsonc from 'cjson'; import os from 'os'; import shelljs from 'shelljs'; import treeKill from 'tree-kill'; import yargs from 'yargs'; import {hideBin} from 'yargs/helpers' import getPort from 'get-port'; +import {constructExampleSandbox} from "./example-sandbox.mjs"; shelljs.set('-e'); @@ -19,13 +19,17 @@ process.env.CHROME_BIN = adjustChromeBinPathForWindows(); process.env.CHROME_BIN = path.resolve(process.env.CHROME_BIN); process.env.CHROMEDRIVER_BIN = path.resolve(process.env.CHROMEDRIVER_BIN); -const {argv} = yargs(hideBin(process.argv)); +const {argv} = yargs(hideBin(process.argv)) + .command("* ") + .option('localPackage', {array: true, type: 'string', default: [], describe: 'Locally built package to substitute, in the form `packageName#packagePath`'}) + .version(false) + .strict(); -const EXAMPLE_PATH = path.resolve(argv._[0]); +const EXAMPLE_PATH = path.resolve(argv.examplePath); const NODE = process.execPath; -const VENDORED_YARN = path.resolve(argv._[1]); -const EXAMPLE_DEPS_WORKSPACE_NAME = argv._[2]; -const LOCAL_PACKAGES = argv._.slice(3).reduce((pkgs, pkgNameAndPath) => { +const VENDORED_YARN = path.resolve(argv.yarnPath); +const EXAMPLE_DEPS_WORKSPACE_NAME = argv.exampleDepsWorkspaceName; +const LOCAL_PACKAGES = argv.localPackage.reduce((pkgs, pkgNameAndPath) => { const [pkgName, pkgPath] = pkgNameAndPath.split('#'); pkgs[pkgName] = path.resolve(pkgPath); return pkgs; @@ -55,15 +59,17 @@ const MAX_NO_OUTPUT_TIMEOUT = 1000 * 60 * 5; // 5 minutes async function runE2e(examplePath) { const exampleName = path.basename(examplePath); const maxAttempts = argv.retry || 1; + const exampleTestPath = generatePathForExampleTest(exampleName); + try { - examplePath = createCopyOfExampleForTest(exampleName, examplePath); - await constructNodeModules(examplePath); - + + await constructExampleSandbox(examplePath, exampleTestPath, path.resolve('..', EXAMPLE_DEPS_WORKSPACE_NAME, 'node_modules'), LOCAL_PACKAGES); + let testFn; - if (isSystemJsTest(examplePath)) { - testFn = () => runE2eTestsSystemJS(exampleName, examplePath); - } else if (isCliTest(examplePath)) { - testFn = () => runE2eTestsCLI(exampleName, examplePath); + if (isSystemJsTest(exampleTestPath)) { + testFn = () => runE2eTestsSystemJS(exampleName, exampleTestPath); + } else if (isCliTest(exampleTestPath)) { + testFn = () => runE2eTestsCLI(exampleName, exampleTestPath); } else { throw new Error(`Unknown e2e test type for example ${exampleName}`); } @@ -73,7 +79,7 @@ async function runE2e(examplePath) { console.error(e); process.exitCode = 1; } finally { - fs.rmSync(examplePath, {recursive: true, force: true}); + fs.rmSync(exampleTestPath, {recursive: true, force: true}); } } @@ -93,18 +99,13 @@ async function attempt(testFn, maxAttempts) { } } -function createCopyOfExampleForTest(exampleName, examplePath) { +function generatePathForExampleTest(exampleName) { // Note that bazel provides a writeable tmp dir for tests in the env var TEST_TMPDIR, // however we do not use it here as in non-sandboxed mode the temp dir sits under the // execroot, so yarn will find the .yarnrc in the root of the workspace. If there is ever // a version mismatch (e.g., if we use multiple vendored yarn versions) then this could // cause subtle errors. Instead, just use a temp dir that bazel doesn't know about. - const testPath = fs.mkdtempSync(`${os.tmpdir()}${path.sep}${exampleName}-`) - globbySync(['**'], {cwd: examplePath, dot: true}).forEach(file => { - fs.copySync(path.join(examplePath, file), path.join(testPath, file)); - fs.chmodSync(path.join(testPath, file), '755'); - }); - return testPath; + return fs.mkdtempSync(`${os.tmpdir()}${path.sep}${exampleName}-`) } function isSystemJsTest(examplePath) { @@ -118,8 +119,6 @@ function isCliTest(examplePath) { async function runE2eTestsSystemJS(exampleName, appDir) { const config = loadExampleConfig(appDir); - preserveSymlinksWhenUsingLocalPackages(LOCAL_PACKAGES, appDir); - const runArgs = await overrideSystemJsExampleToUseRandomPort(config, appDir); const appBuildSpawnInfo = spawnExt(NODE, [VENDORED_YARN, config.build], {cwd: appDir}); @@ -220,82 +219,6 @@ function runProtractorAoT(exampleName, appDir) { return runProtractorSystemJS(exampleName, promise, appDir, aotRunSpawnInfo); } -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 => { - // TODO: Swapping the symlink to ngc with the local package equivalent - // doesn't seem to work. When ngc runs in upgrade-phonecat-2-hybrid via - // the build:aot yarn script, node cannot resolve @angular/compiler. - if (bin === "ngc") { - return; - } - 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 // fileName; then shut down the example. // All protractor output is appended to the outputFile. @@ -315,8 +238,6 @@ function runE2eTestsCLI(exampleName, appDir) { } } - preserveSymlinksWhenUsingLocalPackages(LOCAL_PACKAGES, appDir); - // `--no-webdriver-update` is needed to preserve the ChromeDriver version already installed. const testCommands = config.tests || [{ cmd: NODE, @@ -435,43 +356,4 @@ function adjustChromeBinPathForWindows() { return process.env.CHROME_BIN; } -// When local packages are symlinked in, node has trouble resolving some peer deps. Setting -// preserveSymlinks in relevant files 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. Add the preserveSymlinks options to various -// files that are used by the cli and systemjs tests (and sometimes both). -function preserveSymlinksWhenUsingLocalPackages(LOCAL_PACKAGES, appDir) { - if (Object.keys(LOCAL_PACKAGES).length == 0) { - return; - } - - // Set preserveSymlinks in angular.json - const angularJsonPath = path.join(appDir, 'angular.json'); - if (fs.existsSync(angularJsonPath)) { - const angularJson = jsonc.load(angularJsonPath, {encoding: 'utf-8'}); - angularJson.projects['angular.io-example'].architect.build.options.preserveSymlinks = true; - fs.writeFileSync(angularJsonPath, JSON.stringify(angularJson, undefined, 2)); - } - - // Set preserveSymlinks in any tsconfig.json files - const tsConfigPaths = globbySync([path.join(appDir, 'tsconfig*.json')]); - for (let tsConfigPath of tsConfigPaths) { - const tsConfig = jsonc.load(tsConfigPath, {encoding: 'utf-8'}); - const isRootConfig = !tsConfig.extends; - if (isRootConfig) { - tsConfig.compilerOptions.preserveSymlinks = true; - fs.writeFileSync(tsConfigPath, JSON.stringify(tsConfig, undefined, 2)); - } - } - - // Call rollup with --preserveSymlinks - const packageJsonPath = path.join(appDir, 'package.json'); - const packageJson = jsonc.load(packageJsonPath, {encoding: 'utf-8'}); - if ('rollup' in packageJson.dependencies || 'rollup' in packageJson.devDependencies) { - packageJson.scripts['rollup'] = 'rollup --preserveSymlinks'; - fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, undefined, 2)); - } -} - runE2e(EXAMPLE_PATH); - diff --git a/packages/animations/BUILD.bazel b/packages/animations/BUILD.bazel index 3f8bd91d4ba..1010704447e 100644 --- a/packages/animations/BUILD.bazel +++ b/packages/animations/BUILD.bazel @@ -29,6 +29,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", "//packages/compiler-cli/integrationtest:__pkg__", "//packages/compiler/test:__pkg__", @@ -66,6 +67,5 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", - "PACKAGE.md", - ]), + ]) + ["PACKAGE.md"], ) diff --git a/packages/bazel/BUILD.bazel b/packages/bazel/BUILD.bazel index 99e1bf4080e..e6137e20272 100644 --- a/packages/bazel/BUILD.bazel +++ b/packages/bazel/BUILD.bazel @@ -33,6 +33,7 @@ pkg_npm( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", ], deps = [ diff --git a/packages/common/BUILD.bazel b/packages/common/BUILD.bazel index 6d77fb7035f..626e942398f 100644 --- a/packages/common/BUILD.bazel +++ b/packages/common/BUILD.bazel @@ -48,6 +48,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", "//packages/bazel/test/ng_package:__pkg__", "//packages/compiler-cli/integrationtest:__pkg__", @@ -92,6 +93,5 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", - "PACKAGE.md", - ]), + ]) + ["PACKAGE.md"], ) diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index 8810e163da5..c6a96684b6f 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -133,6 +133,7 @@ pkg_npm( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", "//packages/compiler-cli/integrationtest:__pkg__", ], diff --git a/packages/compiler/BUILD.bazel b/packages/compiler/BUILD.bazel index 30cf72e74fd..06868131251 100644 --- a/packages/compiler/BUILD.bazel +++ b/packages/compiler/BUILD.bazel @@ -31,6 +31,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//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 086010af6ed..25c572ceac6 100644 --- a/packages/core/BUILD.bazel +++ b/packages/core/BUILD.bazel @@ -62,6 +62,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", "//packages/bazel/test/ng_package:__pkg__", "//packages/compiler-cli/integrationtest:__pkg__", @@ -121,8 +122,9 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", + ]) + [ "PACKAGE.md", "global/index.ts", "global/PACKAGE.md", - ]), + ], ) diff --git a/packages/elements/BUILD.bazel b/packages/elements/BUILD.bazel index 71ae65ecd36..51902bd392d 100644 --- a/packages/elements/BUILD.bazel +++ b/packages/elements/BUILD.bazel @@ -29,6 +29,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", ], deps = [ @@ -51,6 +52,5 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", - "PACKAGE.md", - ]), + ]) + ["PACKAGE.md"], ) diff --git a/packages/forms/BUILD.bazel b/packages/forms/BUILD.bazel index a1cc0f5224c..7bda0bf1e64 100644 --- a/packages/forms/BUILD.bazel +++ b/packages/forms/BUILD.bazel @@ -29,6 +29,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", "//packages/compiler-cli/integrationtest:__pkg__", "//packages/compiler-cli/test/diagnostics:__pkg__", @@ -64,6 +65,5 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", - "PACKAGE.md", - ]), + ]) + ["PACKAGE.md"], ) diff --git a/packages/language-service/BUILD.bazel b/packages/language-service/BUILD.bazel index f3770d59e78..3e9b38b79ce 100644 --- a/packages/language-service/BUILD.bazel +++ b/packages/language-service/BUILD.bazel @@ -28,6 +28,7 @@ pkg_npm( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", ], deps = [ diff --git a/packages/platform-browser-dynamic/BUILD.bazel b/packages/platform-browser-dynamic/BUILD.bazel index b02279c6f66..df35931a2d3 100644 --- a/packages/platform-browser-dynamic/BUILD.bazel +++ b/packages/platform-browser-dynamic/BUILD.bazel @@ -33,6 +33,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", "//packages/compiler-cli/integrationtest:__pkg__", ], @@ -57,6 +58,5 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", - "PACKAGE.md", - ]), + ]) + ["PACKAGE.md"], ) diff --git a/packages/platform-browser/BUILD.bazel b/packages/platform-browser/BUILD.bazel index 3a8fa52eb46..1bfeb91997d 100644 --- a/packages/platform-browser/BUILD.bazel +++ b/packages/platform-browser/BUILD.bazel @@ -39,6 +39,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", "//packages/compiler-cli/integrationtest:__pkg__", "//packages/compiler-cli/test:__pkg__", @@ -65,6 +66,5 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", - "PACKAGE.md", - ]), + ]) + ["PACKAGE.md"], ) diff --git a/packages/platform-server/BUILD.bazel b/packages/platform-server/BUILD.bazel index 099bb4f1c97..94420f754d5 100644 --- a/packages/platform-server/BUILD.bazel +++ b/packages/platform-server/BUILD.bazel @@ -50,6 +50,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", "//packages/compiler-cli/integrationtest:__pkg__", ], @@ -75,6 +76,5 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", - "PACKAGE.md", - ]), + ]) + ["PACKAGE.md"], ) diff --git a/packages/router/BUILD.bazel b/packages/router/BUILD.bazel index 365a1bc880f..a4162e8919a 100644 --- a/packages/router/BUILD.bazel +++ b/packages/router/BUILD.bazel @@ -32,6 +32,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", "//packages/compiler-cli/integrationtest:__pkg__", "//packages/compiler-cli/test:__pkg__", @@ -69,6 +70,5 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", - "PACKAGE.md", - ]), + ]) + ["PACKAGE.md"], ) diff --git a/packages/service-worker/BUILD.bazel b/packages/service-worker/BUILD.bazel index 51ae73d70da..0f5a8ed50e8 100644 --- a/packages/service-worker/BUILD.bazel +++ b/packages/service-worker/BUILD.bazel @@ -51,6 +51,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", ], deps = [ @@ -74,6 +75,5 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", - "PACKAGE.md", - ]), + ]) + ["PACKAGE.md"], ) diff --git a/packages/upgrade/BUILD.bazel b/packages/upgrade/BUILD.bazel index 12d99c0875a..a82d1b987b5 100644 --- a/packages/upgrade/BUILD.bazel +++ b/packages/upgrade/BUILD.bazel @@ -32,6 +32,7 @@ ng_package( visibility = [ "//aio:__pkg__", "//aio/content/examples:__subpackages__", + "//aio/tools/examples:__pkg__", "//integration:__subpackages__", ], deps = [ @@ -56,6 +57,5 @@ filegroup( srcs = glob([ "*.ts", "src/**/*.ts", - "PACKAGE.md", - ]), + ]) + ["PACKAGE.md"], )