Skip to content

Commit ec9779c

Browse files
authored
fix: validate non-peerDependencies at build time (#687)
1 parent 530d54e commit ec9779c

File tree

8 files changed

+88
-11
lines changed

8 files changed

+88
-11
lines changed

integration/samples.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ do
1313
elif [ -f $P_JSON ]; then
1414
echo "Building sample ${P_JSON}..."
1515
node dist/cli/main.js -p ${P_JSON}
16-
else
16+
elif [ -f $P ]; then
1717
echo "Building sample ${P}..."
18-
node dist/cli/main.js -p ${P}
18+
if [ $P='failures' ]; then
19+
node dist/cli/main.js -p ${P} || true
20+
else
21+
node dist/cli/main.js -p ${P}
22+
fi
1923
fi
2024
echo "Built."
2125
done

integration/samples/failures/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const PUBLISHING_NON_PEER_DEPENDENCIES = 'should fail';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "non-peer-deps",
3+
"description": "https://github.com/dherges/ng-packagr/pull/687",
4+
"dependencies": {
5+
"foobar": "1.0.0"
6+
},
7+
"ngPackage": {
8+
"dest": "./dist",
9+
"lib": {
10+
"entryFile": "index.ts"
11+
}
12+
}
13+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { expect } from 'chai';
2+
import * as fs from 'fs-extra';
3+
import * as path from 'path';
4+
5+
describe(`Failure Builds`, () => {
6+
describe(`non-peer-deps`, () => {
7+
it(`should have no build output`, () => {
8+
const exists = fs.existsSync(path.resolve(__dirname, 'dist'));
9+
expect(exists).to.be.false;
10+
});
11+
});
12+
});

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,14 @@
122122
"publish:ci": "yarn prerelease && yarn postrelease",
123123
"integration:samples": "integration/samples.sh",
124124
"integration:samples:dev": "ts-node --project src/tsconfig.packagr.json ./integration/samples.dev.ts",
125-
"integration:specs": "cross-env TS_NODE_PROJECT=integration/tsconfig.specs.json mocha --require ts-node/register integration/samples/*/specs/**/*.ts",
125+
"integration:specs":
126+
"cross-env TS_NODE_PROJECT=integration/tsconfig.specs.json mocha --require ts-node/register \"integration/samples/*/specs/**/*.ts\"",
126127
"integration:consumers": "integration/consumers.sh",
127128
"integration:consumers:ngc": "ngc -p integration/consumers/tsc/tsconfig.json",
128-
"test:specs": "cross-env TS_NODE_PROJECT=src/tsconfig.specs.json mocha --require ts-node/register \"src/**/*.spec.ts\"",
129-
"test": "yarn build && yarn test:specs && yarn integration:samples && yarn integration:specs && yarn integration:consumers",
129+
"test:specs":
130+
"cross-env TS_NODE_PROJECT=src/tsconfig.specs.json mocha --require ts-node/register \"src/**/*.spec.ts\"",
131+
"test":
132+
"yarn build && yarn test:specs && yarn integration:samples && yarn integration:specs && yarn integration:consumers",
130133
"commitmsg": "commitlint -e",
131134
"precommit": "pretty-quick --staged",
132135
"gh-pages": "gh-pages -d docs/ghpages"

src/lib/ng-package-format/package.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ export class NgPackage {
7070
return this.primary.$get('keepLifecycleScripts') === true;
7171
}
7272

73+
public get whitelistedNonPeerDependencies(): string[] {
74+
// XX: default array values from JSON schema not recognized
75+
const defValue = ['tslib'];
76+
const value = (this.primary.$get('whitelistedNonPeerDependencies') as string[]) || defValue;
77+
78+
// Always append 'tslib' and dedupe
79+
return value.concat('tslib').filter((value, index, self) => self.indexOf(value) === index);
80+
}
81+
7382
private absolutePathFromPrimary(key: string) {
7483
return path.resolve(this.basePath, this.primary.$get(key));
7584
}

src/lib/ng-v5/entry-point/write-package.transform.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { NgEntryPoint } from '../../ng-package-format/entry-point';
55
import { NgPackage } from '../../ng-package-format/package';
66
import { ensureUnixPath } from '../../util/path';
77
import { copyFiles } from '../../util/copy';
8+
import { rimraf } from '../../util/rimraf';
89
import * as log from '../../util/log';
910
import { isEntryPointInProgress } from '../nodes';
1011

@@ -78,11 +79,16 @@ export async function writePackageJson(
7879
}
7980
}
8081

81-
packageJson.name = entryPoint.moduleId;
82-
83-
// keep the dist package.json clean
84-
// this will not throw if ngPackage field does not exist
85-
delete packageJson.ngPackage;
82+
// Verify non-peerDependencies as they can easily lead to duplicated installs or version conflicts
83+
// in the node_modules folder of an application
84+
const whitelist = pkg.whitelistedNonPeerDependencies.map(value => new RegExp(value));
85+
try {
86+
checkNonPeerDependencies(packageJson, 'dependencies', whitelist);
87+
checkNonPeerDependencies(packageJson, 'devDependencies', whitelist);
88+
} catch (e) {
89+
await rimraf(entryPoint.destinationPath);
90+
throw e;
91+
}
8692

8793
// Removes scripts from package.json after build
8894
if (pkg.keepLifecycleScripts !== true) {
@@ -94,6 +100,11 @@ export async function writePackageJson(
94100
);
95101
}
96102

103+
// keep the dist package.json clean
104+
// this will not throw if ngPackage field does not exist
105+
delete packageJson.ngPackage;
106+
packageJson.name = entryPoint.moduleId;
107+
97108
// `outputJson()` creates intermediate directories, if they do not exist
98109
// -- https://github.com/jprichardson/node-fs-extra/blob/master/docs/outputJson.md
99110
await fs.outputJson(path.resolve(entryPoint.destinationPath, 'package.json'), packageJson, { spaces: 2 });
@@ -111,3 +122,18 @@ export async function copyJavaScriptBundles(stageDir: string, destDir: string):
111122
export async function copyTypingsAndMetadata(from: string, to: string): Promise<void> {
112123
await copyFiles(`${from}/**/*.{d.ts,metadata.json}`, to);
113124
}
125+
126+
function checkNonPeerDependencies(packageJson: { [key: string]: any }, property: string, whitelist: RegExp[]) {
127+
if (packageJson[property]) {
128+
Object.keys(packageJson[property]).forEach(dep => {
129+
if (whitelist.find(regex => regex.test(dep))) {
130+
log.debug(`Dependency ${dep} is whitelisted in '${property}'`);
131+
} else {
132+
log.warn(
133+
`Distributing npm packages with '${property}' is not recommended. Please consider adding ${dep} to 'peerDepenencies' or remove it from '${property}'.`
134+
);
135+
throw new Error(`Dependency ${dep} must be explicitly whitelisted.`);
136+
}
137+
});
138+
}
139+
}

src/ng-package.schema.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131
"type": "string",
3232
"default": ".ng_pkg_build"
3333
},
34+
"whitelistedNonPeerDependencies": {
35+
"description":
36+
"A list of dependencies that are allowed in the 'dependendencies' and 'devDependencies' section of package.json. Values in the list are regular expressions matched against npm package names.",
37+
"type": "array",
38+
"items": {
39+
"type": "string"
40+
},
41+
"default": ["tslib"]
42+
},
3443
"lib": {
3544
"description": "Description of the library's entry point.",
3645
"type": "object",
@@ -83,7 +92,7 @@
8392
"default": "none"
8493
},
8594
"sassIncludePaths": {
86-
"description": "DEPRECATED: Please use styleIncludePaths",
95+
"description": "DEPRECATED: Please use styleIncludePaths. sassIncludePaths will be removed in v3!",
8796
"type": "array",
8897
"items": {
8998
"type": "string"

0 commit comments

Comments
 (0)