From 46304a4f8341aa9ceba753974df8e96eb01627e8 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Wed, 7 Aug 2019 08:34:00 +0200 Subject: [PATCH] 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 --- .../ngcc/src/packages/build_marker.ts | 11 +++++++++++ .../ngcc/src/packages/entry_point.ts | 3 ++- .../ngcc/test/packages/build_marker_spec.ts | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/compiler-cli/ngcc/src/packages/build_marker.ts b/packages/compiler-cli/ngcc/src/packages/build_marker.ts index 4cc43207e3..554ccccc64 100644 --- a/packages/compiler-cli/ngcc/src/packages/build_marker.ts +++ b/packages/compiler-cli/ngcc/src/packages/build_marker.ts @@ -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)); diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point.ts b/packages/compiler-cli/ngcc/src/packages/entry_point.ts index a173a59472..8884a16804 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point.ts @@ -55,7 +55,8 @@ export interface PackageJsonFormatProperties { */ export interface EntryPointPackageJson extends PackageJsonFormatProperties { name: string; - __processed_by_ivy_ngcc__?: {[key: string]: string}; + scripts?: Record; + __processed_by_ivy_ngcc__?: Record; } export type EntryPointJsonProperty = Exclude; diff --git a/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts b/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts index c288fad146..15dc9883e6 100644 --- a/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts @@ -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', () => {