Skip to content

Commit d88d4d6

Browse files
fix(schematics): require only exported devkit paths from the shipped schematics
@angular-devkit/schematics 22.3 adds an exports map that allows ./tasks but not ./tasks/index.js. The shipped setup, deploy builder and v21 migration bundles required tasks/index.js, so `ng update` crashed after installing whenever it ran a 22.3 CLI, and the v21 migration never ran. The source keeps the index.js spelling, which the ESM spec run needs while the repo's devkit 21 has no exports map. An esbuild alias rewrites it in the bundles, and the build now fails when a bundle requires a package subpath that is not in exportedSubpaths, naming the importers. typescript stays external so the v21 migration uses the compiler from the user's workspace at ng update time, rather than adding about 3.5 MB to the package.
1 parent bc3fdad commit d88d4d6

1 file changed

Lines changed: 31 additions & 13 deletions

File tree

‎tools/build.ts‎

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,24 @@ const schematicEntryPoints = [
384384
['update', 'v21', 'index'],
385385
];
386386

387+
const externalPackages = [
388+
"@angular-devkit/schematics",
389+
"@angular-devkit/architect",
390+
"@angular-devkit/core",
391+
"rxjs",
392+
"@schematics/angular",
393+
"jsonc-parser",
394+
"firebase-tools",
395+
"typescript"
396+
];
397+
398+
/* Allowed package subpaths for bundles. Each must be in that package's `exports` map, or the
399+
* schematics cannot load with any version of the package that has one. */
400+
const exportedSubpaths = ['@angular-devkit/schematics/tasks', '@schematics/angular/utility', 'rxjs/operators'];
401+
const isPackageSubpath = (path: string) => externalPackages.some(name => path.startsWith(`${name}/`));
402+
387403
async function compileSchematics() {
388-
await esbuild.build({
404+
const { metafile } = await esbuild.build({
389405
entryPoints: schematicEntryPoints.map(segments => `${src('schematics', ...segments)}.ts`),
390406
format: "cjs",
391407
// turns out schematics don't support ESM, need to use webpack or shim these
@@ -396,20 +412,22 @@ async function compileSchematics() {
396412
minify: true,
397413
platform: "node",
398414
target: "es2016",
399-
external: [
400-
"@angular-devkit/schematics",
401-
"@angular-devkit/architect",
402-
"@angular-devkit/core",
403-
"rxjs",
404-
"@schematics/angular",
405-
"jsonc-parser",
406-
"firebase-tools",
407-
// The v21 migration parses user source with the TypeScript compiler; resolve it from
408-
// the workspace at ng-update time instead of bundling ~3.5MB into the package.
409-
"typescript"
410-
],
415+
external: externalPackages,
416+
alias: { "@angular-devkit/schematics/tasks/index.js": "@angular-devkit/schematics/tasks" },
411417
outdir: dest('schematics'),
418+
metafile: true,
412419
});
420+
const importersBySubpath = new Map<string, string[]>();
421+
for (const [importer, { imports }] of Object.entries(metafile.inputs)) {
422+
for (const { path } of imports.filter(entry => entry.external && isPackageSubpath(entry.path))) {
423+
importersBySubpath.set(path, [...(importersBySubpath.get(path) ?? []), importer]);
424+
}
425+
}
426+
const unlistedSubpaths = [...importersBySubpath.keys()].filter(subpath => !exportedSubpaths.includes(subpath));
427+
if (unlistedSubpaths.length) {
428+
const required = unlistedSubpaths.map(subpath => `\n ${subpath}, imported by ${importersBySubpath.get(subpath)?.join(', ')}`);
429+
throw new Error(`The schematics require package paths that exportedSubpaths does not list:${required.join('')}\nAdd an alias in compileSchematics to a path the package exports, or, if the exports map in the newest published version lists the path (npm view <package>@next exports), add it to exportedSubpaths.`);
430+
}
413431
await Promise.all([
414432
copy(src('schematics', 'versions.json'), dest('schematics', 'versions.json')),
415433
copy(src('schematics', 'builders.json'), dest('schematics', 'builders.json')),

0 commit comments

Comments
 (0)