mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-28 02:58:04 +08:00
refactor(@schematics/angular): remove version 15 migrations
These are no longer used.
This commit is contained in:
parent
05d9c91159
commit
ada4d694c3
@ -1,29 +1,5 @@
|
||||
{
|
||||
"schematics": {
|
||||
"remove-browserslist-config": {
|
||||
"version": "15.0.0",
|
||||
"factory": "./update-15/remove-browserslist-config",
|
||||
"description": "Remove Browserslist configuration files that matches the Angular CLI default configuration."
|
||||
},
|
||||
"remove-platform-server-exports": {
|
||||
"version": "15.0.0",
|
||||
"factory": "./update-15/remove-platform-server-exports",
|
||||
"description": "Remove exported `@angular/platform-server` `renderModule` method. The `renderModule` method is now exported by the Angular CLI."
|
||||
},
|
||||
"update-typescript-target": {
|
||||
"version": "15.0.0",
|
||||
"factory": "./update-15/update-typescript-target",
|
||||
"description": "Update TypeScript compiler `target` and set `useDefineForClassFields`. These changes are for IDE purposes as TypeScript compiler options `target` and `useDefineForClassFields` are set to `ES2022` and `false` respectively by the Angular CLI. To control ECMA version and features use the Browerslist configuration."
|
||||
},
|
||||
"update-workspace-config": {
|
||||
"version": "15.0.0",
|
||||
"factory": "./update-15/update-workspace-config",
|
||||
"description": "Remove options from 'angular.json' that are no longer supported by the official builders."
|
||||
},
|
||||
"update-karma-main-file": {
|
||||
"version": "15.0.0",
|
||||
"factory": "./update-15/update-karma-main-file",
|
||||
"description": "Remove no longer needed require calls in Karma builder main file."
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,90 +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 { Path, join } from '@angular-devkit/core';
|
||||
import { DirEntry, Rule } from '@angular-devkit/schematics';
|
||||
|
||||
const validBrowserslistConfigFilenames = new Set(['browserslist', '.browserslistrc']);
|
||||
|
||||
export const DEFAULT_BROWSERS = [
|
||||
'last 1 Chrome version',
|
||||
'last 1 Firefox version',
|
||||
'last 2 Edge major versions',
|
||||
'last 2 Safari major versions',
|
||||
'last 2 iOS major versions',
|
||||
'Firefox ESR',
|
||||
];
|
||||
|
||||
function* visit(directory: DirEntry): IterableIterator<Path> {
|
||||
for (const path of directory.subfiles) {
|
||||
if (validBrowserslistConfigFilenames.has(path)) {
|
||||
yield join(directory.path, path);
|
||||
}
|
||||
}
|
||||
|
||||
for (const path of directory.subdirs) {
|
||||
if (path === 'node_modules') {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield* visit(directory.dir(path));
|
||||
}
|
||||
}
|
||||
|
||||
export default function (): Rule {
|
||||
return async (tree, { logger }) => {
|
||||
let browserslist: typeof import('browserslist') | undefined;
|
||||
|
||||
try {
|
||||
browserslist = (await import('browserslist')).default;
|
||||
} catch {
|
||||
logger.warn('Skipping migration because the "browserslist" package could not be loaded.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the defaults to match the defaults in build-angular.
|
||||
browserslist.defaults = DEFAULT_BROWSERS;
|
||||
|
||||
const defaultSupportedBrowsers = new Set(browserslist(DEFAULT_BROWSERS));
|
||||
const es5Browsers = new Set(browserslist(['supports es6-module']));
|
||||
|
||||
for (const path of visit(tree.root)) {
|
||||
const { defaults: browsersListConfig, ...otherConfigs } = browserslist.parseConfig(
|
||||
tree.readText(path),
|
||||
);
|
||||
|
||||
if (Object.keys(otherConfigs).length) {
|
||||
// The config contains additional sections.
|
||||
continue;
|
||||
}
|
||||
|
||||
const browserslistInProject = browserslist(
|
||||
// Exclude from the list ES5 browsers which are not supported.
|
||||
browsersListConfig.map((s) => `${s} and supports es6-module`),
|
||||
{
|
||||
ignoreUnknownVersions: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (defaultSupportedBrowsers.size !== browserslistInProject.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const shouldDelete = browserslistInProject.every((browser) =>
|
||||
defaultSupportedBrowsers.has(browser),
|
||||
);
|
||||
|
||||
if (shouldDelete) {
|
||||
// All browsers are the same as the default config.
|
||||
// Delete file as it's redundant.
|
||||
tree.delete(path);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -1,84 +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 { EmptyTree } from '@angular-devkit/schematics';
|
||||
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { DEFAULT_BROWSERS } from './remove-browserslist-config';
|
||||
|
||||
describe('Migration to delete Browserslist configurations', () => {
|
||||
const schematicName = 'remove-browserslist-config';
|
||||
|
||||
const schematicRunner = new SchematicTestRunner(
|
||||
'migrations',
|
||||
require.resolve('../migration-collection.json'),
|
||||
);
|
||||
|
||||
let tree: UnitTestTree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = new UnitTestTree(new EmptyTree());
|
||||
});
|
||||
|
||||
describe('given the Browserslist config matches the default', () => {
|
||||
it('should delete ".browserslistrc" file', async () => {
|
||||
tree.create('/src/app/.browserslistrc', DEFAULT_BROWSERS.join('\n'));
|
||||
expect(tree.exists('/src/app/.browserslistrc')).toBeTrue();
|
||||
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
expect(newTree.exists('/src/app/.browserslistrc')).toBeFalse();
|
||||
});
|
||||
|
||||
it(`should not delete "browserslist" in 'node_modules'`, async () => {
|
||||
tree.create('/node_modules/browserslist', DEFAULT_BROWSERS.join('\n'));
|
||||
tree.create('/node_modules/.browserslistrc', DEFAULT_BROWSERS.join('\n'));
|
||||
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
expect(newTree.exists('/node_modules/browserslist')).toBeTrue();
|
||||
expect(newTree.exists('/node_modules/.browserslistrc')).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('given the Browserslist config does not match the default', () => {
|
||||
it('should not delete "browserslist"', async () => {
|
||||
tree.create('/src/app/browserslist', 'last 1 Chrome version');
|
||||
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
expect(newTree.exists('/src/app/browserslist')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should not delete ".browserslistrc"', async () => {
|
||||
tree.create('/src/app/.browserslistrc', 'last 1 Chrome version');
|
||||
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
expect(newTree.exists('/src/app/.browserslistrc')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should delete ".browserslistrc" file when it only includes non supported ES5 browsers', async () => {
|
||||
tree.create('/src/app/.browserslistrc', [...DEFAULT_BROWSERS, 'IE 10'].join('\n'));
|
||||
expect(tree.exists('/src/app/.browserslistrc')).toBeTrue();
|
||||
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
expect(newTree.exists('/src/app/.browserslistrc')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should not delete ".browserslistrc" file when it includes additional config sections', async () => {
|
||||
tree.create(
|
||||
'/src/app/.browserslistrc',
|
||||
`
|
||||
${DEFAULT_BROWSERS.join('\n')}
|
||||
[modern]
|
||||
last 1 chrome version
|
||||
`,
|
||||
);
|
||||
expect(tree.exists('/src/app/.browserslistrc')).toBeTrue();
|
||||
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
expect(newTree.exists('/src/app/.browserslistrc')).toBeTrue();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,99 +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 { DirEntry, Rule, UpdateRecorder } from '@angular-devkit/schematics';
|
||||
import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript';
|
||||
|
||||
function* visit(directory: DirEntry): IterableIterator<ts.SourceFile> {
|
||||
for (const path of directory.subfiles) {
|
||||
if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {
|
||||
const entry = directory.file(path);
|
||||
if (entry) {
|
||||
const content = entry.content;
|
||||
if (content.includes('@angular/platform-server') && content.includes('renderModule')) {
|
||||
const source = ts.createSourceFile(
|
||||
entry.path,
|
||||
content.toString().replace(/^\uFEFF/, ''),
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
);
|
||||
|
||||
yield source;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const path of directory.subdirs) {
|
||||
if (path === 'node_modules' || path.startsWith('.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield* visit(directory.dir(path));
|
||||
}
|
||||
}
|
||||
|
||||
export default function (): Rule {
|
||||
return (tree) => {
|
||||
for (const sourceFile of visit(tree.root)) {
|
||||
let recorder: UpdateRecorder | undefined;
|
||||
let printer: ts.Printer | undefined;
|
||||
|
||||
ts.forEachChild(sourceFile, function analyze(node) {
|
||||
if (
|
||||
!(
|
||||
ts.isExportDeclaration(node) &&
|
||||
node.moduleSpecifier &&
|
||||
ts.isStringLiteral(node.moduleSpecifier) &&
|
||||
node.moduleSpecifier.text === '@angular/platform-server' &&
|
||||
node.exportClause &&
|
||||
ts.isNamedExports(node.exportClause)
|
||||
)
|
||||
) {
|
||||
// Not a @angular/platform-server named export.
|
||||
return;
|
||||
}
|
||||
|
||||
const exportClause = node.exportClause;
|
||||
const newElements: ts.ExportSpecifier[] = [];
|
||||
for (const element of exportClause.elements) {
|
||||
if (element.name.text !== 'renderModule') {
|
||||
newElements.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
if (newElements.length === exportClause.elements.length) {
|
||||
// No changes
|
||||
return;
|
||||
}
|
||||
|
||||
recorder ??= tree.beginUpdate(sourceFile.fileName);
|
||||
|
||||
if (newElements.length) {
|
||||
// Update named exports as there are leftovers.
|
||||
const newExportClause = ts.factory.updateNamedExports(exportClause, newElements);
|
||||
printer ??= ts.createPrinter();
|
||||
const fix = printer.printNode(ts.EmitHint.Unspecified, newExportClause, sourceFile);
|
||||
|
||||
const index = exportClause.getStart();
|
||||
const length = exportClause.getWidth();
|
||||
recorder.remove(index, length).insertLeft(index, fix);
|
||||
} else {
|
||||
// Delete export as no exports remain.
|
||||
recorder.remove(node.getStart(), node.getWidth());
|
||||
}
|
||||
|
||||
ts.forEachChild(node, analyze);
|
||||
});
|
||||
|
||||
if (recorder) {
|
||||
tree.commitUpdate(recorder);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -1,85 +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 { EmptyTree } from '@angular-devkit/schematics';
|
||||
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
|
||||
|
||||
describe('Migration to delete platform-server exports', () => {
|
||||
const schematicName = 'remove-platform-server-exports';
|
||||
|
||||
const schematicRunner = new SchematicTestRunner(
|
||||
'migrations',
|
||||
require.resolve('../migration-collection.json'),
|
||||
);
|
||||
|
||||
let tree: EmptyTree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = new EmptyTree();
|
||||
});
|
||||
|
||||
const testTypeScriptFilePath = './test.ts';
|
||||
|
||||
describe(`Migration to remove '@angular/platform-server' exports`, () => {
|
||||
it(`should delete '@angular/platform-server' export when 'renderModule' is the only exported symbol`, async () => {
|
||||
tree.create(
|
||||
testTypeScriptFilePath,
|
||||
`
|
||||
import { Path, join } from '@angular-devkit/core';
|
||||
export { renderModule } from '@angular/platform-server';
|
||||
`,
|
||||
);
|
||||
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
const content = newTree.readText(testTypeScriptFilePath);
|
||||
expect(content).not.toContain('@angular/platform-server');
|
||||
expect(content).toContain(`import { Path, join } from '@angular-devkit/core';`);
|
||||
});
|
||||
|
||||
it(`should delete only 'renderModule' when there are additional exports`, async () => {
|
||||
tree.create(
|
||||
testTypeScriptFilePath,
|
||||
`
|
||||
import { Path, join } from '@angular-devkit/core';
|
||||
export { renderModule, ServerModule } from '@angular/platform-server';
|
||||
`,
|
||||
);
|
||||
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
const content = newTree.readContent(testTypeScriptFilePath);
|
||||
expect(content).toContain(`import { Path, join } from '@angular-devkit/core';`);
|
||||
expect(content).toContain(`export { ServerModule } from '@angular/platform-server';`);
|
||||
});
|
||||
|
||||
it(`should not delete 'renderModule' when it's exported from another module`, async () => {
|
||||
tree.create(
|
||||
testTypeScriptFilePath,
|
||||
`
|
||||
export { renderModule } from '@angular/core';
|
||||
`,
|
||||
);
|
||||
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
const content = newTree.readText(testTypeScriptFilePath);
|
||||
expect(content).toContain(`export { renderModule } from '@angular/core';`);
|
||||
});
|
||||
|
||||
it(`should not delete 'renderModule' when it's imported from '@angular/platform-server'`, async () => {
|
||||
tree.create(
|
||||
testTypeScriptFilePath,
|
||||
`
|
||||
import { renderModule } from '@angular/platform-server';
|
||||
`,
|
||||
);
|
||||
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
const content = newTree.readText(testTypeScriptFilePath);
|
||||
expect(content).toContain(`import { renderModule } from '@angular/platform-server'`);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,101 +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 { Rule, Tree } from '@angular-devkit/schematics';
|
||||
import * as ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript';
|
||||
import { readWorkspace } from '../../utility';
|
||||
import { allTargetOptions } from '../../utility/workspace';
|
||||
import { Builders } from '../../utility/workspace-models';
|
||||
|
||||
export default function (): Rule {
|
||||
return async (host) => {
|
||||
for (const file of await findTestMainFiles(host)) {
|
||||
updateTestFile(host, file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function findTestMainFiles(host: Tree): Promise<Set<string>> {
|
||||
const testFiles = new Set<string>();
|
||||
const workspace = await readWorkspace(host);
|
||||
|
||||
// find all test.ts files.
|
||||
for (const project of workspace.projects.values()) {
|
||||
for (const target of project.targets.values()) {
|
||||
if (target.builder !== Builders.Karma) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [, options] of allTargetOptions(target)) {
|
||||
if (typeof options.main === 'string') {
|
||||
testFiles.add(options.main);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return testFiles;
|
||||
}
|
||||
|
||||
function updateTestFile(host: Tree, file: string): void {
|
||||
const content = host.readText(file);
|
||||
if (!content.includes('require.context')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceFile = ts.createSourceFile(
|
||||
file,
|
||||
content.replace(/^\uFEFF/, ''),
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
);
|
||||
|
||||
const usedVariableNames = new Set<string>();
|
||||
const recorder = host.beginUpdate(sourceFile.fileName);
|
||||
|
||||
ts.forEachChild(sourceFile, (node) => {
|
||||
if (ts.isVariableStatement(node)) {
|
||||
const variableDeclaration = node.declarationList.declarations[0];
|
||||
|
||||
if (ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword)) {
|
||||
// `declare const require`
|
||||
if (variableDeclaration.name.getText() !== 'require') {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// `const context = require.context('./', true, /\.spec\.ts$/);`
|
||||
if (!variableDeclaration.initializer?.getText().startsWith('require.context')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add variable name as used.
|
||||
usedVariableNames.add(variableDeclaration.name.getText());
|
||||
}
|
||||
|
||||
// Delete node.
|
||||
recorder.remove(node.getFullStart(), node.getFullWidth());
|
||||
}
|
||||
|
||||
if (
|
||||
usedVariableNames.size &&
|
||||
ts.isExpressionStatement(node) && // context.keys().map(context);
|
||||
ts.isCallExpression(node.expression) && // context.keys().map(context);
|
||||
ts.isPropertyAccessExpression(node.expression.expression) && // context.keys().map
|
||||
ts.isCallExpression(node.expression.expression.expression) && // context.keys()
|
||||
ts.isPropertyAccessExpression(node.expression.expression.expression.expression) && // context.keys
|
||||
ts.isIdentifier(node.expression.expression.expression.expression.expression) && // context
|
||||
usedVariableNames.has(node.expression.expression.expression.expression.expression.getText())
|
||||
) {
|
||||
// `context.keys().map(context);`
|
||||
// `context.keys().forEach(context);`
|
||||
recorder.remove(node.getFullStart(), node.getFullWidth());
|
||||
}
|
||||
});
|
||||
|
||||
host.commitUpdate(recorder);
|
||||
}
|
@ -1,151 +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 { tags } from '@angular-devkit/core';
|
||||
import { EmptyTree } from '@angular-devkit/schematics';
|
||||
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
|
||||
|
||||
function createWorkspace(tree: UnitTestTree): void {
|
||||
const angularConfig: WorkspaceSchema = {
|
||||
version: 1,
|
||||
projects: {
|
||||
app: {
|
||||
root: '',
|
||||
sourceRoot: 'src',
|
||||
projectType: ProjectType.Application,
|
||||
prefix: 'app',
|
||||
architect: {
|
||||
test: {
|
||||
builder: Builders.Karma,
|
||||
options: {
|
||||
main: 'test.ts',
|
||||
karmaConfig: './karma.config.js',
|
||||
tsConfig: 'test-spec.json',
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
main: 'test-multiple-context.ts',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
|
||||
tree.create(
|
||||
'test.ts',
|
||||
tags.stripIndents`
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(path: string, deep?: boolean, filter?: RegExp): {
|
||||
<T>(id: string): T;
|
||||
keys(): string[];
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting(),
|
||||
);
|
||||
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
||||
`,
|
||||
);
|
||||
|
||||
tree.create(
|
||||
'test-multiple-context.ts',
|
||||
tags.stripIndents`
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(path: string, deep?: boolean, filter?: RegExp): {
|
||||
<T>(id: string): T;
|
||||
keys(): string[];
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting(),
|
||||
);
|
||||
|
||||
// Then we find all the tests.
|
||||
const context1 = require.context('./', true, /\.spec\.ts$/);
|
||||
const context2 = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context2.keys().forEach(context2);
|
||||
context1.keys().map(context1);
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
describe(`Migration to karma builder main file (test.ts)`, () => {
|
||||
const schematicName = 'update-karma-main-file';
|
||||
|
||||
const schematicRunner = new SchematicTestRunner(
|
||||
'migrations',
|
||||
require.resolve('../migration-collection.json'),
|
||||
);
|
||||
|
||||
let tree: UnitTestTree;
|
||||
beforeEach(() => {
|
||||
tree = new UnitTestTree(new EmptyTree());
|
||||
createWorkspace(tree);
|
||||
});
|
||||
|
||||
it(`should remove 'declare const require' and 'require.context' usages`, async () => {
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
expect(newTree.readText('test.ts')).toBe(tags.stripIndents`
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting(),
|
||||
);
|
||||
`);
|
||||
});
|
||||
|
||||
it(`should remove multiple 'require.context' usages`, async () => {
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
expect(newTree.readText('test-multiple-context.ts')).toBe(tags.stripIndents`
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting(),
|
||||
);
|
||||
`);
|
||||
});
|
||||
});
|
@ -1,87 +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 { JsonObject } from '@angular-devkit/core';
|
||||
import { Rule, Tree } from '@angular-devkit/schematics';
|
||||
import { JSONFile } from '../../utility/json-file';
|
||||
import { getWorkspace } from '../../utility/workspace';
|
||||
import { Builders } from '../../utility/workspace-models';
|
||||
|
||||
export default function (): Rule {
|
||||
return async (host, context) => {
|
||||
// Workspace level tsconfig
|
||||
updateTarget(host, 'tsconfig.json');
|
||||
|
||||
const workspace = await getWorkspace(host);
|
||||
|
||||
// Find all tsconfig which are refereces used by builders
|
||||
for (const [, project] of workspace.projects) {
|
||||
for (const [targetName, target] of project.targets) {
|
||||
// Update all other known CLI builders that use a tsconfig
|
||||
const tsConfigs = [target.options || {}, ...Object.values(target.configurations || {})]
|
||||
.filter((opt) => typeof opt?.tsConfig === 'string')
|
||||
.map((opt) => (opt as { tsConfig: string }).tsConfig);
|
||||
|
||||
const uniqueTsConfigs = new Set(tsConfigs);
|
||||
for (const tsConfig of uniqueTsConfigs) {
|
||||
if (host.exists(tsConfig)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uniqueTsConfigs.delete(tsConfig);
|
||||
context.logger.warn(
|
||||
`'${tsConfig}' referenced in the '${targetName}' target does not exist.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!uniqueTsConfigs.size) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (target.builder as Builders) {
|
||||
case Builders.Server:
|
||||
case Builders.Karma:
|
||||
case Builders.Browser:
|
||||
case Builders.NgPackagr:
|
||||
for (const tsConfig of uniqueTsConfigs) {
|
||||
removeOrUpdateTarget(host, tsConfig);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function removeOrUpdateTarget(host: Tree, tsConfigPath: string): void {
|
||||
const json = new JSONFile(host, tsConfigPath);
|
||||
if (typeof json.get(['extends']) === 'string') {
|
||||
json.remove(['compilerOptions', 'target']);
|
||||
} else {
|
||||
updateTarget(host, tsConfigPath);
|
||||
}
|
||||
}
|
||||
|
||||
const ESNEXT_ES2022_REGEXP = /^es(?:next|2022)$/i;
|
||||
function updateTarget(host: Tree, tsConfigPath: string): void {
|
||||
const json = new JSONFile(host, tsConfigPath);
|
||||
const jsonPath = ['compilerOptions'];
|
||||
const compilerOptions = json.get(jsonPath);
|
||||
|
||||
if (compilerOptions && typeof compilerOptions === 'object') {
|
||||
const { target } = compilerOptions as JsonObject;
|
||||
|
||||
if (typeof target === 'string' && !ESNEXT_ES2022_REGEXP.test(target)) {
|
||||
json.modify(jsonPath, {
|
||||
...compilerOptions,
|
||||
'target': 'ES2022',
|
||||
'useDefineForClassFields': false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,147 +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 { isJsonObject } from '@angular-devkit/core';
|
||||
import { EmptyTree } from '@angular-devkit/schematics';
|
||||
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
|
||||
|
||||
describe('Migration to update target and add useDefineForClassFields', () => {
|
||||
const schematicName = 'update-typescript-target';
|
||||
|
||||
const schematicRunner = new SchematicTestRunner(
|
||||
'migrations',
|
||||
require.resolve('../migration-collection.json'),
|
||||
);
|
||||
|
||||
function createJsonFile(tree: EmptyTree, filePath: string, content: {}): void {
|
||||
const stringifiedContent = JSON.stringify(content, undefined, 2);
|
||||
if (tree.exists(filePath)) {
|
||||
tree.overwrite(filePath, stringifiedContent);
|
||||
} else {
|
||||
tree.create(filePath, stringifiedContent);
|
||||
}
|
||||
}
|
||||
|
||||
function getCompilerOptionsValue(tree: UnitTestTree, filePath: string): Record<string, unknown> {
|
||||
const json = tree.readJson(filePath);
|
||||
if (isJsonObject(json) && isJsonObject(json.compilerOptions)) {
|
||||
return json.compilerOptions;
|
||||
}
|
||||
|
||||
throw new Error(`Cannot retrieve 'compilerOptions'.`);
|
||||
}
|
||||
|
||||
function createWorkSpaceConfig(tree: EmptyTree) {
|
||||
const angularConfig: WorkspaceSchema = {
|
||||
version: 1,
|
||||
projects: {
|
||||
app: {
|
||||
root: '',
|
||||
sourceRoot: 'src',
|
||||
projectType: ProjectType.Application,
|
||||
prefix: 'app',
|
||||
architect: {
|
||||
build: {
|
||||
builder: Builders.Browser,
|
||||
options: {
|
||||
tsConfig: 'src/tsconfig.app.json',
|
||||
main: '',
|
||||
polyfills: '',
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
tsConfig: 'src/tsconfig.app.prod.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
test: {
|
||||
builder: Builders.Karma,
|
||||
options: {
|
||||
karmaConfig: '',
|
||||
tsConfig: 'src/tsconfig.spec.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
createJsonFile(tree, 'angular.json', angularConfig);
|
||||
}
|
||||
|
||||
let tree: EmptyTree;
|
||||
beforeEach(() => {
|
||||
tree = new EmptyTree();
|
||||
createWorkSpaceConfig(tree);
|
||||
|
||||
// Create tsconfigs
|
||||
const compilerOptions = { target: 'es2015', module: 'es2020' };
|
||||
const configWithExtends = { extends: './tsconfig.json', compilerOptions };
|
||||
|
||||
// Workspace
|
||||
createJsonFile(tree, 'tsconfig.json', { compilerOptions });
|
||||
|
||||
// Application
|
||||
createJsonFile(tree, 'src/tsconfig.app.json', configWithExtends);
|
||||
createJsonFile(tree, 'src/tsconfig.app.prod.json', configWithExtends);
|
||||
createJsonFile(tree, 'src/tsconfig.spec.json', { compilerOptions });
|
||||
});
|
||||
|
||||
it(`should update target and add useDefineForClassFields in workspace 'tsconfig.json'`, async () => {
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
const compilerOptions = getCompilerOptionsValue(newTree, 'tsconfig.json');
|
||||
expect(compilerOptions).toEqual(
|
||||
jasmine.objectContaining({
|
||||
target: 'ES2022',
|
||||
useDefineForClassFields: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it(`should remove target value from tsconfig referenced in options and configuration`, async () => {
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
{
|
||||
const compilerOptions = getCompilerOptionsValue(newTree, 'src/tsconfig.app.prod.json');
|
||||
expect(compilerOptions['target']).toBeUndefined();
|
||||
expect(compilerOptions['useDefineForClassFields']).toBeUndefined();
|
||||
}
|
||||
{
|
||||
const compilerOptions = getCompilerOptionsValue(newTree, 'src/tsconfig.app.json');
|
||||
expect(compilerOptions['target']).toBeUndefined();
|
||||
expect(compilerOptions['useDefineForClassFields']).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should add target and useDefineForClassFields when tsconfig is not extended', async () => {
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
const compilerOptions = getCompilerOptionsValue(newTree, 'src/tsconfig.spec.json');
|
||||
expect(compilerOptions).toEqual(
|
||||
jasmine.objectContaining({
|
||||
target: 'ES2022',
|
||||
useDefineForClassFields: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add useDefineForClassFields when tsconfig target is ES2022', async () => {
|
||||
createJsonFile(tree, 'tsconfig.json', { compilerOptions: { 'target': 'es2022' } });
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
|
||||
const compilerOptions = getCompilerOptionsValue(newTree, 'tsconfig.json');
|
||||
expect(compilerOptions).toEqual({ target: 'es2022' });
|
||||
});
|
||||
|
||||
it('should not add useDefineForClassFields when tsconfig target is ESNEXT', async () => {
|
||||
createJsonFile(tree, 'tsconfig.json', { compilerOptions: { 'target': 'esnext' } });
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
|
||||
const compilerOptions = getCompilerOptionsValue(newTree, 'tsconfig.json');
|
||||
expect(compilerOptions).toEqual({ target: 'esnext' });
|
||||
});
|
||||
});
|
@ -1,31 +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 { Rule } from '@angular-devkit/schematics';
|
||||
import { allTargetOptions, updateWorkspace } from '../../utility/workspace';
|
||||
import { Builders } from '../../utility/workspace-models';
|
||||
|
||||
export default function (): Rule {
|
||||
return updateWorkspace((workspace) => {
|
||||
for (const project of workspace.projects.values()) {
|
||||
for (const target of project.targets.values()) {
|
||||
if (target.builder !== Builders.Server) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [name, options] of allTargetOptions(target)) {
|
||||
delete options.bundleDependencies;
|
||||
|
||||
if (name === 'development') {
|
||||
options.vendorChunk ??= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@ -1,99 +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 { JsonObject } from '@angular-devkit/core';
|
||||
import { EmptyTree } from '@angular-devkit/schematics';
|
||||
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import {
|
||||
BuilderTarget,
|
||||
Builders,
|
||||
ProjectType,
|
||||
WorkspaceSchema,
|
||||
} from '../../utility/workspace-models';
|
||||
|
||||
function getServerTarget(tree: UnitTestTree): BuilderTarget<Builders.Server, JsonObject> {
|
||||
const target = (tree.readJson('/angular.json') as unknown as WorkspaceSchema).projects.app
|
||||
.architect?.server;
|
||||
|
||||
return target as unknown as BuilderTarget<Builders.Server, JsonObject>;
|
||||
}
|
||||
|
||||
function createWorkSpaceConfig(tree: UnitTestTree) {
|
||||
const angularConfig: WorkspaceSchema = {
|
||||
version: 1,
|
||||
projects: {
|
||||
app: {
|
||||
root: '',
|
||||
sourceRoot: 'src',
|
||||
projectType: ProjectType.Application,
|
||||
prefix: 'app',
|
||||
architect: {
|
||||
server: {
|
||||
builder: Builders.Server,
|
||||
options: {
|
||||
main: './server.ts',
|
||||
bundleDependencies: false,
|
||||
sourceMaps: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any,
|
||||
configurations: {
|
||||
one: {
|
||||
aot: true,
|
||||
},
|
||||
two: {
|
||||
bundleDependencies: true,
|
||||
aot: true,
|
||||
},
|
||||
development: {
|
||||
bundleDependencies: true,
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
|
||||
}
|
||||
|
||||
const schematicName = 'update-workspace-config';
|
||||
|
||||
describe(`Migration to update 'angular.json'. ${schematicName}`, () => {
|
||||
const schematicRunner = new SchematicTestRunner(
|
||||
'migrations',
|
||||
require.resolve('../migration-collection.json'),
|
||||
);
|
||||
|
||||
let tree: UnitTestTree;
|
||||
beforeEach(() => {
|
||||
tree = new UnitTestTree(new EmptyTree());
|
||||
createWorkSpaceConfig(tree);
|
||||
});
|
||||
|
||||
it(`should remove 'bundleDependencies'`, async () => {
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
const { options, configurations } = getServerTarget(newTree);
|
||||
|
||||
expect(options.bundleDependencies).toBeUndefined();
|
||||
expect(configurations).toBeDefined();
|
||||
expect(configurations?.one.bundleDependencies).toBeUndefined();
|
||||
expect(configurations?.two.bundleDependencies).toBeUndefined();
|
||||
});
|
||||
|
||||
it(`should add 'vendorChunk: true' to development configuration`, async () => {
|
||||
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||
const { options, configurations } = getServerTarget(newTree);
|
||||
|
||||
expect(options.bundleDependencies).toBeUndefined();
|
||||
expect(configurations).toBeDefined();
|
||||
expect(configurations?.development.vendorChunk).toBeTrue();
|
||||
expect(configurations?.one.vendorChunk).toBeUndefined();
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user