Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
"factory": "./update-module-resolution/migration",
"description": "Update 'moduleResolution' to 'bundler' in TypeScript configurations. You can read more about this, here: https://www.typescriptlang.org/tsconfig/#moduleResolution"
},
"previous-style-guide": {
"version": "20.0.0",
"factory": "./previous-style-guide/migration",
"description": "Update workspace generation defaults to maintain previous style guide behavior."
},
"use-application-builder": {
"version": "20.0.0",
"factory": "./use-application-builder/migration",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @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.dev/license
*/

import type { Rule } from '@angular-devkit/schematics';
import { updateWorkspace } from '../../utility/workspace';

const TYPE_SCHEMATICS = ['component', 'directive', 'service'] as const;

const SEPARATOR_SCHEMATICS = ['guard', 'interceptor', 'module', 'pipe', 'resolver'] as const;

export default function (): Rule {
return updateWorkspace((workspace) => {
let schematicsDefaults = workspace.extensions['schematics'];

// Ensure "schematics" field is an object
if (
!schematicsDefaults ||
typeof schematicsDefaults !== 'object' ||
Array.isArray(schematicsDefaults)
) {
schematicsDefaults = workspace.extensions['schematics'] = {};
}

// Add "type" value for each schematic to continue generating a type suffix.
// New default is an empty type value.
for (const schematicName of TYPE_SCHEMATICS) {
const schematic = (schematicsDefaults[`@schematics/angular:${schematicName}`] ??= {});
if (typeof schematic === 'object' && !Array.isArray(schematic) && !('type' in schematic)) {
schematic['type'] = schematicName;
}
}

// Add "typeSeparator" value for each schematic to continue generating "." before type.
// New default is an "-" type value.
for (const schematicName of SEPARATOR_SCHEMATICS) {
const schematic = (schematicsDefaults[`@schematics/angular:${schematicName}`] ??= {});
if (
typeof schematic === 'object' &&
!Array.isArray(schematic) &&
!('typeSeparator' in schematic)
) {
schematic['typeSeparator'] = '.';
}
}
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* @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.dev/license
*/

import { EmptyTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { ProjectType, WorkspaceSchema } from '../../utility/workspace-models';

function createWorkSpaceConfig(tree: UnitTestTree, initialSchematicsValue?: unknown) {
const angularConfig: WorkspaceSchema = {
version: 1,
projects: {
app: {
root: '/project/lib',
sourceRoot: '/project/app/src',
projectType: ProjectType.Application,
prefix: 'app',
architect: {},
},
},
};

if (initialSchematicsValue !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(angularConfig as any).schematics = initialSchematicsValue;
}

tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
}

describe(`Migration to update 'angular.json'.`, () => {
const schematicName = 'previous-style-guide';
const schematicRunner = new SchematicTestRunner(
'migrations',
require.resolve('../migration-collection.json'),
);

let tree: UnitTestTree;
beforeEach(() => {
tree = new UnitTestTree(new EmptyTree());
});

it(`should add defaults if no "schematics" workspace field is present`, async () => {
createWorkSpaceConfig(tree);

const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const { schematics } = JSON.parse(newTree.readContent('/angular.json'));

expect(schematics).toEqual({
'@schematics/angular:component': { type: 'component' },
'@schematics/angular:directive': { type: 'directive' },
'@schematics/angular:service': { type: 'service' },
'@schematics/angular:guard': { typeSeparator: '.' },
'@schematics/angular:interceptor': { typeSeparator: '.' },
'@schematics/angular:module': { typeSeparator: '.' },
'@schematics/angular:pipe': { typeSeparator: '.' },
'@schematics/angular:resolver': { typeSeparator: '.' },
});
});

it(`should add defaults if empty "schematics" workspace field is present`, async () => {
createWorkSpaceConfig(tree, {});

const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const { schematics } = JSON.parse(newTree.readContent('/angular.json'));

expect(schematics).toEqual({
'@schematics/angular:component': { type: 'component' },
'@schematics/angular:directive': { type: 'directive' },
'@schematics/angular:service': { type: 'service' },
'@schematics/angular:guard': { typeSeparator: '.' },
'@schematics/angular:interceptor': { typeSeparator: '.' },
'@schematics/angular:module': { typeSeparator: '.' },
'@schematics/angular:pipe': { typeSeparator: '.' },
'@schematics/angular:resolver': { typeSeparator: '.' },
});
});

it(`should add defaults if invalid "schematics" workspace field is present`, async () => {
createWorkSpaceConfig(tree, 10);

const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const { schematics } = JSON.parse(newTree.readContent('/angular.json'));

expect(schematics).toEqual({
'@schematics/angular:component': { type: 'component' },
'@schematics/angular:directive': { type: 'directive' },
'@schematics/angular:service': { type: 'service' },
'@schematics/angular:guard': { typeSeparator: '.' },
'@schematics/angular:interceptor': { typeSeparator: '.' },
'@schematics/angular:module': { typeSeparator: '.' },
'@schematics/angular:pipe': { typeSeparator: '.' },
'@schematics/angular:resolver': { typeSeparator: '.' },
});
});

it(`should add defaults if existing unrelated "schematics" workspace defaults are present`, async () => {
createWorkSpaceConfig(tree, {
'@schematics/angular:component': { style: 'scss' },
});

const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const { schematics } = JSON.parse(newTree.readContent('/angular.json'));

expect(schematics).toEqual({
'@schematics/angular:component': { style: 'scss', type: 'component' },
'@schematics/angular:directive': { type: 'directive' },
'@schematics/angular:service': { type: 'service' },
'@schematics/angular:guard': { typeSeparator: '.' },
'@schematics/angular:interceptor': { typeSeparator: '.' },
'@schematics/angular:module': { typeSeparator: '.' },
'@schematics/angular:pipe': { typeSeparator: '.' },
'@schematics/angular:resolver': { typeSeparator: '.' },
});
});

it(`should not overwrite defaults if existing "schematics" workspace defaults are present`, async () => {
createWorkSpaceConfig(tree, {
'@schematics/angular:component': { type: 'example' },
'@schematics/angular:guard': { typeSeparator: '-' },
});

const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const { schematics } = JSON.parse(newTree.readContent('/angular.json'));

expect(schematics).toEqual({
'@schematics/angular:component': { type: 'example' },
'@schematics/angular:directive': { type: 'directive' },
'@schematics/angular:service': { type: 'service' },
'@schematics/angular:guard': { typeSeparator: '-' },
'@schematics/angular:interceptor': { typeSeparator: '.' },
'@schematics/angular:module': { typeSeparator: '.' },
'@schematics/angular:pipe': { typeSeparator: '.' },
'@schematics/angular:resolver': { typeSeparator: '.' },
});
});
});