@schematics/angular 17.2.3 → 17.3.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
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
+ * Scans a CSS or Sass file and locates all valid import/use directive values as defined by the
10
+ * syntax specification.
11
+ * @param contents A string containing a CSS or Sass file to scan.
12
+ * @returns An iterable that yields each CSS directive value found.
13
+ */
14
+ export declare function findImports(contents: string, sass: boolean): Iterable<{
15
+ start: number;
16
+ end: number;
17
+ specifier: string;
18
+ fromUse?: boolean;
19
+ }>;
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.io/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.findImports = void 0;
11
+ /**
12
+ * Determines if a unicode code point is a CSS whitespace character.
13
+ * @param code The unicode code point to test.
14
+ * @returns true, if the code point is CSS whitespace; false, otherwise.
15
+ */
16
+ function isWhitespace(code) {
17
+ // Based on https://www.w3.org/TR/css-syntax-3/#whitespace
18
+ switch (code) {
19
+ case 0x0009: // tab
20
+ case 0x0020: // space
21
+ case 0x000a: // line feed
22
+ case 0x000c: // form feed
23
+ case 0x000d: // carriage return
24
+ return true;
25
+ default:
26
+ return false;
27
+ }
28
+ }
29
+ /**
30
+ * Scans a CSS or Sass file and locates all valid import/use directive values as defined by the
31
+ * syntax specification.
32
+ * @param contents A string containing a CSS or Sass file to scan.
33
+ * @returns An iterable that yields each CSS directive value found.
34
+ */
35
+ function* findImports(contents, sass) {
36
+ yield* find(contents, '@import ');
37
+ if (sass) {
38
+ for (const result of find(contents, '@use ')) {
39
+ yield { ...result, fromUse: true };
40
+ }
41
+ }
42
+ }
43
+ exports.findImports = findImports;
44
+ /**
45
+ * Scans a CSS or Sass file and locates all valid function/directive values as defined by the
46
+ * syntax specification.
47
+ * @param contents A string containing a CSS or Sass file to scan.
48
+ * @param prefix The prefix to start a valid segment.
49
+ * @returns An iterable that yields each CSS url function value found.
50
+ */
51
+ function* find(contents, prefix) {
52
+ let pos = 0;
53
+ let width = 1;
54
+ let current = -1;
55
+ const next = () => {
56
+ pos += width;
57
+ current = contents.codePointAt(pos) ?? -1;
58
+ width = current > 0xffff ? 2 : 1;
59
+ return current;
60
+ };
61
+ // Based on https://www.w3.org/TR/css-syntax-3/#consume-ident-like-token
62
+ while ((pos = contents.indexOf(prefix, pos)) !== -1) {
63
+ // Set to position of the last character in prefix
64
+ pos += prefix.length - 1;
65
+ width = 1;
66
+ // Consume all leading whitespace
67
+ while (isWhitespace(next())) {
68
+ /* empty */
69
+ }
70
+ // Initialize URL state
71
+ const url = { start: pos, end: -1, specifier: '' };
72
+ let complete = false;
73
+ // If " or ', then consume the value as a string
74
+ if (current === 0x0022 || current === 0x0027) {
75
+ const ending = current;
76
+ // Based on https://www.w3.org/TR/css-syntax-3/#consume-string-token
77
+ while (!complete) {
78
+ switch (next()) {
79
+ case -1: // EOF
80
+ return;
81
+ case 0x000a: // line feed
82
+ case 0x000c: // form feed
83
+ case 0x000d: // carriage return
84
+ // Invalid
85
+ complete = true;
86
+ break;
87
+ case 0x005c: // \ -- character escape
88
+ // If not EOF or newline, add the character after the escape
89
+ switch (next()) {
90
+ case -1:
91
+ return;
92
+ case 0x000a: // line feed
93
+ case 0x000c: // form feed
94
+ case 0x000d: // carriage return
95
+ // Skip when inside a string
96
+ break;
97
+ default:
98
+ // TODO: Handle hex escape codes
99
+ url.specifier += String.fromCodePoint(current);
100
+ break;
101
+ }
102
+ break;
103
+ case ending:
104
+ // Full string position should include the quotes for replacement
105
+ url.end = pos + 1;
106
+ complete = true;
107
+ yield url;
108
+ break;
109
+ default:
110
+ url.specifier += String.fromCodePoint(current);
111
+ break;
112
+ }
113
+ }
114
+ next();
115
+ continue;
116
+ }
117
+ }
118
+ }
@@ -6,4 +6,7 @@
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
8
  import { Rule } from '@angular-devkit/schematics';
9
+ /**
10
+ * Migration main entrypoint
11
+ */
9
12
  export default function (): Rule;
@@ -7,16 +7,109 @@
7
7
  * found in the LICENSE file at https://angular.io/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- const core_1 = require("@angular-devkit/core");
11
10
  const schematics_1 = require("@angular-devkit/schematics");
12
11
  const posix_1 = require("node:path/posix");
13
12
  const json_file_1 = require("../../utility/json-file");
14
13
  const workspace_1 = require("../../utility/workspace");
15
14
  const workspace_models_1 = require("../../utility/workspace-models");
16
- function default_1() {
17
- return async (tree, context) => {
15
+ const css_import_lexer_1 = require("./css-import-lexer");
16
+ function* updateBuildTarget(projectName, buildTarget, serverTarget, tree, context) {
17
+ // Update builder target and options
18
+ buildTarget.builder = workspace_models_1.Builders.Application;
19
+ for (const [, options] of (0, workspace_1.allTargetOptions)(buildTarget, false)) {
20
+ // Show warnings for using no longer supported options
21
+ if (usesNoLongerSupportedOptions(options, context, projectName)) {
22
+ continue;
23
+ }
24
+ if (options['index'] === '') {
25
+ options['index'] = false;
26
+ }
27
+ // Rename and transform options
28
+ options['browser'] = options['main'];
29
+ if (serverTarget && typeof options['browser'] === 'string') {
30
+ options['server'] = (0, posix_1.dirname)(options['browser']) + '/main.server.ts';
31
+ }
32
+ options['serviceWorker'] = options['ngswConfigPath'] ?? options['serviceWorker'];
33
+ if (typeof options['polyfills'] === 'string') {
34
+ options['polyfills'] = [options['polyfills']];
35
+ }
36
+ let outputPath = options['outputPath'];
37
+ if (typeof outputPath === 'string') {
38
+ if (!/\/browser\/?$/.test(outputPath)) {
39
+ // TODO: add prompt.
40
+ context.logger.warn(`The output location of the browser build has been updated from "${outputPath}" to ` +
41
+ `"${(0, posix_1.join)(outputPath, 'browser')}". ` +
42
+ 'You might need to adjust your deployment pipeline or, as an alternative, ' +
43
+ 'set outputPath.browser to "" in order to maintain the previous functionality.');
44
+ }
45
+ else {
46
+ outputPath = outputPath.replace(/\/browser\/?$/, '');
47
+ }
48
+ options['outputPath'] = {
49
+ base: outputPath,
50
+ };
51
+ if (typeof options['resourcesOutputPath'] === 'string') {
52
+ const media = options['resourcesOutputPath'].replaceAll('/', '');
53
+ if (media && media !== 'media') {
54
+ options['outputPath'] = {
55
+ base: outputPath,
56
+ media,
57
+ };
58
+ }
59
+ }
60
+ }
61
+ // Delete removed options
62
+ delete options['deployUrl'];
63
+ delete options['vendorChunk'];
64
+ delete options['commonChunk'];
65
+ delete options['resourcesOutputPath'];
66
+ delete options['buildOptimizer'];
67
+ delete options['main'];
68
+ delete options['ngswConfigPath'];
69
+ }
70
+ // Merge browser and server tsconfig
71
+ if (serverTarget) {
72
+ const browserTsConfig = buildTarget.options?.tsConfig;
73
+ const serverTsConfig = serverTarget.options?.tsConfig;
74
+ if (typeof browserTsConfig !== 'string') {
75
+ throw new schematics_1.SchematicsException(`Cannot update project "${projectName}" to use the application builder` +
76
+ ` as the browser tsconfig cannot be located.`);
77
+ }
78
+ if (typeof serverTsConfig !== 'string') {
79
+ throw new schematics_1.SchematicsException(`Cannot update project "${projectName}" to use the application builder` +
80
+ ` as the server tsconfig cannot be located.`);
81
+ }
82
+ const browserJson = new json_file_1.JSONFile(tree, browserTsConfig);
83
+ const serverJson = new json_file_1.JSONFile(tree, serverTsConfig);
84
+ const filesPath = ['files'];
85
+ const files = new Set([
86
+ ...(browserJson.get(filesPath) ?? []),
87
+ ...(serverJson.get(filesPath) ?? []),
88
+ ]);
89
+ // Server file will be added later by the means of the ssr schematic.
90
+ files.delete('server.ts');
91
+ browserJson.modify(filesPath, Array.from(files));
92
+ const typesPath = ['compilerOptions', 'types'];
93
+ browserJson.modify(typesPath, Array.from(new Set([
94
+ ...(browserJson.get(typesPath) ?? []),
95
+ ...(serverJson.get(typesPath) ?? []),
96
+ ])));
97
+ // Delete server tsconfig
98
+ yield deleteFile(serverTsConfig);
99
+ }
100
+ // Update server file
101
+ const ssrMainFile = serverTarget?.options?.['main'];
102
+ if (typeof ssrMainFile === 'string') {
103
+ yield deleteFile(ssrMainFile);
104
+ yield (0, schematics_1.externalSchematic)('@schematics/angular', 'ssr', {
105
+ project: projectName,
106
+ skipInstall: true,
107
+ });
108
+ }
109
+ }
110
+ function updateProjects(tree, context) {
111
+ return (0, workspace_1.updateWorkspace)((workspace) => {
18
112
  const rules = [];
19
- const workspace = await (0, workspace_1.getWorkspace)(tree);
20
113
  for (const [name, project] of workspace.projects) {
21
114
  if (project.extensions.projectType !== workspace_models_1.ProjectType.Application) {
22
115
  // Only interested in application projects since these changes only effects application builders
@@ -32,107 +125,8 @@ function default_1() {
32
125
  ` Only "${workspace_models_1.Builders.BrowserEsbuild}" and "${workspace_models_1.Builders.Browser}" can be automatically migrated.`);
33
126
  continue;
34
127
  }
35
- // Update builder target and options
36
- buildTarget.builder = workspace_models_1.Builders.Application;
37
- const hasServerTarget = project.targets.has('server');
38
- for (const [, options] of (0, workspace_1.allTargetOptions)(buildTarget, false)) {
39
- // Show warnings for using no longer supported options
40
- if (usesNoLongerSupportedOptions(options, context, name)) {
41
- continue;
42
- }
43
- if (options['index'] === '') {
44
- options['index'] = false;
45
- }
46
- // Rename and transform options
47
- options['browser'] = options['main'];
48
- if (hasServerTarget && typeof options['browser'] === 'string') {
49
- options['server'] = (0, posix_1.dirname)(options['browser']) + '/main.server.ts';
50
- }
51
- options['serviceWorker'] = options['ngswConfigPath'] ?? options['serviceWorker'];
52
- if (typeof options['polyfills'] === 'string') {
53
- options['polyfills'] = [options['polyfills']];
54
- }
55
- let outputPath = options['outputPath'];
56
- if (typeof outputPath === 'string') {
57
- if (!/\/browser\/?$/.test(outputPath)) {
58
- // TODO: add prompt.
59
- context.logger.warn(`The output location of the browser build has been updated from "${outputPath}" to ` +
60
- `"${(0, posix_1.join)(outputPath, 'browser')}". ` +
61
- 'You might need to adjust your deployment pipeline or, as an alternative, ' +
62
- 'set outputPath.browser to "" in order to maintain the previous functionality.');
63
- }
64
- else {
65
- outputPath = outputPath.replace(/\/browser\/?$/, '');
66
- }
67
- options['outputPath'] = {
68
- base: outputPath,
69
- };
70
- if (typeof options['resourcesOutputPath'] === 'string') {
71
- const media = options['resourcesOutputPath'].replaceAll('/', '');
72
- if (media && media !== 'media') {
73
- options['outputPath'] = {
74
- base: outputPath,
75
- media: media,
76
- };
77
- }
78
- }
79
- }
80
- // Delete removed options
81
- delete options['deployUrl'];
82
- delete options['vendorChunk'];
83
- delete options['commonChunk'];
84
- delete options['resourcesOutputPath'];
85
- delete options['buildOptimizer'];
86
- delete options['main'];
87
- delete options['ngswConfigPath'];
88
- }
89
- // Merge browser and server tsconfig
90
- if (hasServerTarget) {
91
- const browserTsConfig = buildTarget?.options?.tsConfig;
92
- const serverTsConfig = project.targets.get('server')?.options?.tsConfig;
93
- if (typeof browserTsConfig !== 'string') {
94
- throw new schematics_1.SchematicsException(`Cannot update project "${name}" to use the application builder` +
95
- ` as the browser tsconfig cannot be located.`);
96
- }
97
- if (typeof serverTsConfig !== 'string') {
98
- throw new schematics_1.SchematicsException(`Cannot update project "${name}" to use the application builder` +
99
- ` as the server tsconfig cannot be located.`);
100
- }
101
- const browserJson = new json_file_1.JSONFile(tree, browserTsConfig);
102
- const serverJson = new json_file_1.JSONFile(tree, serverTsConfig);
103
- const filesPath = ['files'];
104
- const files = new Set([
105
- ...(browserJson.get(filesPath) ?? []),
106
- ...(serverJson.get(filesPath) ?? []),
107
- ]);
108
- // Server file will be added later by the means of the ssr schematic.
109
- files.delete('server.ts');
110
- browserJson.modify(filesPath, Array.from(files));
111
- const typesPath = ['compilerOptions', 'types'];
112
- browserJson.modify(typesPath, Array.from(new Set([
113
- ...(browserJson.get(typesPath) ?? []),
114
- ...(serverJson.get(typesPath) ?? []),
115
- ])));
116
- // Delete server tsconfig
117
- tree.delete(serverTsConfig);
118
- }
119
- // Update main tsconfig
120
- const rootJson = new json_file_1.JSONFile(tree, 'tsconfig.json');
121
- rootJson.modify(['compilerOptions', 'esModuleInterop'], true);
122
- rootJson.modify(['compilerOptions', 'downlevelIteration'], undefined);
123
- rootJson.modify(['compilerOptions', 'allowSyntheticDefaultImports'], undefined);
124
- // Update server file
125
- const ssrMainFile = project.targets.get('server')?.options?.['main'];
126
- if (typeof ssrMainFile === 'string') {
127
- tree.delete(ssrMainFile);
128
- rules.push((0, schematics_1.externalSchematic)('@schematics/angular', 'ssr', {
129
- project: name,
130
- skipInstall: true,
131
- }));
132
- }
133
- // Delete package.json helper scripts
134
- const pkgJson = new json_file_1.JSONFile(tree, 'package.json');
135
- ['build:ssr', 'dev:ssr', 'serve:ssr', 'prerender'].forEach((s) => pkgJson.remove(['scripts', s]));
128
+ const serverTarget = project.targets.get('server');
129
+ rules.push(...updateBuildTarget(name, buildTarget, serverTarget, tree, context));
136
130
  // Delete all redundant targets
137
131
  for (const [key, target] of project.targets) {
138
132
  switch (target.builder) {
@@ -144,14 +138,131 @@ function default_1() {
144
138
  break;
145
139
  }
146
140
  }
141
+ // Update CSS/Sass import specifiers
142
+ const projectSourceRoot = (0, posix_1.join)(project.root, project.sourceRoot ?? 'src');
143
+ updateStyleImports(tree, projectSourceRoot, buildTarget);
147
144
  }
148
- // Save workspace changes
149
- await core_1.workspaces.writeWorkspace(workspace, new workspace_1.TreeWorkspaceHost(tree));
150
145
  return (0, schematics_1.chain)(rules);
146
+ });
147
+ }
148
+ function* visit(directory) {
149
+ for (const path of directory.subfiles) {
150
+ const sass = path.endsWith('.scss');
151
+ if (path.endsWith('.css') || sass) {
152
+ const entry = directory.file(path);
153
+ if (entry) {
154
+ const content = entry.content;
155
+ yield [entry.path, content.toString(), sass];
156
+ }
157
+ }
158
+ }
159
+ for (const path of directory.subdirs) {
160
+ if (path === 'node_modules' || path.startsWith('.')) {
161
+ continue;
162
+ }
163
+ yield* visit(directory.dir(path));
164
+ }
165
+ }
166
+ // Based on https://github.com/sass/dart-sass/blob/44d6bb6ac72fe6b93f5bfec371a1fffb18e6b76d/lib/src/importer/utils.dart
167
+ function* potentialSassImports(specifier, base, fromImport) {
168
+ const directory = (0, posix_1.join)(base, (0, posix_1.dirname)(specifier));
169
+ const extension = (0, posix_1.extname)(specifier);
170
+ const hasStyleExtension = extension === '.scss' || extension === '.sass' || extension === '.css';
171
+ // Remove the style extension if present to allow adding the `.import` suffix
172
+ const filename = (0, posix_1.basename)(specifier, hasStyleExtension ? extension : undefined);
173
+ if (hasStyleExtension) {
174
+ if (fromImport) {
175
+ yield (0, posix_1.join)(directory, filename + '.import' + extension);
176
+ yield (0, posix_1.join)(directory, '_' + filename + '.import' + extension);
177
+ }
178
+ yield (0, posix_1.join)(directory, filename + extension);
179
+ yield (0, posix_1.join)(directory, '_' + filename + extension);
180
+ }
181
+ else {
182
+ if (fromImport) {
183
+ yield (0, posix_1.join)(directory, filename + '.import.scss');
184
+ yield (0, posix_1.join)(directory, filename + '.import.sass');
185
+ yield (0, posix_1.join)(directory, filename + '.import.css');
186
+ yield (0, posix_1.join)(directory, '_' + filename + '.import.scss');
187
+ yield (0, posix_1.join)(directory, '_' + filename + '.import.sass');
188
+ yield (0, posix_1.join)(directory, '_' + filename + '.import.css');
189
+ }
190
+ yield (0, posix_1.join)(directory, filename + '.scss');
191
+ yield (0, posix_1.join)(directory, filename + '.sass');
192
+ yield (0, posix_1.join)(directory, filename + '.css');
193
+ yield (0, posix_1.join)(directory, '_' + filename + '.scss');
194
+ yield (0, posix_1.join)(directory, '_' + filename + '.sass');
195
+ yield (0, posix_1.join)(directory, '_' + filename + '.css');
196
+ }
197
+ }
198
+ function updateStyleImports(tree, projectSourceRoot, buildTarget) {
199
+ const external = new Set();
200
+ let needWorkspaceIncludePath = false;
201
+ for (const file of visit(tree.getDir(projectSourceRoot))) {
202
+ const [path, content, sass] = file;
203
+ const relativeBase = (0, posix_1.dirname)(path);
204
+ let updater;
205
+ for (const { start, specifier, fromUse } of (0, css_import_lexer_1.findImports)(content, sass)) {
206
+ if (specifier[0] === '~') {
207
+ updater ??= tree.beginUpdate(path);
208
+ // start position includes the opening quote
209
+ updater.remove(start + 1, 1);
210
+ }
211
+ else if (specifier[0] === '^') {
212
+ updater ??= tree.beginUpdate(path);
213
+ // start position includes the opening quote
214
+ updater.remove(start + 1, 1);
215
+ // Add to externalDependencies
216
+ external.add(specifier.slice(1));
217
+ }
218
+ else if (sass &&
219
+ [...potentialSassImports(specifier, relativeBase, !fromUse)].every((v) => !tree.exists(v)) &&
220
+ [...potentialSassImports(specifier, '/', !fromUse)].some((v) => tree.exists(v))) {
221
+ needWorkspaceIncludePath = true;
222
+ }
223
+ }
224
+ if (updater) {
225
+ tree.commitUpdate(updater);
226
+ }
227
+ }
228
+ if (needWorkspaceIncludePath) {
229
+ buildTarget.options ??= {};
230
+ buildTarget.options['stylePreprocessorOptions'] ??= {};
231
+ (buildTarget.options['stylePreprocessorOptions']['includePaths'] ??= []).push('.');
232
+ }
233
+ if (external.size > 0) {
234
+ buildTarget.options ??= {};
235
+ (buildTarget.options['externalDependencies'] ??= []).push(...external);
236
+ }
237
+ }
238
+ function deleteFile(path) {
239
+ return (tree) => {
240
+ tree.delete(path);
241
+ };
242
+ }
243
+ function updateJsonFile(path, updater) {
244
+ return (tree) => {
245
+ updater(new json_file_1.JSONFile(tree, path));
151
246
  };
152
247
  }
248
+ /**
249
+ * Migration main entrypoint
250
+ */
251
+ function default_1() {
252
+ return (0, schematics_1.chain)([
253
+ updateProjects,
254
+ // Delete package.json helper scripts
255
+ updateJsonFile('package.json', (pkgJson) => ['build:ssr', 'dev:ssr', 'serve:ssr', 'prerender'].forEach((s) => pkgJson.remove(['scripts', s]))),
256
+ // Update main tsconfig
257
+ updateJsonFile('tsconfig.json', (rootJson) => {
258
+ rootJson.modify(['compilerOptions', 'esModuleInterop'], true);
259
+ rootJson.modify(['compilerOptions', 'downlevelIteration'], undefined);
260
+ rootJson.modify(['compilerOptions', 'allowSyntheticDefaultImports'], undefined);
261
+ }),
262
+ ]);
263
+ }
153
264
  exports.default = default_1;
154
- function usesNoLongerSupportedOptions({ deployUrl, resourcesOutputPath }, context, projectName) {
265
+ function usesNoLongerSupportedOptions({ deployUrl }, context, projectName) {
155
266
  let hasUsage = false;
156
267
  if (typeof deployUrl === 'string') {
157
268
  hasUsage = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematics/angular",
3
- "version": "17.2.3",
3
+ "version": "17.3.0-rc.0",
4
4
  "description": "Schematics specific to Angular",
5
5
  "homepage": "https://github.com/angular/angular-cli",
6
6
  "keywords": [
@@ -23,8 +23,8 @@
23
23
  },
24
24
  "schematics": "./collection.json",
25
25
  "dependencies": {
26
- "@angular-devkit/core": "17.2.3",
27
- "@angular-devkit/schematics": "17.2.3",
26
+ "@angular-devkit/core": "17.3.0-rc.0",
27
+ "@angular-devkit/schematics": "17.3.0-rc.0",
28
28
  "jsonc-parser": "3.2.1"
29
29
  },
30
30
  "repository": {
@@ -42,7 +42,7 @@ export declare function addModuleImportToStandaloneBootstrap(tree: Tree, filePat
42
42
  * @param functionName Name of the function that should be called.
43
43
  * @param importPath Path from which to import the function.
44
44
  * @param args Arguments to use when calling the function.
45
- * @returns The file path that the provider was added to.
45
+ * @return The file path that the provider was added to.
46
46
  * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from
47
47
  * `@schematics/angular/utility` instead.
48
48
  */
@@ -127,7 +127,7 @@ exports.addModuleImportToStandaloneBootstrap = addModuleImportToStandaloneBootst
127
127
  * @param functionName Name of the function that should be called.
128
128
  * @param importPath Path from which to import the function.
129
129
  * @param args Arguments to use when calling the function.
130
- * @returns The file path that the provider was added to.
130
+ * @return The file path that the provider was added to.
131
131
  * @deprecated Private utility that will be removed. Use `addRootImport` or `addRootProvider` from
132
132
  * `@schematics/angular/utility` instead.
133
133
  */