From 17539102383b8ab733209ab0dc4d2a61d516548e Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Tue, 6 Nov 2018 18:08:46 +0200 Subject: [PATCH] feat(jsii): enforce peer dependencies (#294) If a jsii module exposes a type from a dependency in it's public API, jsii now enforces that this dependency is also defined as a "peer" instead of a normal dependency. This tells npm that if a user of this module already installed a compatible verison of this dependency in their closure, npm will pick the one installed by the user (as a peer) instead of fetching another version. jsii will also flag these dependencies as "peer" in the jsii spec. At the moment, this won't have implications on generated language packages, but in environments that have support for peer dependencies, we should make sure the module's metadata reflects this idea as well. A utility called "jsii-fix-peers" is included. It will inspect .jsii and package.json and will add "peerDependencies" to package.json for all dependencies that have types in the public API. Related to awslabs/aws-cdk#979 --- package-lock.json | 19 ++-- packages/jsii-calc-base/package.json | 3 + packages/jsii-calc-base/test/assembly.jsii | 3 +- packages/jsii-calc-lib/package.json | 3 + packages/jsii-calc-lib/test/assembly.jsii | 4 +- packages/jsii-calc/package.json | 4 + packages/jsii-calc/test/assembly.jsii | 7 +- .../.jsii | 3 +- .../.jsii | 4 +- .../.jsii | 7 +- packages/jsii-spec/lib/spec.ts | 16 ++++ packages/jsii/bin/jsii-fix-peers | 2 + packages/jsii/bin/jsii-fix-peers.ts | 95 +++++++++++++++++++ packages/jsii/lib/assembler.ts | 35 ++++++- packages/jsii/lib/project-info.ts | 40 +++++--- packages/jsii/package.json | 3 +- packages/jsii/test/test.negatives.ts | 1 + 17 files changed, 221 insertions(+), 28 deletions(-) create mode 100755 packages/jsii/bin/jsii-fix-peers create mode 100644 packages/jsii/bin/jsii-fix-peers.ts diff --git a/package-lock.json b/package-lock.json index 7b8bba1be7..95e0955a7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -426,7 +426,8 @@ "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true }, "growl": { "version": "1.10.5", @@ -597,6 +598,7 @@ "dedent": "^0.7.0", "execa": "^0.8.0", "find-up": "^2.1.0", + "fs-extra": "^4.0.1", "get-port": "^3.2.0", "glob": "^7.1.2", "glob-parent": "^3.1.0", @@ -1409,9 +1411,10 @@ } }, "fs-extra": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.0.tgz", - "integrity": "sha512-EglNDLRpmaTWiD/qraZn6HREAEAHJcJOmxNEYwq6xeMKnVMAy3GUcFB+wXt2C6k4CNvB/mP1y/U3dzvKKj5OtQ==", + "version": "4.0.3", + "resolved": "http://localhost:4873/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -1884,8 +1887,9 @@ }, "jsonfile": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "resolved": "http://localhost:4873/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, "requires": { "graceful-fs": "^4.1.6" } @@ -2844,8 +2848,9 @@ }, "universalify": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + "resolved": "http://localhost:4873/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true }, "unzip-response": { "version": "2.0.1", diff --git a/packages/jsii-calc-base/package.json b/packages/jsii-calc-base/package.json index 0e745d1f18..d7e3de28c9 100644 --- a/packages/jsii-calc-base/package.json +++ b/packages/jsii-calc-base/package.json @@ -34,6 +34,9 @@ "dependencies": { "@scope/jsii-calc-base-of-base": "^0.7.8" }, + "peerDependencies": { + "@scope/jsii-calc-base-of-base": "^0.7.8" + }, "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com", diff --git a/packages/jsii-calc-base/test/assembly.jsii b/packages/jsii-calc-base/test/assembly.jsii index 6c2dc94153..d9b4a575a4 100644 --- a/packages/jsii-calc-base/test/assembly.jsii +++ b/packages/jsii-calc-base/test/assembly.jsii @@ -9,6 +9,7 @@ }, "dependencies": { "@scope/jsii-calc-base-of-base": { + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", @@ -102,5 +103,5 @@ } }, "version": "0.7.8", - "fingerprint": "G+ZeFV6LQWFdp9ZuDgqN4rS10q/JBHuy0Wo8qaTK/t8=" + "fingerprint": "K1rAUs6WiQ5lF08T46B8v/5UL8T8Ot59e0Nc8rh2jiQ=" } diff --git a/packages/jsii-calc-lib/package.json b/packages/jsii-calc-lib/package.json index 882955e410..66caf3214b 100644 --- a/packages/jsii-calc-lib/package.json +++ b/packages/jsii-calc-lib/package.json @@ -34,6 +34,9 @@ "dependencies": { "@scope/jsii-calc-base": "^0.7.8" }, + "peerDependencies": { + "@scope/jsii-calc-base": "^0.7.8" + }, "author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com", diff --git a/packages/jsii-calc-lib/test/assembly.jsii b/packages/jsii-calc-lib/test/assembly.jsii index dc6ac23805..5e0a9d1ce5 100644 --- a/packages/jsii-calc-lib/test/assembly.jsii +++ b/packages/jsii-calc-lib/test/assembly.jsii @@ -11,6 +11,7 @@ "@scope/jsii-calc-base": { "dependencies": { "@scope/jsii-calc-base-of-base": { + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", @@ -30,6 +31,7 @@ "version": "0.7.8" } }, + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace", @@ -351,5 +353,5 @@ } }, "version": "0.7.8", - "fingerprint": "HzcyHys0b9gFmP4dogeIJmGE6GVtrSo/P0S54Vd/X8U=" + "fingerprint": "ENH00UDSeTnXKpvD3/vp0k8zyd+KCHzc9QWB151/gM8=" } diff --git a/packages/jsii-calc/package.json b/packages/jsii-calc/package.json index 515e45b365..675cae4033 100644 --- a/packages/jsii-calc/package.json +++ b/packages/jsii-calc/package.json @@ -35,6 +35,10 @@ "@scope/jsii-calc-lib": "^0.7.8", "jsii-calc-bundled": "^0.7.8" }, + "peerDependencies": { + "@scope/jsii-calc-base": "^0.7.8", + "@scope/jsii-calc-lib": "^0.7.8" + }, "devDependencies": { "@types/node": "^8.10.37", "jsii": "^0.7.8", diff --git a/packages/jsii-calc/test/assembly.jsii b/packages/jsii-calc/test/assembly.jsii index c6ffb76b77..97e7a8e8a5 100644 --- a/packages/jsii-calc/test/assembly.jsii +++ b/packages/jsii-calc/test/assembly.jsii @@ -37,6 +37,7 @@ "@scope/jsii-calc-base": { "dependencies": { "@scope/jsii-calc-base-of-base": { + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", @@ -56,6 +57,7 @@ "version": "0.7.8" } }, + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace", @@ -79,6 +81,7 @@ "@scope/jsii-calc-base": { "dependencies": { "@scope/jsii-calc-base-of-base": { + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", @@ -98,6 +101,7 @@ "version": "0.7.8" } }, + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace", @@ -117,6 +121,7 @@ "version": "0.7.8" } }, + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.LibNamespace", @@ -3407,5 +3412,5 @@ } }, "version": "0.7.8", - "fingerprint": "jHSXTzCSZbwYMvLKpeZB6SE8hNgYgt9/2JF1ihM41SI=" + "fingerprint": "Xn7Rk17rqR3AaMx3+ssxT0GR1sCWwz0OGC+C8QuLI3A=" } diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/.jsii b/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/.jsii index 6c2dc94153..d9b4a575a4 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/.jsii +++ b/packages/jsii-pacmak/test/expected.jsii-calc-base/dotnet/Amazon.JSII.Tests.CalculatorPackageId.BasePackageId/.jsii @@ -9,6 +9,7 @@ }, "dependencies": { "@scope/jsii-calc-base-of-base": { + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", @@ -102,5 +103,5 @@ } }, "version": "0.7.8", - "fingerprint": "G+ZeFV6LQWFdp9ZuDgqN4rS10q/JBHuy0Wo8qaTK/t8=" + "fingerprint": "K1rAUs6WiQ5lF08T46B8v/5UL8T8Ot59e0Nc8rh2jiQ=" } diff --git a/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/.jsii b/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/.jsii index dc6ac23805..5e0a9d1ce5 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/.jsii +++ b/packages/jsii-pacmak/test/expected.jsii-calc-lib/dotnet/Amazon.JSII.Tests.CalculatorPackageId.LibPackageId/.jsii @@ -11,6 +11,7 @@ "@scope/jsii-calc-base": { "dependencies": { "@scope/jsii-calc-base-of-base": { + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", @@ -30,6 +31,7 @@ "version": "0.7.8" } }, + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace", @@ -351,5 +353,5 @@ } }, "version": "0.7.8", - "fingerprint": "HzcyHys0b9gFmP4dogeIJmGE6GVtrSo/P0S54Vd/X8U=" + "fingerprint": "ENH00UDSeTnXKpvD3/vp0k8zyd+KCHzc9QWB151/gM8=" } diff --git a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii index c6ffb76b77..97e7a8e8a5 100644 --- a/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii +++ b/packages/jsii-pacmak/test/expected.jsii-calc/dotnet/Amazon.JSII.Tests.CalculatorPackageId/.jsii @@ -37,6 +37,7 @@ "@scope/jsii-calc-base": { "dependencies": { "@scope/jsii-calc-base-of-base": { + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", @@ -56,6 +57,7 @@ "version": "0.7.8" } }, + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace", @@ -79,6 +81,7 @@ "@scope/jsii-calc-base": { "dependencies": { "@scope/jsii-calc-base-of-base": { + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseOfBaseNamespace", @@ -98,6 +101,7 @@ "version": "0.7.8" } }, + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.BaseNamespace", @@ -117,6 +121,7 @@ "version": "0.7.8" } }, + "peer": true, "targets": { "dotnet": { "namespace": "Amazon.JSII.Tests.CalculatorNamespace.LibNamespace", @@ -3407,5 +3412,5 @@ } }, "version": "0.7.8", - "fingerprint": "jHSXTzCSZbwYMvLKpeZB6SE8hNgYgt9/2JF1ihM41SI=" + "fingerprint": "Xn7Rk17rqR3AaMx3+ssxT0GR1sCWwz0OGC+C8QuLI3A=" } diff --git a/packages/jsii-spec/lib/spec.ts b/packages/jsii-spec/lib/spec.ts index 43d239cda7..5150455ed2 100644 --- a/packages/jsii-spec/lib/spec.ts +++ b/packages/jsii-spec/lib/spec.ts @@ -153,6 +153,22 @@ export interface PackageVersion { /** Dependencies of this dependency */ dependencies?: { [assembly: string]: PackageVersion }; + + /** + * Indicates if this dependency is a peer dependency or a normal dependency. + * + * Peer dependencies are expected to be explicitly defined by the user of + * this library instead of brought in as transitive dependencies. + * + * jsii enforces that if this module exports a type from a dependency, this + * dependency must be defined as a peer and not as a normal dependency. + * Otherwise, it would be impossible to safely use two versions of this + * dependency in a closure. + * + * @see https://github.com/awslabs/aws-cdk/issues/979 + * @see https://nodejs.org/en/blog/npm/peer-dependencies/ + */ + peer?: boolean; } /** diff --git a/packages/jsii/bin/jsii-fix-peers b/packages/jsii/bin/jsii-fix-peers new file mode 100755 index 0000000000..03978a0086 --- /dev/null +++ b/packages/jsii/bin/jsii-fix-peers @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./jsii-fix-peers.js'); \ No newline at end of file diff --git a/packages/jsii/bin/jsii-fix-peers.ts b/packages/jsii/bin/jsii-fix-peers.ts new file mode 100644 index 0000000000..7e2feb7ed4 --- /dev/null +++ b/packages/jsii/bin/jsii-fix-peers.ts @@ -0,0 +1,95 @@ +// +// jsii-fix-peers +// +// Inspects the local .jsii file and adds "peerDependencies" to the local +// package.json for all modules that include types that are referenced as part +// of the current module's public API. +// +// This resolves all peer dependency warnings emitted by the jsii compiler. +// + +// tslint:disable:no-console + +import fs = require('fs-extra'); +import spec = require('jsii-spec'); + +fixPeers().catch(e => { + console.error(e.stack); + process.exit(1); +}); + +async function fixPeers() { + const jsiiFile = './.jsii'; + const pkgFile = './package.json'; + + if (!(await fs.pathExists(jsiiFile))) { + throw new Error(`Cannot find '${jsiiFile}. Make sure to compile first`); + } + + if (!(await fs.pathExists(pkgFile))) { + throw new Error(`Cannot find '${pkgFile}' in current working directory`); + } + + const jsii = await fs.readJson(jsiiFile) as spec.Assembly; + const pkg = await fs.readJson(pkgFile); + + // find all type references (FQNs) in the assembly. + const fqns = findAllFQNs(jsii); + + let updated = false; + + // read "dependencies" and "peerDependencies" from package.json + if (!pkg.peerDependencies) { pkg.peerDependencies = {}; } + const peers = pkg.peerDependencies; + const deps = pkg.dependencies || {}; + + // iterate over all FQNs and ensure that all referenced modules appear as peer dependencies + for (const fqn of fqns) { + const [ module, ] = fqn.split('.'); + if (module === jsii.name) { + continue; + } + + // verify that `module` exists in peerDependencies + if (module in peers) { + continue; + } + + // fetch the module's version requirement from "dependencies" (we expect it to be there) + const dep = deps[module]; + if (!dep) { + throw new Error(`Cannot find ${module} neither under "peerDependencies" nor "dependencies". Can't fix.`); + } + + // fixing time! add this module to "peerDependencies" + peers[module] = dep; + console.log(`added ${module}@${dep} to "peerDependencies"`); + updated = true; + } + + if (updated) { + await fs.writeJson(pkgFile, pkg, { spaces: 2 }); + } +} + +/** + * Looks up all { "fqn": "boom" } objects in a JSON tree. + * @param node Initial node (i.e. a JSII assembly) + * @param out Used by the recursive call to collect outputs + */ +function findAllFQNs(node: any, out = new Set()) { + if (typeof(node) === 'object' && 'fqn' in node) { + out.add(node.fqn); + } + + for (const key of Object.keys(node)) { + const value = node[key]; + if (Array.isArray(value)) { + value.forEach(v => findAllFQNs(v, out)); + } else if (typeof(value) === 'object') { + findAllFQNs(value, out); + } + } + + return Array.from(out); +} diff --git a/packages/jsii/lib/assembler.ts b/packages/jsii/lib/assembler.ts index 4d2ae4ea06..d9847e6792 100644 --- a/packages/jsii/lib/assembler.ts +++ b/packages/jsii/lib/assembler.ts @@ -103,7 +103,7 @@ export class Assembler implements Emitter { author: this.projectInfo.author, contributors: this.projectInfo.contributors && [...this.projectInfo.contributors], repository: this.projectInfo.repository, - dependencies: _toDependencies(this.projectInfo.dependencies), + dependencies: _toDependencies(this.projectInfo.dependencies, this.projectInfo.peerDependencies), bundled: this.projectInfo.bundleDependencies, types: this._types, targets: this.projectInfo.targets, @@ -199,6 +199,18 @@ export class Assembler implements Emitter { } else { const assembly = this.projectInfo.transitiveDependencies.find(dep => dep.name === assm); type = assembly && assembly.types && assembly.types[ref.fqn]; + + // since we are exposing a type of this assembly in this module's public API, + // we expect it to appear as a peer dependency instead of a normal dependency. + if (assembly) { + const asPeerDependency = this.projectInfo.peerDependencies.find(d => d.name === assembly.name); + if (!asPeerDependency) { + this._diagnostic(referencingNode, ts.DiagnosticCategory.Warning, + `The type '${ref.fqn}' is exposed in the public API of this module. ` + + `Therefore, the module '${assembly.name}' must also be defined under "peerDependencies". ` + + `You can use the "jsii-fix-peers" utility to fix.`); + } + } } if (!type) { @@ -1045,7 +1057,7 @@ function _sortMembers(type: spec.ClassType | spec.InterfaceType): spec.ClassTyp }; } -function _toDependencies(assemblies: ReadonlyArray): { [name: string]: spec.PackageVersion } { +function _toDependencies(assemblies: ReadonlyArray, peers: ReadonlyArray): { [name: string]: spec.PackageVersion } { const result: { [name: string]: spec.PackageVersion } = {}; for (const assembly of assemblies) { result[assembly.name] = { @@ -1054,6 +1066,25 @@ function _toDependencies(assemblies: ReadonlyArray): { [name: str dependencies: assembly.dependencies }; } + + for (const peer of peers) { + if (peer.name in result) { + // module already appears as a normal dependency. just make sure it's the same version + const depVersion = result[peer.name].version; + if (depVersion !== peer.version) { + throw new Error( + `Module '${peer.name}' appears both as a dependency (${depVersion}) ` + + `and a peer dependency (${peer.version}), with mismatching versions`); + } + } + + result[peer.name] = { + version: peer.version, + targets: peer.targets, + dependencies: peer.dependencies, + peer: true + }; + } return result; } diff --git a/packages/jsii/lib/project-info.ts b/packages/jsii/lib/project-info.ts index 380743acd3..e759cac32b 100644 --- a/packages/jsii/lib/project-info.ts +++ b/packages/jsii/lib/project-info.ts @@ -26,6 +26,7 @@ export interface ProjectInfo { readonly types: string; readonly dependencies: ReadonlyArray; + readonly peerDependencies: ReadonlyArray; readonly transitiveDependencies: ReadonlyArray; readonly bundleDependencies: { readonly [name: string]: string }; readonly targets: spec.AssemblyTargets; @@ -46,8 +47,26 @@ export async function loadProjectInfo(projectRoot: string): Promise bundleDependencies[name] = version; }); - const [dependencies, transitiveDependencies] = - await _loadDependencies(pkg.dependencies, projectRoot, new Set(Object.keys(bundleDependencies))); + // verify peer dependencies and dependencies have the same version specs + const deps = pkg.dependencies || { }; + const peerDeps = pkg.peerDependencies || { }; + for (const module of Object.keys(peerDeps)) { + const peerVersion = peerDeps[module]; + const depVersion = deps[module]; + if (depVersion && peerVersion !== depVersion) { + throw new Error( + `The module '${module}' is specified as ${peerVersion} under ` + + `"peerDependencieds" and as ${depVersion} under "dependencies"`); + } + } + + const transitiveAssemblies: { [name: string]: spec.Assembly } = {}; + const dependencies = + await _loadDependencies(pkg.dependencies, projectRoot, transitiveAssemblies, new Set(Object.keys(bundleDependencies))); + const peerDependencies = + await _loadDependencies(pkg.peerDependencies, projectRoot, transitiveAssemblies); + + const transitiveDependencies = Object.keys(transitiveAssemblies).map(name => transitiveAssemblies[name]); return { projectRoot, @@ -65,6 +84,7 @@ export async function loadProjectInfo(projectRoot: string): Promise types: _required(pkg.types, 'The "package.json" file must specify the "types" attribute'), dependencies, + peerDependencies, transitiveDependencies, bundleDependencies, targets: { @@ -90,10 +110,10 @@ function _guessRepositoryType(url: string): string { async function _loadDependencies(dependencies: { [name: string]: string | spec.PackageVersion } | undefined, searchPath: string, - bundled: Set = new Set()): Promise<[spec.Assembly[], spec.Assembly[]]> { - if (!dependencies) { return [[], []]; } + transitiveAssemblies: { [name: string]: spec.Assembly }, + bundled = new Set()): Promise { + if (!dependencies) { return []; } const assemblies = new Array(); - const transitiveAssemblies = new Array(); for (const name of Object.keys(dependencies)) { if (bundled.has(name)) { continue; } const dep = dependencies[name]; @@ -109,17 +129,13 @@ async function _loadDependencies(dependencies: { [name: string]: string | spec. throw new Error(`Declared dependency on version ${versionString} of ${name}, but version ${assm.version} was found`); } assemblies.push(assm); - transitiveAssemblies.push(assm); + transitiveAssemblies[assm.name] = assm; const pkgDir = path.dirname(pkg); if (assm.dependencies) { - const [depAssemblies, depTransitiveAssemblies, ] = await _loadDependencies(assm.dependencies, pkgDir); - for (const depAssembly of depAssemblies.concat(depTransitiveAssemblies)) { - if (transitiveAssemblies.find(a => a.name === depAssembly.name) != null) { continue; } - transitiveAssemblies.push(depAssembly); - } + await _loadDependencies(assm.dependencies, pkgDir, transitiveAssemblies); } } - return [assemblies, transitiveAssemblies]; + return assemblies; } function _required(value: T, message: string): T { diff --git a/packages/jsii/package.json b/packages/jsii/package.json index 4960ad579b..bfdcc7aa9f 100644 --- a/packages/jsii/package.json +++ b/packages/jsii/package.json @@ -9,7 +9,8 @@ "organization": true }, "bin": { - "jsii": "bin/jsii" + "jsii": "bin/jsii", + "jsii-fix-peers": "bin/jsii-fix-peers" }, "scripts": { "build": "cp ../../README.md . && bash ./generate.sh && tsc", diff --git a/packages/jsii/test/test.negatives.ts b/packages/jsii/test/test.negatives.ts index 2d4b174af2..40335652a7 100644 --- a/packages/jsii/test/test.negatives.ts +++ b/packages/jsii/test/test.negatives.ts @@ -70,6 +70,7 @@ function _makeProjectInfo(types: string): ProjectInfo { author: { name: 'John Doe', roles: ['author'] }, repository: { type: 'git', url: 'https://github.com/awslabs/jsii.git' }, dependencies: [], + peerDependencies: [], transitiveDependencies: [], bundleDependencies: {}, targets: {}