Skip to content

build: ensure that there are no invalid dynamic imports #14619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
19 changes: 17 additions & 2 deletions tools/release/release-output/check-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ import {bold, red, yellow} from 'chalk';
import {existsSync} from 'fs';
import {sync as glob} from 'glob';
import {join} from 'path';
import {checkMaterialPackage, checkReleaseBundle} from './output-validations';
import {
checkMaterialPackage,
checkReleaseBundle,
checkTypeDefinitionFile
} from './output-validations';

/** Glob that matches all JavaScript bundle files within a release package. */
const releaseBundlesGlob = '+(esm5|esm2015|bundles)/*.js';

/** Glob that matches all TypeScript definition files within a release package. */
const releaseTypeDefinitionsGlob = '**/*.d.ts';

/**
* Type that describes a map of package failures. The keys are failure messages and
* their value is an array of specifically affected files.
Expand All @@ -21,19 +28,27 @@ type PackageFailures = Map<string, string[]>;
*/
export function checkReleasePackage(releasesPath: string, packageName: string): boolean {
const packagePath = join(releasesPath, packageName);
const bundlePaths = glob(releaseBundlesGlob, {cwd: packagePath, absolute: true});
const failures = new Map() as PackageFailures;
const addFailure = (message, filePath?) => {
failures.set(message, (failures.get(message) || []).concat(filePath));
};

const bundlePaths = glob(releaseBundlesGlob, {cwd: packagePath, absolute: true});
const typeDefinitions = glob(releaseTypeDefinitionsGlob, {cwd: packagePath, absolute: true});

// We want to walk through each bundle within the current package and run
// release validations that ensure that the bundles are not invalid.
bundlePaths.forEach(bundlePath => {
checkReleaseBundle(bundlePath)
.forEach(message => addFailure(message, bundlePath));
});

// Run output validations for all TypeScript definition files within the release output.
typeDefinitions.forEach(filePath => {
checkTypeDefinitionFile(filePath)
.forEach(message => addFailure(message, filePath));
});

// Special release validation checks for the "material" release package.
if (packageName === 'material') {
checkMaterialPackage(join(releasesPath, packageName))
Expand Down
45 changes: 43 additions & 2 deletions tools/release/release-output/output-validations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {existsSync, readFileSync} from 'fs';
import {sync as glob} from 'glob';
import {join} from 'path';
import {dirname, isAbsolute, join} from 'path';
import * as ts from 'typescript';

/** RegExp that matches Angular component inline styles that contain a sourcemap reference. */
const inlineStylesSourcemapRegex = /styles: ?\[["'].*sourceMappingURL=.*["']/;
Expand All @@ -14,7 +15,7 @@ const externalReferencesRegex = /(templateUrl|styleUrls): *["'[]/;
*/
export function checkReleaseBundle(bundlePath: string): string[] {
const bundleContent = readFileSync(bundlePath, 'utf8');
let failures: string[] = [];
const failures: string[] = [];

if (inlineStylesSourcemapRegex.exec(bundleContent) !== null) {
failures.push('Found sourcemap references in component styles.');
Expand All @@ -27,6 +28,46 @@ export function checkReleaseBundle(bundlePath: string): string[] {
return failures;
}

/**
* Checks the specified TypeScript definition file by ensuring it does not contain invalid
* dynamic import statements. There can be invalid type imports paths because we compose the
* release package by moving things in a desired output structure. See Angular package format
* specification and https://github.com/angular/material2/pull/12876
*/
export function checkTypeDefinitionFile(filePath: string): string[] {
const baseDir = dirname(filePath);
const fileContent = readFileSync(filePath, 'utf8');
const failures = [];

const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true);
const nodeQueue = [...sourceFile.getChildren()];

while (nodeQueue.length) {
const node = nodeQueue.shift()!;

// Check all dynamic type imports and ensure that the import path is valid within the release
// output. Note that we don't want to enforce that there are no dynamic type imports because
// type inference is heavily used within the schematics and is useful in some situations.
if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument) &&
ts.isStringLiteral(node.argument.literal)) {
const importPath = node.argument.literal.text;

// In case the type import path starts with a dot, we know that this is a relative path
// and can ensure that the target path exists. Note that we cannot completely rely on
// "isAbsolute" because dynamic imports can also import from modules (e.g. "my-npm-module")
if (importPath.startsWith('.') && !existsSync(join(baseDir, `${importPath}.d.ts`))) {
failures.push('Found relative type imports which do not exist.');
} else if (isAbsolute(importPath)) {
failures.push('Found absolute type imports in definition file.');
}
}

nodeQueue.push(...node.getChildren());
}

return failures;
}

/**
* Checks the Angular Material release package and ensures that prebuilt themes
* and the theming bundle are built properly.
Expand Down