feat(ivy): show error when trying to publish NGCC'd packages (#32031)

Publishing of NGCC packages should not be allowed. It is easy for a user to publish an NGCC'd version of a library they have workspace libraries which are being used in a workspace application.

If a users builds a library and afterwards the application, the library will be transformed with NGCC and since NGCC taints the distributed files that should be published.

With this change we use the npm/yarn `prepublishOnly` hook to display and error and abort the process with a non zero error code when a user tries to publish an NGCC version of the package.

More info: https://docs.npmjs.com/misc/scripts

PR Close #32031
This commit is contained in:
Alan Agius 2019-08-07 08:34:00 +02:00 committed by Alex Rickabaugh
parent f7eebd0227
commit 46304a4f83
3 changed files with 32 additions and 1 deletions

View File

@ -57,6 +57,17 @@ export function markAsProcessed(
processed[prop] = NGCC_VERSION;
}
const scripts = packageJson.scripts || (packageJson.scripts = {});
scripts.prepublishOnly__ivy_ngcc_bak =
scripts.prepublishOnly__ivy_ngcc_bak || scripts.prepublishOnly;
scripts.prepublishOnly = 'node --eval \"console.error(\'' +
'ERROR: Trying to publish a package that has been compiled by NGCC. This is not allowed.\\n' +
'Please delete and rebuild the package, without compiling with NGCC, before attempting to publish.\\n' +
'Note that NGCC may have been run by importing this package into another project that is being built with Ivy enabled.\\n' +
'\')\" ' +
'&& exit 1';
// Just in case this package.json was synthesized due to a custom configuration
// we will ensure that the path to the containing folder exists before we write the file.
fs.ensureDir(dirname(packageJsonPath));

View File

@ -55,7 +55,8 @@ export interface PackageJsonFormatProperties {
*/
export interface EntryPointPackageJson extends PackageJsonFormatProperties {
name: string;
__processed_by_ivy_ngcc__?: {[key: string]: string};
scripts?: Record<string, string>;
__processed_by_ivy_ngcc__?: Record<string, string>;
}
export type EntryPointJsonProperty = Exclude<keyof PackageJsonFormatProperties, 'types'|'typings'>;

View File

@ -84,6 +84,7 @@ runInEachFileSystem(() => {
const fs = getFileSystem();
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
expect(pkg.scripts).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']);
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
@ -91,6 +92,7 @@ runInEachFileSystem(() => {
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
expect(pkg.scripts.prepublishOnly).toBeDefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']);
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
@ -98,6 +100,7 @@ runInEachFileSystem(() => {
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.scripts.prepublishOnly).toBeDefined();
});
it('should update the packageJson object in-place', () => {
@ -105,18 +108,21 @@ runInEachFileSystem(() => {
const fs = getFileSystem();
const pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
expect(pkg.scripts).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5']);
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
expect(pkg.scripts.prepublishOnly).toBeDefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['esm2015', 'esm5']);
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.fesm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm2015).toBe('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBe('0.0.0-PLACEHOLDER');
expect(pkg.scripts.prepublishOnly).toBeDefined();
});
it('should one perform one write operation for all updated properties', () => {
@ -128,6 +134,19 @@ runInEachFileSystem(() => {
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015', 'fesm5', 'esm2015', 'esm5']);
expect(writeFileSpy).toHaveBeenCalledTimes(1);
});
it(`should keep backup of existing 'prepublishOnly' script`, () => {
const COMMON_PACKAGE_PATH = _('/node_modules/@angular/common/package.json');
const fs = getFileSystem();
const prepublishOnly = 'existing script';
let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
pkg.scripts = {prepublishOnly};
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, ['fesm2015']);
pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.scripts.prepublishOnly).toContain('This is not allowed');
expect(pkg.scripts.prepublishOnly__ivy_ngcc_bak).toBe(prepublishOnly);
});
});
describe('hasBeenProcessed', () => {