Skip to content

Commit dd2b659

Browse files
alan-agius4angular-robot[bot]
authored andcommittedJan 3, 2023
feat(@schematics/angular): add configuration files generation schematic
This commits add a schematic to generate Karma and Browserlist files which since version 15 are no longer generated by default. This schematic should be used to generate these files when further customisation is needed. Usage ``` ng generate config karma ng generate config browserlist ``` Closes #24294
1 parent 7c87ce4 commit dd2b659

File tree

9 files changed

+279
-2
lines changed

9 files changed

+279
-2
lines changed
 

‎packages/angular_devkit/build_angular/src/builders/karma/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export function execute(
9696

9797
const karmaOptions: KarmaConfigOptions = options.karmaConfig
9898
? {}
99-
: getBuiltInKarmaConfig(karma, context.workspaceRoot, projectName);
99+
: getBuiltInKarmaConfig(context.workspaceRoot, projectName);
100100

101101
karmaOptions.singleRun = singleRun;
102102

@@ -186,7 +186,6 @@ export function execute(
186186
}
187187

188188
function getBuiltInKarmaConfig(
189-
karma: typeof import('karma'),
190189
workspaceRoot: string,
191190
projectName: string,
192191
): ConfigOptions & Record<string, unknown> {
@@ -197,6 +196,7 @@ function getBuiltInKarmaConfig(
197196

198197
const workspaceRootRequire = createRequire(workspaceRoot + '/');
199198

199+
// Any changes to the config here need to be synced to: packages/schematics/angular/config/files/karma.conf.js.template
200200
return {
201201
basePath: '',
202202
frameworks: ['jasmine', '@angular-devkit/build-angular'],

‎packages/schematics/angular/collection.json

+5
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@
120120
"factory": "./environments",
121121
"schema": "./environments/schema.json",
122122
"description": "Generate project environment files."
123+
},
124+
"config": {
125+
"factory": "./config",
126+
"schema": "./config/schema.json",
127+
"description": "Generates a configuration file."
123128
}
124129
}
125130
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2+
# For additional information regarding the format and rule options, please see:
3+
# https://github.com/browserslist/browserslist#queries
4+
5+
# For the full list of supported browsers by the Angular framework, please see:
6+
# https://angular.io/guide/browser-support
7+
8+
# You can see what browsers were selected by your queries by running:
9+
# npx browserslist
10+
11+
last 1 Chrome version
12+
last 1 Firefox version
13+
last 2 Edge major versions
14+
last 2 Safari major versions
15+
last 2 iOS major versions
16+
Firefox ESR
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Karma configuration file, see link for more information
2+
// https://karma-runner.github.io/1.0/config/configuration-file.html
3+
4+
module.exports = function (config) {
5+
config.set({
6+
basePath: '',
7+
frameworks: ['jasmine', '@angular-devkit/build-angular'],
8+
plugins: [
9+
require('karma-jasmine'),
10+
require('karma-chrome-launcher'),
11+
require('karma-jasmine-html-reporter'),
12+
require('karma-coverage'),
13+
require('@angular-devkit/build-angular/plugins/karma')
14+
],
15+
client: {
16+
jasmine: {
17+
// you can add configuration options for Jasmine here
18+
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
19+
// for example, you can disable the random execution with `random: false`
20+
// or set a specific seed with `seed: 4321`
21+
},
22+
clearContext: false // leave Jasmine Spec Runner output visible in browser
23+
},
24+
jasmineHtmlReporter: {
25+
suppressAll: true // removes the duplicated traces
26+
},
27+
coverageReporter: {
28+
dir: require('path').join(__dirname, '<%= relativePathToWorkspaceRoot %>/coverage/<%= folderName %>'),
29+
subdir: '.',
30+
reporters: [
31+
{ type: 'html' },
32+
{ type: 'text-summary' }
33+
]
34+
},
35+
reporters: ['progress', 'kjhtml'],
36+
browsers: ['Chrome'],
37+
restartOnFileChange: true
38+
});
39+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {
10+
Rule,
11+
SchematicsException,
12+
apply,
13+
applyTemplates,
14+
filter,
15+
mergeWith,
16+
move,
17+
strings,
18+
url,
19+
} from '@angular-devkit/schematics';
20+
import { AngularBuilder, readWorkspace, updateWorkspace } from '@schematics/angular/utility';
21+
import { posix as path } from 'path';
22+
import { relativePathToWorkspaceRoot } from '../utility/paths';
23+
import { Schema as ConfigOptions, Type as ConfigType } from './schema';
24+
25+
export default function (options: ConfigOptions): Rule {
26+
switch (options.type) {
27+
case ConfigType.Karma:
28+
return addKarmaConfig(options);
29+
case ConfigType.Browserslist:
30+
return addBrowserslistConfig(options);
31+
default:
32+
throw new SchematicsException(`"${options.type}" is an unknown configuration file type.`);
33+
}
34+
}
35+
36+
function addBrowserslistConfig(options: ConfigOptions): Rule {
37+
return async (host) => {
38+
const workspace = await readWorkspace(host);
39+
const project = workspace.projects.get(options.project);
40+
if (!project) {
41+
throw new SchematicsException(`Project name "${options.project}" doesn't not exist.`);
42+
}
43+
44+
return mergeWith(
45+
apply(url('./files'), [
46+
filter((p) => p.endsWith('.browserslistrc.template')),
47+
applyTemplates({}),
48+
move(project.root),
49+
]),
50+
);
51+
};
52+
}
53+
54+
function addKarmaConfig(options: ConfigOptions): Rule {
55+
return updateWorkspace((workspace) => {
56+
const project = workspace.projects.get(options.project);
57+
if (!project) {
58+
throw new SchematicsException(`Project name "${options.project}" doesn't not exist.`);
59+
}
60+
61+
const testTarget = project.targets.get('test');
62+
if (!testTarget) {
63+
throw new SchematicsException(
64+
`No "test" target found for project "${options.project}".` +
65+
' A "test" target is required to generate a karma configuration.',
66+
);
67+
}
68+
69+
if (testTarget.builder !== AngularBuilder.Karma) {
70+
throw new SchematicsException(
71+
`Cannot add a karma configuration as builder for "test" target in project does not use "${AngularBuilder.Karma}".`,
72+
);
73+
}
74+
75+
testTarget.options ??= {};
76+
testTarget.options.karmaConfig = path.join(project.root, 'karma.conf.js');
77+
78+
// If scoped project (i.e. "@foo/bar"), convert dir to "foo/bar".
79+
let folderName = options.project.startsWith('@') ? options.project.slice(1) : options.project;
80+
if (/[A-Z]/.test(folderName)) {
81+
folderName = strings.dasherize(folderName);
82+
}
83+
84+
return mergeWith(
85+
apply(url('./files'), [
86+
filter((p) => p.endsWith('karma.conf.js.template')),
87+
applyTemplates({
88+
relativePathToWorkspaceRoot: relativePathToWorkspaceRoot(project.root),
89+
folderName,
90+
}),
91+
move(project.root),
92+
]),
93+
);
94+
});
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
10+
import { Schema as ApplicationOptions } from '../application/schema';
11+
import { Schema as WorkspaceOptions } from '../workspace/schema';
12+
import { Schema as ConfigOptions, Type as ConfigType } from './schema';
13+
14+
describe('Application Schematic', () => {
15+
const schematicRunner = new SchematicTestRunner(
16+
'@schematics/angular',
17+
require.resolve('../collection.json'),
18+
);
19+
20+
const workspaceOptions: WorkspaceOptions = {
21+
name: 'workspace',
22+
newProjectRoot: 'projects',
23+
version: '15.0.0',
24+
};
25+
26+
const defaultAppOptions: ApplicationOptions = {
27+
name: 'foo',
28+
inlineStyle: true,
29+
inlineTemplate: true,
30+
routing: false,
31+
skipPackageJson: false,
32+
};
33+
34+
let applicationTree: UnitTestTree;
35+
function runConfigSchematic(type: ConfigType): Promise<UnitTestTree> {
36+
return schematicRunner.runSchematic<ConfigOptions>(
37+
'config',
38+
{
39+
project: 'foo',
40+
type,
41+
},
42+
applicationTree,
43+
);
44+
}
45+
46+
beforeEach(async () => {
47+
const workspaceTree = await schematicRunner.runSchematic('workspace', workspaceOptions);
48+
applicationTree = await schematicRunner.runSchematic(
49+
'application',
50+
defaultAppOptions,
51+
workspaceTree,
52+
);
53+
});
54+
55+
describe(`when 'type' is 'karma'`, () => {
56+
it('should create a karma.conf.js file', async () => {
57+
const tree = await runConfigSchematic(ConfigType.Karma);
58+
expect(tree.exists('projects/foo/karma.conf.js')).toBeTrue();
59+
});
60+
61+
it('should set the right coverage folder', async () => {
62+
const tree = await runConfigSchematic(ConfigType.Karma);
63+
const karmaConf = tree.readText('projects/foo/karma.conf.js');
64+
expect(karmaConf).toContain(`dir: require('path').join(__dirname, '../../coverage/foo')`);
65+
});
66+
67+
it(`should set 'karmaConfig' in test builder`, async () => {
68+
const tree = await runConfigSchematic(ConfigType.Karma);
69+
const config = JSON.parse(tree.readContent('/angular.json'));
70+
const prj = config.projects.foo;
71+
const { karmaConfig } = prj.architect.test.options;
72+
expect(karmaConfig).toBe('projects/foo/karma.conf.js');
73+
});
74+
});
75+
76+
describe(`when 'type' is 'browserslist'`, () => {
77+
it('should create a .browserslistrc file', async () => {
78+
const tree = await runConfigSchematic(ConfigType.Browserslist);
79+
expect(tree.exists('projects/foo/.browserslistrc')).toBeTrue();
80+
});
81+
});
82+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"$id": "SchematicsAngularConfig",
4+
"title": "Angular Config File Options Schema",
5+
"type": "object",
6+
"additionalProperties": false,
7+
"description": "Generates a configuration file in the given project.",
8+
"properties": {
9+
"project": {
10+
"type": "string",
11+
"description": "The name of the project.",
12+
"$default": {
13+
"$source": "projectName"
14+
}
15+
},
16+
"type": {
17+
"type": "string",
18+
"description": "Specifies which type of configuration file to create.",
19+
"enum": ["karma", "browserslist"],
20+
"x-prompt": "Which type of configuration file would you like to create?",
21+
"$default": {
22+
"$source": "argv",
23+
"index": 0
24+
}
25+
}
26+
},
27+
"required": ["project", "type"]
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ng } from '../../../utils/process';
2+
3+
export default async function () {
4+
await ng('generate', 'config', 'browserslist');
5+
await ng('build');
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ng } from '../../../utils/process';
2+
3+
export default async function () {
4+
await ng('generate', 'config', 'karma');
5+
await ng('test', '--watch=false');
6+
}

0 commit comments

Comments
 (0)
Please sign in to comment.