refactor(bazel): remove old Angular CLI schematics and builder (#41575)
This is leftover code which is no longer used. PR Close #41575
This commit is contained in:
parent
c7f9516ab9
commit
71b8c9ab29
|
@ -42,7 +42,6 @@
|
||||||
"// 1": "dependencies are used locally and by bazel",
|
"// 1": "dependencies are used locally and by bazel",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/cli": "12.0.0-next.7",
|
"@angular/cli": "12.0.0-next.7",
|
||||||
"@angular-devkit/architect": "0.1200.0-next.7",
|
|
||||||
"@angular-devkit/build-angular": "github:angular/angular-devkit-build-angular-builds#e0c1374b12cfc892844030431bc19f631c0086a5",
|
"@angular-devkit/build-angular": "github:angular/angular-devkit-build-angular-builds#e0c1374b12cfc892844030431bc19f631c0086a5",
|
||||||
"@angular-devkit/build-optimizer": "0.1200.0-next.7",
|
"@angular-devkit/build-optimizer": "0.1200.0-next.7",
|
||||||
"@angular-devkit/core": "12.0.0-next.7",
|
"@angular-devkit/core": "12.0.0-next.7",
|
||||||
|
|
|
@ -13,9 +13,6 @@ pkg_npm(
|
||||||
"//packages/bazel/src/ngc-wrapped:package_assets",
|
"//packages/bazel/src/ngc-wrapped:package_assets",
|
||||||
"//packages/bazel/third_party/github.com/bazelbuild/bazel/src/main/protobuf:package_assets",
|
"//packages/bazel/third_party/github.com/bazelbuild/bazel/src/main/protobuf:package_assets",
|
||||||
],
|
],
|
||||||
nested_packages = [
|
|
||||||
"//packages/bazel/docs",
|
|
||||||
],
|
|
||||||
substitutions = {
|
substitutions = {
|
||||||
"(#|//)\\s+BEGIN-DEV-ONLY[\\w\\W]+?(#|//)\\s+END-DEV-ONLY": "",
|
"(#|//)\\s+BEGIN-DEV-ONLY[\\w\\W]+?(#|//)\\s+END-DEV-ONLY": "",
|
||||||
"//packages/bazel/": "//@angular/bazel/",
|
"//packages/bazel/": "//@angular/bazel/",
|
||||||
|
|
|
@ -45,7 +45,7 @@ This new rule leverages ngtsc plugin supported by `ts_library`, and it is much f
|
||||||
|
|
||||||
For the latest recommendations, please refer to the canonical Angular Bazel [repo](https://github.com/bazelbuild/rules_nodejs/tree/master/examples/angular).
|
For the latest recommendations, please refer to the canonical Angular Bazel [repo](https://github.com/bazelbuild/rules_nodejs/tree/master/examples/angular).
|
||||||
|
|
||||||
For questions, please ask in the `#angular` channel in https://slack.bazel.build/.
|
For questions, please ask in the `#angular` channel in http://slack.bazel.build/.
|
||||||
|
|
||||||
## Angular CLI
|
## Angular CLI
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
load("@io_bazel_skydoc//skylark:skylark.bzl", "skylark_doc")
|
|
||||||
|
|
||||||
skylark_doc(
|
|
||||||
name = "docs",
|
|
||||||
srcs = [
|
|
||||||
"//packages/bazel/src:ng_module.bzl",
|
|
||||||
"//packages/bazel/src/ng_package:ng_package.bzl",
|
|
||||||
],
|
|
||||||
format = "html",
|
|
||||||
site_root = "/bazel-builds",
|
|
||||||
strip_prefix = "packages/bazel/",
|
|
||||||
visibility = ["//packages/bazel:__subpackages__"],
|
|
||||||
)
|
|
|
@ -1,200 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// <reference types='node'/>
|
|
||||||
|
|
||||||
import {spawn} from 'child_process';
|
|
||||||
import {copyFileSync, existsSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync} from 'fs';
|
|
||||||
import {platform} from 'os';
|
|
||||||
import {dirname, join, normalize} from 'path';
|
|
||||||
|
|
||||||
export type Executable = 'bazel'|'ibazel';
|
|
||||||
export type Command = 'build'|'test'|'run'|'coverage'|'query';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spawn the Bazel process. Trap SINGINT to make sure Bazel process is killed.
|
|
||||||
*/
|
|
||||||
export function runBazel(
|
|
||||||
projectDir: string, binary: string, command: Command, workspaceTarget: string,
|
|
||||||
flags: string[]) {
|
|
||||||
projectDir = normalize(projectDir);
|
|
||||||
binary = normalize(binary);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const buildProcess = spawn(binary, [command, workspaceTarget, ...flags], {
|
|
||||||
cwd: projectDir,
|
|
||||||
stdio: 'inherit',
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('SIGINT', (signal) => {
|
|
||||||
if (!buildProcess.killed) {
|
|
||||||
buildProcess.kill();
|
|
||||||
reject(new Error(`Bazel process received ${signal}.`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
buildProcess.once('close', (code: number) => {
|
|
||||||
if (code === 0) {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
reject(new Error(`${binary} failed with code ${code}.`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the path to `@bazel/bazel` or `@bazel/ibazel`.
|
|
||||||
*/
|
|
||||||
export function checkInstallation(name: Executable, projectDir: string): string {
|
|
||||||
projectDir = normalize(projectDir);
|
|
||||||
const packageName = `@bazel/${name}`;
|
|
||||||
try {
|
|
||||||
const bazelPath = require.resolve(packageName, {
|
|
||||||
paths: [projectDir],
|
|
||||||
});
|
|
||||||
return require(bazelPath).getNativeBinary();
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code === 'MODULE_NOT_FOUND') {
|
|
||||||
throw new Error(
|
|
||||||
`Could not run ${name}. Please make sure that the ` +
|
|
||||||
`"${name}" command is installed by running ` +
|
|
||||||
`"npm install ${packageName}" or "yarn install ${packageName}".`);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the absolute path to the template directory in `@angular/bazel`.
|
|
||||||
*/
|
|
||||||
export function getTemplateDir(root: string): string {
|
|
||||||
root = normalize(root);
|
|
||||||
const packageJson = require.resolve('@angular/bazel/package.json', {
|
|
||||||
paths: [root],
|
|
||||||
});
|
|
||||||
const packageDir = dirname(packageJson);
|
|
||||||
const templateDir = join(packageDir, 'src', 'builders', 'files');
|
|
||||||
if (!statSync(templateDir).isDirectory()) {
|
|
||||||
throw new Error('Could not find Bazel template directory in "@angular/bazel".');
|
|
||||||
}
|
|
||||||
return templateDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively list the specified 'dir' using depth-first approach. Paths
|
|
||||||
* returned are relative to 'dir'.
|
|
||||||
*/
|
|
||||||
function listR(dir: string): string[] {
|
|
||||||
function list(dir: string, root: string, results: string[]) {
|
|
||||||
const paths = readdirSync(dir);
|
|
||||||
for (const path of paths) {
|
|
||||||
const absPath = join(dir, path);
|
|
||||||
const relPath = join(root, path);
|
|
||||||
if (statSync(absPath).isFile()) {
|
|
||||||
results.push(relPath);
|
|
||||||
} else {
|
|
||||||
list(absPath, relPath, results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
return list(dir, '', []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the name of the lock file that is present in the specified 'root'
|
|
||||||
* directory. If none exists, default to creating an empty yarn.lock file.
|
|
||||||
*/
|
|
||||||
function getOrCreateLockFile(root: string): 'yarn.lock'|'package-lock.json' {
|
|
||||||
const yarnLock = join(root, 'yarn.lock');
|
|
||||||
if (existsSync(yarnLock)) {
|
|
||||||
return 'yarn.lock';
|
|
||||||
}
|
|
||||||
const npmLock = join(root, 'package-lock.json');
|
|
||||||
if (existsSync(npmLock)) {
|
|
||||||
return 'package-lock.json';
|
|
||||||
}
|
|
||||||
// Prefer yarn if no lock file exists
|
|
||||||
writeFileSync(yarnLock, '');
|
|
||||||
return 'yarn.lock';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace yarn_install rule with npm_install and copy from 'source' to 'dest'.
|
|
||||||
function replaceYarnWithNpm(source: string, dest: string) {
|
|
||||||
const srcContent = readFileSync(source, 'utf-8');
|
|
||||||
const destContent = srcContent.replace(/yarn_install/g, 'npm_install')
|
|
||||||
.replace('yarn_lock', 'package_lock_json')
|
|
||||||
.replace('yarn.lock', 'package-lock.json');
|
|
||||||
writeFileSync(dest, destContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable sandbox on Mac OS by setting spawn_strategy in .bazelrc.
|
|
||||||
* For a hello world (ng new) application, removing the sandbox improves build
|
|
||||||
* time by almost 40%.
|
|
||||||
* ng build with sandbox: 22.0 seconds
|
|
||||||
* ng build without sandbox: 13.3 seconds
|
|
||||||
*/
|
|
||||||
function disableSandbox(source: string, dest: string) {
|
|
||||||
const srcContent = readFileSync(source, 'utf-8');
|
|
||||||
const destContent = `${srcContent}
|
|
||||||
# Disable sandbox on Mac OS for performance reason.
|
|
||||||
build --spawn_strategy=local
|
|
||||||
run --spawn_strategy=local
|
|
||||||
test --spawn_strategy=local
|
|
||||||
`;
|
|
||||||
writeFileSync(dest, destContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy Bazel files (WORKSPACE, BUILD.bazel, etc) from the template directory to
|
|
||||||
* the project `root` directory, and return the absolute paths of the files
|
|
||||||
* copied, so that they can be deleted later.
|
|
||||||
* Existing files in `root` will not be replaced.
|
|
||||||
*/
|
|
||||||
export function copyBazelFiles(root: string, templateDir: string) {
|
|
||||||
root = normalize(root);
|
|
||||||
templateDir = normalize(templateDir);
|
|
||||||
const bazelFiles: string[] = [];
|
|
||||||
const templates = listR(templateDir);
|
|
||||||
const useYarn = getOrCreateLockFile(root) === 'yarn.lock';
|
|
||||||
|
|
||||||
for (const template of templates) {
|
|
||||||
const name = template.replace('__dot__', '.').replace('.template', '');
|
|
||||||
const source = join(templateDir, template);
|
|
||||||
const dest = join(root, name);
|
|
||||||
try {
|
|
||||||
if (!existsSync(dest)) {
|
|
||||||
if (!useYarn && name === 'WORKSPACE') {
|
|
||||||
replaceYarnWithNpm(source, dest);
|
|
||||||
} else if (platform() === 'darwin' && name === '.bazelrc') {
|
|
||||||
disableSandbox(source, dest);
|
|
||||||
} else {
|
|
||||||
copyFileSync(source, dest);
|
|
||||||
}
|
|
||||||
bazelFiles.push(dest);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bazelFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the specified 'files'. This function never throws.
|
|
||||||
*/
|
|
||||||
export function deleteBazelFiles(files: string[]) {
|
|
||||||
for (const file of files) {
|
|
||||||
try {
|
|
||||||
unlinkSync(file);
|
|
||||||
} catch {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*
|
|
||||||
* @fileoverview Bazel builder
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {BuilderContext, BuilderOutput, createBuilder,} from '@angular-devkit/architect';
|
|
||||||
import {JsonObject} from '@angular-devkit/core';
|
|
||||||
import {checkInstallation, copyBazelFiles, deleteBazelFiles, getTemplateDir, runBazel} from './bazel';
|
|
||||||
import {Schema} from './schema';
|
|
||||||
|
|
||||||
async function _bazelBuilder(
|
|
||||||
options: JsonObject&Schema,
|
|
||||||
context: BuilderContext,
|
|
||||||
): Promise<BuilderOutput> {
|
|
||||||
const {logger, workspaceRoot} = context;
|
|
||||||
const {bazelCommand, leaveBazelFilesOnDisk, targetLabel, watch} = options;
|
|
||||||
const executable = watch ? 'ibazel' : 'bazel';
|
|
||||||
const binary = checkInstallation(executable, workspaceRoot);
|
|
||||||
const templateDir = getTemplateDir(workspaceRoot);
|
|
||||||
const bazelFiles = copyBazelFiles(workspaceRoot, templateDir);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const flags: string[] = [];
|
|
||||||
await runBazel(workspaceRoot, binary, bazelCommand, targetLabel, flags);
|
|
||||||
return {success: true};
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(err.message);
|
|
||||||
return {success: false};
|
|
||||||
} finally {
|
|
||||||
if (!leaveBazelFilesOnDisk) {
|
|
||||||
deleteBazelFiles(bazelFiles); // this will never throw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createBuilder(_bazelBuilder);
|
|
|
@ -1,38 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for Bazel Builder
|
|
||||||
*/
|
|
||||||
export interface Schema {
|
|
||||||
/**
|
|
||||||
* Common commands supported by Bazel.
|
|
||||||
*/
|
|
||||||
bazelCommand: BazelCommand;
|
|
||||||
/**
|
|
||||||
* If true, leave Bazel files on disk after running command.
|
|
||||||
*/
|
|
||||||
leaveBazelFilesOnDisk?: boolean;
|
|
||||||
/**
|
|
||||||
* Target to be executed under Bazel.
|
|
||||||
*/
|
|
||||||
targetLabel: string;
|
|
||||||
/**
|
|
||||||
* If true, watch the filesystem using ibazel.
|
|
||||||
*/
|
|
||||||
watch?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common commands supported by Bazel.
|
|
||||||
*/
|
|
||||||
export enum BazelCommand {
|
|
||||||
Build = 'build',
|
|
||||||
Run = 'run',
|
|
||||||
Test = 'test',
|
|
||||||
}
|
|
|
@ -1,357 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*
|
|
||||||
* @fileoverview Schematics for ng-new project that builds with Bazel.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {JsonAstObject, parseJsonAst} from '@angular-devkit/core';
|
|
||||||
import {apply, applyTemplates, chain, mergeWith, Rule, SchematicContext, SchematicsException, Tree, url} from '@angular-devkit/schematics';
|
|
||||||
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
|
|
||||||
import {getWorkspace, getWorkspacePath} from '@schematics/angular/utility/config';
|
|
||||||
import {addPackageJsonDependency, getPackageJsonDependency, NodeDependencyType, removePackageJsonDependency} from '@schematics/angular/utility/dependencies';
|
|
||||||
import {findPropertyInAstObject, insertPropertyInAstObjectInOrder} from '@schematics/angular/utility/json-utils';
|
|
||||||
import {validateProjectName} from '@schematics/angular/utility/validation';
|
|
||||||
|
|
||||||
import {isJsonAstObject, replacePropertyInAstObject} from '../utility/json-utils';
|
|
||||||
import {findE2eArchitect} from '../utility/workspace-utils';
|
|
||||||
|
|
||||||
import {Schema} from './schema';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Packages that build under Bazel require additional dev dependencies. This
|
|
||||||
* function adds those dependencies to "devDependencies" section in
|
|
||||||
* package.json.
|
|
||||||
*/
|
|
||||||
function addDevDependenciesToPackageJson(options: Schema) {
|
|
||||||
return (host: Tree) => {
|
|
||||||
const angularCore = getPackageJsonDependency(host, '@angular/core');
|
|
||||||
if (!angularCore) {
|
|
||||||
throw new Error('@angular/core dependency not found in package.json');
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use a Record<string, string> when the tsc lib setting allows us
|
|
||||||
const devDependencies: [string, string][] = [
|
|
||||||
['@angular/bazel', angularCore.version],
|
|
||||||
['@bazel/bazel', '2.1.0'],
|
|
||||||
['@bazel/ibazel', '0.12.3'],
|
|
||||||
['@bazel/karma', '1.6.0'],
|
|
||||||
['@bazel/protractor', '1.6.0'],
|
|
||||||
['@bazel/rollup', '1.6.0'],
|
|
||||||
['@bazel/terser', '1.6.0'],
|
|
||||||
['@bazel/typescript', '1.6.0'],
|
|
||||||
['history-server', '1.3.1'],
|
|
||||||
['html-insert-assets', '0.5.0'],
|
|
||||||
['karma', '4.4.1'],
|
|
||||||
['karma-chrome-launcher', '3.1.0'],
|
|
||||||
['karma-firefox-launcher', '1.2.0'],
|
|
||||||
['karma-jasmine', '2.0.1'],
|
|
||||||
['karma-requirejs', '1.1.0'],
|
|
||||||
['karma-sourcemap-loader', '0.3.7'],
|
|
||||||
['protractor', '5.4.2'],
|
|
||||||
['requirejs', '2.3.6'],
|
|
||||||
['rollup', '1.27.5'],
|
|
||||||
['rollup-plugin-commonjs', '10.1.0'],
|
|
||||||
['rollup-plugin-node-resolve', '5.2.0'],
|
|
||||||
['terser', '4.4.0'],
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [name, version] of devDependencies) {
|
|
||||||
const dep = getPackageJsonDependency(host, name);
|
|
||||||
if (dep && dep.type !== NodeDependencyType.Dev) {
|
|
||||||
removePackageJsonDependency(host, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
addPackageJsonDependency(host, {
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
type: NodeDependencyType.Dev,
|
|
||||||
overwrite: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove packages that are not needed under Bazel.
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
function removeObsoleteDependenciesFromPackageJson(options: Schema) {
|
|
||||||
return (host: Tree) => {
|
|
||||||
const depsToRemove = [
|
|
||||||
'@angular-devkit/build-angular',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const packageName of depsToRemove) {
|
|
||||||
removePackageJsonDependency(host, packageName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append additional Javascript / Typescript files needed to compile an Angular
|
|
||||||
* project under Bazel.
|
|
||||||
*/
|
|
||||||
function addFilesRequiredByBazel(options: Schema) {
|
|
||||||
return (host: Tree) => {
|
|
||||||
return mergeWith(apply(url('./files'), [
|
|
||||||
applyTemplates({}),
|
|
||||||
]));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append '/bazel-out' to the gitignore file.
|
|
||||||
*/
|
|
||||||
function updateGitignore() {
|
|
||||||
return (host: Tree) => {
|
|
||||||
const gitignore = '/.gitignore';
|
|
||||||
if (!host.exists(gitignore)) {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
const gitIgnoreContentRaw = host.read(gitignore);
|
|
||||||
if (!gitIgnoreContentRaw) {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
const gitIgnoreContent = gitIgnoreContentRaw.toString();
|
|
||||||
if (gitIgnoreContent.includes('\n/bazel-out\n')) {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
const compiledOutput = '# compiled output\n';
|
|
||||||
const index = gitIgnoreContent.indexOf(compiledOutput);
|
|
||||||
const insertionIndex = index >= 0 ? index + compiledOutput.length : gitIgnoreContent.length;
|
|
||||||
const recorder = host.beginUpdate(gitignore);
|
|
||||||
recorder.insertRight(insertionIndex, '/bazel-out\n');
|
|
||||||
host.commitUpdate(recorder);
|
|
||||||
return host;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the architect in angular.json to use Bazel builder.
|
|
||||||
*/
|
|
||||||
function updateAngularJsonToUseBazelBuilder(options: Schema): Rule {
|
|
||||||
return (host: Tree) => {
|
|
||||||
const name = options.name!;
|
|
||||||
const workspacePath = getWorkspacePath(host);
|
|
||||||
if (!workspacePath) {
|
|
||||||
throw new Error('Could not find angular.json');
|
|
||||||
}
|
|
||||||
const workspaceContent = host.read(workspacePath);
|
|
||||||
if (!workspaceContent) {
|
|
||||||
throw new Error('Failed to read angular.json content');
|
|
||||||
}
|
|
||||||
const workspaceJsonAst = parseJsonAst(workspaceContent.toString()) as JsonAstObject;
|
|
||||||
const projects = findPropertyInAstObject(workspaceJsonAst, 'projects');
|
|
||||||
if (!projects) {
|
|
||||||
throw new SchematicsException('Expect projects in angular.json to be an Object');
|
|
||||||
}
|
|
||||||
const project = findPropertyInAstObject(projects as JsonAstObject, name);
|
|
||||||
if (!project) {
|
|
||||||
throw new SchematicsException(`Expected projects to contain ${name}`);
|
|
||||||
}
|
|
||||||
const recorder = host.beginUpdate(workspacePath);
|
|
||||||
const indent = 8;
|
|
||||||
const architect =
|
|
||||||
findPropertyInAstObject(project as JsonAstObject, 'architect') as JsonAstObject;
|
|
||||||
replacePropertyInAstObject(
|
|
||||||
recorder, architect, 'build', {
|
|
||||||
builder: '@angular/bazel:build',
|
|
||||||
options: {
|
|
||||||
targetLabel: '//src:prodapp',
|
|
||||||
bazelCommand: 'build',
|
|
||||||
},
|
|
||||||
configurations: {
|
|
||||||
production: {
|
|
||||||
targetLabel: '//src:prodapp',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
indent);
|
|
||||||
replacePropertyInAstObject(
|
|
||||||
recorder, architect, 'serve', {
|
|
||||||
builder: '@angular/bazel:build',
|
|
||||||
options: {
|
|
||||||
targetLabel: '//src:devserver',
|
|
||||||
bazelCommand: 'run',
|
|
||||||
watch: true,
|
|
||||||
},
|
|
||||||
configurations: {
|
|
||||||
production: {
|
|
||||||
targetLabel: '//src:prodserver',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
indent);
|
|
||||||
|
|
||||||
if (findPropertyInAstObject(architect, 'test')) {
|
|
||||||
replacePropertyInAstObject(
|
|
||||||
recorder, architect, 'test', {
|
|
||||||
builder: '@angular/bazel:build',
|
|
||||||
options: {
|
|
||||||
bazelCommand: 'test',
|
|
||||||
targetLabel: '//src:test',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
const e2eArchitect = findE2eArchitect(workspaceJsonAst, name);
|
|
||||||
if (e2eArchitect && findPropertyInAstObject(e2eArchitect, 'e2e')) {
|
|
||||||
replacePropertyInAstObject(
|
|
||||||
recorder, e2eArchitect, 'e2e', {
|
|
||||||
builder: '@angular/bazel:build',
|
|
||||||
options: {
|
|
||||||
bazelCommand: 'test',
|
|
||||||
targetLabel: '//e2e:devserver_test',
|
|
||||||
},
|
|
||||||
configurations: {
|
|
||||||
production: {
|
|
||||||
targetLabel: '//e2e:prodserver_test',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
host.commitUpdate(recorder);
|
|
||||||
return host;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a backup for the original angular.json file in case user wants to
|
|
||||||
* eject Bazel and revert to the original workflow.
|
|
||||||
*/
|
|
||||||
function backupAngularJson(): Rule {
|
|
||||||
return (host: Tree, context: SchematicContext) => {
|
|
||||||
const workspacePath = getWorkspacePath(host);
|
|
||||||
if (!workspacePath) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
host.create(
|
|
||||||
`${workspacePath}.bak`,
|
|
||||||
'// This is a backup file of the original angular.json. ' +
|
|
||||||
'This file is needed in case you want to revert to the workflow without Bazel.\n\n' +
|
|
||||||
host.read(workspacePath));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @angular/bazel requires minimum version of rxjs to be 6.4.0. This function
|
|
||||||
* upgrades the version of rxjs in package.json if necessary.
|
|
||||||
*/
|
|
||||||
function upgradeRxjs() {
|
|
||||||
return (host: Tree, context: SchematicContext) => {
|
|
||||||
const rxjsNode = getPackageJsonDependency(host, 'rxjs');
|
|
||||||
if (!rxjsNode) {
|
|
||||||
throw new Error(`Failed to find rxjs dependency.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = rxjsNode.version.match(/(\d)+\.(\d)+.(\d)+$/);
|
|
||||||
if (match) {
|
|
||||||
const [_, major, minor] = match;
|
|
||||||
if (major < '6' || (major === '6' && minor < '5')) {
|
|
||||||
addPackageJsonDependency(host, {
|
|
||||||
...rxjsNode,
|
|
||||||
version: '~6.5.3',
|
|
||||||
overwrite: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
context.logger.info(
|
|
||||||
'Could not determine version of rxjs. \n' +
|
|
||||||
'Please make sure that version is at least 6.5.3.');
|
|
||||||
}
|
|
||||||
return host;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When using Ivy, ngcc must be run as a postinstall step.
|
|
||||||
* This function adds this postinstall step.
|
|
||||||
*/
|
|
||||||
function addPostinstallToRunNgcc() {
|
|
||||||
return (host: Tree, context: SchematicContext) => {
|
|
||||||
const packageJson = 'package.json';
|
|
||||||
if (!host.exists(packageJson)) {
|
|
||||||
throw new Error(`Could not find ${packageJson}`);
|
|
||||||
}
|
|
||||||
const content = host.read(packageJson);
|
|
||||||
if (!content) {
|
|
||||||
throw new Error('Failed to read package.json content');
|
|
||||||
}
|
|
||||||
const jsonAst = parseJsonAst(content.toString());
|
|
||||||
if (!isJsonAstObject(jsonAst)) {
|
|
||||||
throw new Error(`Failed to parse JSON for ${packageJson}`);
|
|
||||||
}
|
|
||||||
const scripts = findPropertyInAstObject(jsonAst, 'scripts') as JsonAstObject;
|
|
||||||
const recorder = host.beginUpdate(packageJson);
|
|
||||||
// For bazel we need to compile the all files in place so we
|
|
||||||
// don't use `--first-only` or `--create-ivy-entry-points`
|
|
||||||
const ngccCommand = 'ngcc --properties es2015 browser module main';
|
|
||||||
if (scripts) {
|
|
||||||
const postInstall = findPropertyInAstObject(scripts, 'postinstall');
|
|
||||||
if (postInstall && postInstall.value) {
|
|
||||||
let value = postInstall.value as string;
|
|
||||||
if (/\bngcc\b/.test(value)) {
|
|
||||||
// `ngcc` is already in the postinstall script
|
|
||||||
value =
|
|
||||||
value.replace(/\s*--first-only\b/, '').replace(/\s*--create-ivy-entry-points\b/, '');
|
|
||||||
replacePropertyInAstObject(recorder, scripts, 'postinstall', value);
|
|
||||||
} else {
|
|
||||||
const command = `${postInstall.value}; ${ngccCommand}`;
|
|
||||||
replacePropertyInAstObject(recorder, scripts, 'postinstall', command);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
insertPropertyInAstObjectInOrder(recorder, scripts, 'postinstall', ngccCommand, 4);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
insertPropertyInAstObjectInOrder(
|
|
||||||
recorder, jsonAst, 'scripts', {
|
|
||||||
postinstall: ngccCommand,
|
|
||||||
},
|
|
||||||
2);
|
|
||||||
}
|
|
||||||
host.commitUpdate(recorder);
|
|
||||||
return host;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedule a task to perform npm / yarn install.
|
|
||||||
*/
|
|
||||||
function installNodeModules(options: Schema): Rule {
|
|
||||||
return (host: Tree, context: SchematicContext) => {
|
|
||||||
if (!options.skipInstall) {
|
|
||||||
context.addTask(new NodePackageInstallTask());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function(options: Schema): Rule {
|
|
||||||
return (host: Tree) => {
|
|
||||||
options.name = options.name || getWorkspace(host).defaultProject;
|
|
||||||
if (!options.name) {
|
|
||||||
throw new Error('Please specify a project using "--name project-name"');
|
|
||||||
}
|
|
||||||
validateProjectName(options.name);
|
|
||||||
|
|
||||||
return chain([
|
|
||||||
addFilesRequiredByBazel(options),
|
|
||||||
addDevDependenciesToPackageJson(options),
|
|
||||||
removeObsoleteDependenciesFromPackageJson(options),
|
|
||||||
addPostinstallToRunNgcc(),
|
|
||||||
backupAngularJson(),
|
|
||||||
updateAngularJsonToUseBazelBuilder(options),
|
|
||||||
updateGitignore(),
|
|
||||||
upgradeRxjs(),
|
|
||||||
installNodeModules(options),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,340 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {HostTree} from '@angular-devkit/schematics';
|
|
||||||
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
|
|
||||||
|
|
||||||
describe('ng-add schematic', () => {
|
|
||||||
const defaultOptions = {name: 'demo'};
|
|
||||||
let host: UnitTestTree;
|
|
||||||
let schematicRunner: SchematicTestRunner;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
host = new UnitTestTree(new HostTree());
|
|
||||||
host.create('package.json', JSON.stringify({
|
|
||||||
name: 'demo',
|
|
||||||
dependencies: {
|
|
||||||
'@angular/core': '1.2.3',
|
|
||||||
'rxjs': '~6.3.3',
|
|
||||||
},
|
|
||||||
devDependencies: {
|
|
||||||
'typescript': '3.2.2',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
host.create('tsconfig.json', JSON.stringify({
|
|
||||||
compileOnSave: false,
|
|
||||||
compilerOptions: {
|
|
||||||
baseUrl: './',
|
|
||||||
outDir: './dist/out-tsc',
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
host.create('angular.json', JSON.stringify({
|
|
||||||
projects: {
|
|
||||||
'demo': {
|
|
||||||
architect: {
|
|
||||||
build: {},
|
|
||||||
serve: {},
|
|
||||||
test: {},
|
|
||||||
'extract-i18n': {
|
|
||||||
builder: '@angular-devkit/build-angular:extract-i18n',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'demo-e2e': {
|
|
||||||
architect: {
|
|
||||||
e2e: {},
|
|
||||||
lint: {
|
|
||||||
builder: '@angular-devkit/build-angular:tslint',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultProject: 'demo',
|
|
||||||
}));
|
|
||||||
schematicRunner =
|
|
||||||
new SchematicTestRunner('@angular/bazel', require.resolve('../collection.json'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if package.json is not found', async () => {
|
|
||||||
expect(host.files).toContain('/package.json');
|
|
||||||
host.delete('/package.json');
|
|
||||||
|
|
||||||
let message = 'No error';
|
|
||||||
|
|
||||||
try {
|
|
||||||
await schematicRunner.runSchematicAsync('ng-add', defaultOptions).toPromise();
|
|
||||||
} catch (e) {
|
|
||||||
message = e.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(message).toBe('Could not read package.json.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws if angular.json is not found', async () => {
|
|
||||||
expect(host.files).toContain('/angular.json');
|
|
||||||
host.delete('/angular.json');
|
|
||||||
|
|
||||||
let message = 'No error';
|
|
||||||
|
|
||||||
try {
|
|
||||||
await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
} catch (e) {
|
|
||||||
message = e.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(message).toBe('Could not find angular.json');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add @angular/bazel to package.json dependencies', async () => {
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const {files} = host;
|
|
||||||
expect(files).toContain('/package.json');
|
|
||||||
const content = host.readContent('/package.json');
|
|
||||||
expect(() => JSON.parse(content)).not.toThrow();
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
const core = '@angular/core';
|
|
||||||
const bazel = '@angular/bazel';
|
|
||||||
expect(Object.keys(json)).toContain('dependencies');
|
|
||||||
expect(Object.keys(json)).toContain('devDependencies');
|
|
||||||
expect(Object.keys(json.dependencies)).toContain(core);
|
|
||||||
expect(Object.keys(json.devDependencies)).toContain(bazel);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add @bazel/* dev dependencies', async () => {
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const content = host.readContent('/package.json');
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
const devDeps = Object.keys(json.devDependencies);
|
|
||||||
expect(devDeps).toContain('@bazel/bazel');
|
|
||||||
expect(devDeps).toContain('@bazel/ibazel');
|
|
||||||
expect(devDeps).toContain('@bazel/karma');
|
|
||||||
expect(devDeps).toContain('@bazel/protractor');
|
|
||||||
expect(devDeps).toContain('@bazel/typescript');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should replace an existing dev dependency', async () => {
|
|
||||||
expect(host.files).toContain('/package.json');
|
|
||||||
const packageJson = JSON.parse(host.readContent('/package.json'));
|
|
||||||
packageJson.devDependencies['@angular/bazel'] = '4.2.42';
|
|
||||||
host.overwrite('/package.json', JSON.stringify(packageJson));
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const content = host.readContent('/package.json');
|
|
||||||
// It is possible that a dep gets added twice if the package already exists.
|
|
||||||
expect(content.match(/@angular\/bazel/g)!.length).toEqual(1);
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
expect(json.devDependencies['@angular/bazel']).toBe('1.2.3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove an existing dependency', async () => {
|
|
||||||
expect(host.files).toContain('/package.json');
|
|
||||||
const packageJson = JSON.parse(host.readContent('/package.json'));
|
|
||||||
packageJson.dependencies['@angular/bazel'] = '4.2.42';
|
|
||||||
expect(Object.keys(packageJson.dependencies)).toContain('@angular/bazel');
|
|
||||||
host.overwrite('/package.json', JSON.stringify(packageJson));
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const content = host.readContent('/package.json');
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
expect(Object.keys(json.dependencies)).not.toContain('@angular/bazel');
|
|
||||||
expect(json.devDependencies['@angular/bazel']).toBe('1.2.3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove unneeded dependencies', async () => {
|
|
||||||
const packageJson = JSON.parse(host.readContent('/package.json'));
|
|
||||||
packageJson.devDependencies['@angular-devkit/build-angular'] = '1.2.3';
|
|
||||||
host.overwrite('/package.json', JSON.stringify(packageJson));
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const content = host.readContent('/package.json');
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
expect(json.devDependencies['angular-devkit/build-angular']).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should append to scripts.postinstall if it already exists', async () => {
|
|
||||||
const packageJson = JSON.parse(host.readContent('/package.json'));
|
|
||||||
packageJson['scripts'] = {
|
|
||||||
postinstall: 'angular rocks',
|
|
||||||
};
|
|
||||||
host.overwrite('/package.json', JSON.stringify(packageJson));
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const content = host.readContent('/package.json');
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
expect(json.scripts['postinstall'])
|
|
||||||
.toBe('angular rocks; ngcc --properties es2015 browser module main');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update ngcc in scripts.postinstall if it already exists', async () => {
|
|
||||||
const packageJson = JSON.parse(host.readContent('/package.json'));
|
|
||||||
packageJson['scripts'] = {
|
|
||||||
postinstall:
|
|
||||||
'ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points',
|
|
||||||
};
|
|
||||||
host.overwrite('/package.json', JSON.stringify(packageJson));
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const content = host.readContent('/package.json');
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
expect(json.scripts['postinstall']).toBe('ngcc --properties es2015 browser module main');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not create Bazel workspace file', async () => {
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const {files} = host;
|
|
||||||
expect(files).not.toContain('/WORKSPACE');
|
|
||||||
expect(files).not.toContain('/BUILD.bazel');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should produce main.dev.ts and main.prod.ts for AOT', async () => {
|
|
||||||
host.create('/src/main.ts', 'generated by CLI');
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const {files} = host;
|
|
||||||
// main.dev.ts and main.prod.ts are used by Bazel for AOT
|
|
||||||
expect(files).toContain('/src/main.dev.ts');
|
|
||||||
expect(files).toContain('/src/main.prod.ts');
|
|
||||||
// main.ts is produced by original ng-add schematics
|
|
||||||
// This file should be present for backwards compatibility.
|
|
||||||
expect(files).toContain('/src/main.ts');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not overwrite index.html with script tags', async () => {
|
|
||||||
host.create('/src/index.html', '<html>Hello World</html>');
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const {files} = host;
|
|
||||||
expect(files).toContain('/src/index.html');
|
|
||||||
const content = host.readContent('/src/index.html');
|
|
||||||
expect(content).not.toMatch('<script src="/zone.umd.min.js"></script>');
|
|
||||||
expect(content).not.toMatch('<script src="/bundle.min.js"></script>');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should generate main.dev.ts and main.prod.ts', async () => {
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const {files} = host;
|
|
||||||
expect(files).toContain('/src/main.dev.ts');
|
|
||||||
expect(files).toContain('/src/main.prod.ts');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should overwrite .gitignore for bazel-out directory', async () => {
|
|
||||||
host.create('.gitignore', '\n# compiled output\n');
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const {files} = host;
|
|
||||||
expect(files).toContain('/.gitignore');
|
|
||||||
const content = host.readContent('/.gitignore');
|
|
||||||
expect(content).toMatch('\n# compiled output\n/bazel-out\n');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create a backup for original angular.json', async () => {
|
|
||||||
expect(host.files).toContain('/angular.json');
|
|
||||||
const original = host.readContent('/angular.json');
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
expect(host.files).toContain('/angular.json.bak');
|
|
||||||
const content = host.readContent('/angular.json.bak');
|
|
||||||
expect(content.startsWith('// This is a backup file')).toBe(true);
|
|
||||||
expect(content).toMatch(original);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update angular.json to use Bazel builder', async () => {
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
const {files} = host;
|
|
||||||
expect(files).toContain('/angular.json');
|
|
||||||
const content = host.readContent('/angular.json');
|
|
||||||
expect(() => JSON.parse(content)).not.toThrow();
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
const demo = json.projects.demo;
|
|
||||||
const demo_e2e = json.projects['demo-e2e'];
|
|
||||||
const {build, serve, test} = demo.architect;
|
|
||||||
expect(build.builder).toBe('@angular/bazel:build');
|
|
||||||
expect(serve.builder).toBe('@angular/bazel:build');
|
|
||||||
expect(test.builder).toBe('@angular/bazel:build');
|
|
||||||
const {e2e, lint} = demo_e2e.architect;
|
|
||||||
expect(e2e.builder).toBe('@angular/bazel:build');
|
|
||||||
// it should leave non-Bazel commands unmodified
|
|
||||||
expect(demo.architect['extract-i18n'].builder)
|
|
||||||
.toBe('@angular-devkit/build-angular:extract-i18n');
|
|
||||||
expect(lint.builder).toBe('@angular-devkit/build-angular:tslint');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get defaultProject if name is not provided', async () => {
|
|
||||||
const options = {};
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', options, host).toPromise();
|
|
||||||
const content = host.readContent('/angular.json');
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
const builder = json.projects.demo.architect.build.builder;
|
|
||||||
expect(builder).toBe('@angular/bazel:build');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('rxjs', () => {
|
|
||||||
const cases = [
|
|
||||||
// version|upgrade
|
|
||||||
['6.3.3', true],
|
|
||||||
['~6.3.3', true],
|
|
||||||
['^6.3.3', true],
|
|
||||||
['~6.3.11', true],
|
|
||||||
['6.4.0', true],
|
|
||||||
['~6.4.0', true],
|
|
||||||
['~6.4.1', true],
|
|
||||||
['6.5.0', false],
|
|
||||||
['~6.5.0', false],
|
|
||||||
['^6.5.0', false],
|
|
||||||
['~7.0.1', false],
|
|
||||||
];
|
|
||||||
for (const [version, upgrade] of cases) {
|
|
||||||
it(`should ${upgrade ? '' : 'not '}upgrade v${version}')`, async () => {
|
|
||||||
host.overwrite('package.json', JSON.stringify({
|
|
||||||
name: 'demo',
|
|
||||||
dependencies: {
|
|
||||||
'@angular/core': '1.2.3',
|
|
||||||
'rxjs': version,
|
|
||||||
},
|
|
||||||
devDependencies: {
|
|
||||||
'typescript': '3.2.2',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
expect(host.files).toContain('/package.json');
|
|
||||||
const content = host.readContent('/package.json');
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
if (upgrade) {
|
|
||||||
expect(json.dependencies.rxjs).toBe('~6.5.3');
|
|
||||||
} else {
|
|
||||||
expect(json.dependencies.rxjs).toBe(version);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add a postinstall step to package.json', async () => {
|
|
||||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
expect(host.files).toContain('/package.json');
|
|
||||||
const content = host.readContent('/package.json');
|
|
||||||
const json = JSON.parse(content);
|
|
||||||
expect(json.scripts.postinstall).toBe('ngcc --properties es2015 browser module main');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work when run on a minimal project (without test and e2e targets)', async () => {
|
|
||||||
host.overwrite('angular.json', JSON.stringify({
|
|
||||||
projects: {
|
|
||||||
'demo': {
|
|
||||||
architect: {
|
|
||||||
build: {},
|
|
||||||
serve: {},
|
|
||||||
'extract-i18n': {
|
|
||||||
builder: '@angular-devkit/build-angular:extract-i18n',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
let error: Error|null = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,18 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface Schema {
|
|
||||||
/**
|
|
||||||
* The name of the project.
|
|
||||||
*/
|
|
||||||
name?: string;
|
|
||||||
/**
|
|
||||||
* When true, does not install dependency packages.
|
|
||||||
*/
|
|
||||||
skipInstall?: boolean;
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*
|
|
||||||
* @fileoverview Schematics for ng-new project that builds with Bazel.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {chain, externalSchematic, Rule, schematic, Tree} from '@angular-devkit/schematics';
|
|
||||||
import {validateProjectName} from '@schematics/angular/utility/validation';
|
|
||||||
|
|
||||||
import {Schema} from './schema';
|
|
||||||
|
|
||||||
export default function(options: Schema): Rule {
|
|
||||||
return (host: Tree) => {
|
|
||||||
validateProjectName(options.name);
|
|
||||||
|
|
||||||
return chain([
|
|
||||||
externalSchematic('@schematics/angular', 'ng-new', options),
|
|
||||||
schematic(
|
|
||||||
'ng-add', {
|
|
||||||
name: options.name,
|
|
||||||
// skip install since `ng-new` above will schedule the task
|
|
||||||
skipInstall: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scope: options.name,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
|
||||||
|
|
||||||
describe('ng-new schematic', () => {
|
|
||||||
const schematicRunner = new SchematicTestRunner(
|
|
||||||
'@angular/bazel',
|
|
||||||
require.resolve('../collection.json'),
|
|
||||||
);
|
|
||||||
const defaultOptions = {
|
|
||||||
name: 'demo',
|
|
||||||
version: '7.0.0',
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should call external @schematics/angular', async () => {
|
|
||||||
const options = {...defaultOptions};
|
|
||||||
const host = await schematicRunner.runSchematicAsync('ng-new', options).toPromise();
|
|
||||||
const {files} = host;
|
|
||||||
// External schematic should produce workspace file angular.json
|
|
||||||
expect(files).toContain('/demo/angular.json');
|
|
||||||
expect(files).toContain('/demo/package.json');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call ng-add to generate additional files needed by Bazel', async () => {
|
|
||||||
const options = {...defaultOptions};
|
|
||||||
const host = await schematicRunner.runSchematicAsync('ng-new', options).toPromise();
|
|
||||||
const {files} = host;
|
|
||||||
expect(files).toContain('/demo/src/main.dev.ts');
|
|
||||||
expect(files).toContain('/demo/src/main.prod.ts');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,112 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface Schema {
|
|
||||||
/**
|
|
||||||
* Initial git repository commit information.
|
|
||||||
*/
|
|
||||||
commit?: CommitUnion;
|
|
||||||
/**
|
|
||||||
* When true (the default), creates a new initial app project in the src folder of the new
|
|
||||||
* workspace. When false, creates an empty workspace with no initial app. You can then use
|
|
||||||
* the generate application command so that all apps are created in the projects folder.
|
|
||||||
*/
|
|
||||||
createApplication?: boolean;
|
|
||||||
/**
|
|
||||||
* The directory name to create the workspace in.
|
|
||||||
*/
|
|
||||||
directory?: string;
|
|
||||||
/**
|
|
||||||
* When true, creates a new app that uses the Ivy rendering engine.
|
|
||||||
*/
|
|
||||||
enableIvy?: boolean;
|
|
||||||
/**
|
|
||||||
* When true, includes styles inline in the component TS file. By default, an external
|
|
||||||
* styles file is created and referenced in the component TS file.
|
|
||||||
*/
|
|
||||||
inlineStyle?: boolean;
|
|
||||||
/**
|
|
||||||
* When true, includes template inline in the component TS file. By default, an external
|
|
||||||
* template file is created and referenced in the component TS file.
|
|
||||||
*/
|
|
||||||
inlineTemplate?: boolean;
|
|
||||||
/**
|
|
||||||
* When true, links the CLI to the global version (internal development only).
|
|
||||||
*/
|
|
||||||
linkCli?: boolean;
|
|
||||||
/**
|
|
||||||
* When true, creates a project without any testing frameworks. (Use for learning purposes
|
|
||||||
* only.)
|
|
||||||
*/
|
|
||||||
minimal?: boolean;
|
|
||||||
/**
|
|
||||||
* The name of the new workspace and initial project.
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* The path where new projects will be created, relative to the new workspace root.
|
|
||||||
*/
|
|
||||||
newProjectRoot?: string;
|
|
||||||
/**
|
|
||||||
* The prefix to apply to generated selectors for the initial project.
|
|
||||||
*/
|
|
||||||
prefix?: string;
|
|
||||||
/**
|
|
||||||
* When true, generates a routing module for the initial project.
|
|
||||||
*/
|
|
||||||
routing?: boolean;
|
|
||||||
/**
|
|
||||||
* When true, does not initialize a git repository.
|
|
||||||
*/
|
|
||||||
skipGit?: boolean;
|
|
||||||
/**
|
|
||||||
* When true, does not install dependency packages.
|
|
||||||
*/
|
|
||||||
skipInstall?: boolean;
|
|
||||||
/**
|
|
||||||
* When true, does not generate "spec.ts" test files for the new project.
|
|
||||||
*/
|
|
||||||
skipTests?: boolean;
|
|
||||||
/**
|
|
||||||
* The file extension or preprocessor to use for style files.
|
|
||||||
*/
|
|
||||||
style?: Style;
|
|
||||||
/**
|
|
||||||
* The version of the Angular CLI to use.
|
|
||||||
*/
|
|
||||||
version: string;
|
|
||||||
/**
|
|
||||||
* The view encapsulation strategy to use in the initial project.
|
|
||||||
*/
|
|
||||||
viewEncapsulation?: ViewEncapsulation;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Initial git repository commit information.
|
|
||||||
*/
|
|
||||||
export declare type CommitUnion = boolean | CommitObject;
|
|
||||||
export interface CommitObject {
|
|
||||||
email: string;
|
|
||||||
message?: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The file extension or preprocessor to use for style files.
|
|
||||||
*/
|
|
||||||
export declare enum Style {
|
|
||||||
Css = 'css',
|
|
||||||
Sass = 'sass',
|
|
||||||
Scss = 'scss',
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The view encapsulation strategy to use in the initial project.
|
|
||||||
*/
|
|
||||||
export declare enum ViewEncapsulation {
|
|
||||||
Emulated = 'Emulated',
|
|
||||||
None = 'None',
|
|
||||||
ShadowDom = 'ShadowDom'
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {JsonAstNode, JsonAstObject, JsonValue} from '@angular-devkit/core';
|
|
||||||
import {UpdateRecorder} from '@angular-devkit/schematics';
|
|
||||||
import {findPropertyInAstObject} from '@schematics/angular/utility/json-utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace the value of the key-value pair in the 'node' object with a different
|
|
||||||
* 'value' and record the update using the specified 'recorder'.
|
|
||||||
*/
|
|
||||||
export function replacePropertyInAstObject(
|
|
||||||
recorder: UpdateRecorder, node: JsonAstObject, propertyName: string, value: JsonValue,
|
|
||||||
indent: number = 0) {
|
|
||||||
const property = findPropertyInAstObject(node, propertyName);
|
|
||||||
if (property === null) {
|
|
||||||
throw new Error(`Property '${propertyName}' does not exist in JSON object`);
|
|
||||||
}
|
|
||||||
const {start, text} = property;
|
|
||||||
recorder.remove(start.offset, text.length);
|
|
||||||
const indentStr = '\n' +
|
|
||||||
' '.repeat(indent);
|
|
||||||
const content = JSON.stringify(value, null, ' ').replace(/\n/g, indentStr);
|
|
||||||
recorder.insertLeft(start.offset, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the key-value pair with the specified 'key' in the specified 'node'
|
|
||||||
* object and record the update using the specified 'recorder'.
|
|
||||||
*/
|
|
||||||
export function removeKeyValueInAstObject(
|
|
||||||
recorder: UpdateRecorder, content: string, node: JsonAstObject, key: string) {
|
|
||||||
for (const [i, prop] of node.properties.entries()) {
|
|
||||||
if (prop.key.value === key) {
|
|
||||||
const start = prop.start.offset;
|
|
||||||
const end = prop.end.offset;
|
|
||||||
let length = end - start;
|
|
||||||
const match = content.slice(end).match(/^[,\s]+/);
|
|
||||||
if (match) {
|
|
||||||
length += match.pop()!.length;
|
|
||||||
}
|
|
||||||
recorder.remove(start, length);
|
|
||||||
if (i === node.properties.length - 1) { // last property
|
|
||||||
let offset = 0;
|
|
||||||
while (/(,|\s)/.test(content.charAt(start - offset - 1))) {
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
recorder.remove(start - offset, offset);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the specified 'node' is a JsonAstObject, false otherwise.
|
|
||||||
*/
|
|
||||||
export function isJsonAstObject(node: JsonAstNode|null): node is JsonAstObject {
|
|
||||||
return !!node && node.kind === 'object';
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {JsonAstObject, parseJsonAst} from '@angular-devkit/core';
|
|
||||||
import {HostTree} from '@angular-devkit/schematics';
|
|
||||||
import {UnitTestTree} from '@angular-devkit/schematics/testing';
|
|
||||||
import {isJsonAstObject, removeKeyValueInAstObject, replacePropertyInAstObject} from './json-utils';
|
|
||||||
|
|
||||||
describe('JsonUtils', () => {
|
|
||||||
let tree: UnitTestTree;
|
|
||||||
beforeEach(() => {
|
|
||||||
tree = new UnitTestTree(new HostTree());
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('replacePropertyInAstObject', () => {
|
|
||||||
it('should replace property', () => {
|
|
||||||
const content = JSON.stringify({foo: {bar: 'baz'}});
|
|
||||||
tree.create('tmp', content);
|
|
||||||
const ast = parseJsonAst(content) as JsonAstObject;
|
|
||||||
const recorder = tree.beginUpdate('tmp');
|
|
||||||
replacePropertyInAstObject(recorder, ast, 'foo', [1, 2, 3]);
|
|
||||||
tree.commitUpdate(recorder);
|
|
||||||
const value = tree.readContent('tmp');
|
|
||||||
expect(JSON.parse(value)).toEqual({
|
|
||||||
foo: [1, 2, 3],
|
|
||||||
});
|
|
||||||
expect(value).toBe(`{"foo":[
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
3
|
|
||||||
]}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should respect the indent parameter', () => {
|
|
||||||
const content = JSON.stringify({hello: 'world'}, null, 2);
|
|
||||||
tree.create('tmp', content);
|
|
||||||
const ast = parseJsonAst(content) as JsonAstObject;
|
|
||||||
const recorder = tree.beginUpdate('tmp');
|
|
||||||
replacePropertyInAstObject(recorder, ast, 'hello', 'world!', 2);
|
|
||||||
tree.commitUpdate(recorder);
|
|
||||||
const value = tree.readContent('tmp');
|
|
||||||
expect(JSON.parse(value)).toEqual({
|
|
||||||
hello: 'world!',
|
|
||||||
});
|
|
||||||
expect(value).toBe(`{
|
|
||||||
"hello": "world!"
|
|
||||||
}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw error if property is not found', () => {
|
|
||||||
const content = JSON.stringify({});
|
|
||||||
tree.create('tmp', content);
|
|
||||||
const ast = parseJsonAst(content) as JsonAstObject;
|
|
||||||
const recorder = tree.beginUpdate('tmp');
|
|
||||||
expect(() => replacePropertyInAstObject(recorder, ast, 'foo', 'bar'))
|
|
||||||
.toThrowError(`Property 'foo' does not exist in JSON object`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('removeKeyValueInAstObject', () => {
|
|
||||||
it('should remove key-value pair', () => {
|
|
||||||
const content = JSON.stringify({hello: 'world', foo: 'bar'});
|
|
||||||
tree.create('tmp', content);
|
|
||||||
const ast = parseJsonAst(content) as JsonAstObject;
|
|
||||||
let recorder = tree.beginUpdate('tmp');
|
|
||||||
removeKeyValueInAstObject(recorder, content, ast, 'foo');
|
|
||||||
tree.commitUpdate(recorder);
|
|
||||||
const tmp = tree.readContent('tmp');
|
|
||||||
expect(JSON.parse(tmp)).toEqual({
|
|
||||||
hello: 'world',
|
|
||||||
});
|
|
||||||
expect(tmp).toBe('{"hello":"world"}');
|
|
||||||
recorder = tree.beginUpdate('tmp');
|
|
||||||
const newContent = tree.readContent('tmp');
|
|
||||||
removeKeyValueInAstObject(recorder, newContent, ast, 'hello');
|
|
||||||
tree.commitUpdate(recorder);
|
|
||||||
const value = tree.readContent('tmp');
|
|
||||||
expect(JSON.parse(value)).toEqual({});
|
|
||||||
expect(value).toBe('{}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be a noop if key is not found', () => {
|
|
||||||
const content = JSON.stringify({foo: 'bar'});
|
|
||||||
tree.create('tmp', content);
|
|
||||||
const ast = parseJsonAst(content) as JsonAstObject;
|
|
||||||
let recorder = tree.beginUpdate('tmp');
|
|
||||||
expect(() => removeKeyValueInAstObject(recorder, content, ast, 'hello')).not.toThrow();
|
|
||||||
tree.commitUpdate(recorder);
|
|
||||||
const value = tree.readContent('tmp');
|
|
||||||
expect(JSON.parse(value)).toEqual({foo: 'bar'});
|
|
||||||
expect(value).toBe('{"foo":"bar"}');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('isJsonAstObject', () => {
|
|
||||||
it('should return true for an object', () => {
|
|
||||||
const ast = parseJsonAst(JSON.stringify({}));
|
|
||||||
expect(isJsonAstObject(ast)).toBe(true);
|
|
||||||
});
|
|
||||||
it('should return false for a non-object', () => {
|
|
||||||
const ast = parseJsonAst(JSON.stringify([]));
|
|
||||||
expect(isJsonAstObject(ast)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,40 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {JsonAstNode, JsonAstObject} from '@angular-devkit/core';
|
|
||||||
import {findPropertyInAstObject} from '@schematics/angular/utility/json-utils';
|
|
||||||
import {isJsonAstObject} from './json-utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the e2e architect node in the JSON ast.
|
|
||||||
* The e2e application is relocated alongside the existing application.
|
|
||||||
* This function supports looking up the e2e architect in both the new and old
|
|
||||||
* layout.
|
|
||||||
* See https://github.com/angular/angular-cli/pull/13780
|
|
||||||
*/
|
|
||||||
export function findE2eArchitect(ast: JsonAstObject, name: string): JsonAstObject|null {
|
|
||||||
const projects = findPropertyInAstObject(ast, 'projects');
|
|
||||||
if (!isJsonAstObject(projects)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let architect: JsonAstNode|null;
|
|
||||||
const e2e = findPropertyInAstObject(projects, `${name}-e2e`);
|
|
||||||
if (isJsonAstObject(e2e)) {
|
|
||||||
architect = findPropertyInAstObject(e2e, 'architect');
|
|
||||||
} else {
|
|
||||||
const project = findPropertyInAstObject(projects, name);
|
|
||||||
if (!isJsonAstObject(project)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
architect = findPropertyInAstObject(project, 'architect');
|
|
||||||
}
|
|
||||||
if (!isJsonAstObject(architect)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return architect;
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google LLC All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {JsonAstObject, parseJsonAst} from '@angular-devkit/core';
|
|
||||||
import {isJsonAstObject} from './json-utils';
|
|
||||||
import {findE2eArchitect} from './workspace-utils';
|
|
||||||
|
|
||||||
describe('Workspace utils', () => {
|
|
||||||
describe('findE2eArchitect', () => {
|
|
||||||
it('should find e2e architect in old project layout', () => {
|
|
||||||
const workspace = {
|
|
||||||
projects: {
|
|
||||||
demo: {},
|
|
||||||
'demo-e2e': {
|
|
||||||
architect: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const ast = parseJsonAst(JSON.stringify(workspace));
|
|
||||||
const architect = findE2eArchitect(ast as JsonAstObject, 'demo');
|
|
||||||
expect(isJsonAstObject(architect)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find e2e architect in new project layout', () => {
|
|
||||||
const workspace = {
|
|
||||||
projects: {
|
|
||||||
demo: {
|
|
||||||
architect: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const ast = parseJsonAst(JSON.stringify(workspace));
|
|
||||||
const architect = findE2eArchitect(ast as JsonAstObject, 'demo');
|
|
||||||
expect(isJsonAstObject(architect)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in New Issue