diff --git a/.gitignore b/.gitignore index ebe4b53..9616d58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ -node_modules \ No newline at end of file +node_modules +deps.json* \ No newline at end of file diff --git a/bin/compile b/bin/compile index 6e7f89a..962f660 100644 --- a/bin/compile +++ b/bin/compile @@ -27,3 +27,13 @@ CACHE_DIR=${CACHE_DIR} \ ENV_DIR=${ENV_DIR} \ SOURCE_VERSION=${SOURCE_VERSION} \ node ${BP_DIR}/lib/upload.js | output "$LOG_FILE" + + +### Send deps to Frontier Dashboard +if [ -f $1/package.json ]; then + printf "\nFrontier Dashboard steps:\n" + printf "Scanning deps... " && npm ls --json --prefix ${BUILD_DIR} > deps.json && printf "Done.\n" + printf "Flattening deps... " && BUILD_DIR=${BUILD_DIR} BP_DIR=${BP_DIR} node ${BP_DIR}/bin/flatten-deps.js && printf "Done.\n" + printf "Sending deps to Frontier Dashboard... " curl -X POST -H "x-api-key: hmPlMe6As2aNih6Dg3sRj8mHhiM9mDWo1bldZoaz" -d "$(cat ./deps.json.flat)" https://tiagtww9kj.execute-api.us-east-1.amazonaws.com/dev/app/deploy/snapshot && printf "Done.\n" + printf "Done with Frontier Dashboard steps\n" +fi diff --git a/bin/deps-test.sh b/bin/deps-test.sh new file mode 100644 index 0000000..7d6fc55 --- /dev/null +++ b/bin/deps-test.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# bin/compile + +### Configure environment + +set -o errexit # always exit on error +set -o pipefail # don't ignore exit codes when piping output +set -o nounset # fail on unset variables +unset GIT_DIR # Avoid GIT_DIR leak from previous build steps + +### Send deps to Frontier Dashboard +printf "\nFrontier Dashboard steps:\n" +printf "$(ls -la)" +printf "Scanning deps... " && npm ls --json > deps.json && printf "Done.\n" +printf "Flattening deps... " && node bin/flatten-deps.js && printf "Done.\n" +printf "Sending deps to Frontier Dashboard... " curl -X POST -H "x-api-key: hmPlMe6As2aNih6Dg3sRj8mHhiM9mDWo1bldZoaz" -d "$(cat ./deps.json.flat)" https://tiagtww9kj.execute-api.us-east-1.amazonaws.com/dev/app/deploy/snapshot && printf "Done.\n" +printf "Done with Frontier Dashboard steps\n" diff --git a/bin/flatten-deps.js b/bin/flatten-deps.js new file mode 100644 index 0000000..e86b29c --- /dev/null +++ b/bin/flatten-deps.js @@ -0,0 +1,76 @@ +const path = require('path'); +const fs = require('fs'); +const semverGt = require('semver/functions/gt') +const semverValid = require('semver/functions/valid') +// heroku buildpack dir or local test +const buildDir = process.env.BUILD_DIR || '../' +const bpDir = process.env.BP_DIR || '../' +const deps = require(path.join(bpDir, 'deps.json')); +// get host app's package.json +const package = require(path.join(buildDir, 'package.json')); +console.log('Flattening dependencies for', package.name, '...'); + +const output = { + name: deps.name, + version: deps.version +} + +const recursiveSearch = (obj, searchKey, results = []) => { + const r = results; + Object.keys(obj).forEach(key => { + const value = obj[key]; + // push package version + if (key !== searchKey && typeof value === 'object' && value.hasOwnProperty('version')) { + r.push({ name: key, version: value.version }); + } + // recurse into deps + if (key === searchKey && typeof value === 'object') { + recursiveSearch(value, searchKey, r); + } + // recurse into sub-deps + if (typeof value === 'object' && value.hasOwnProperty(searchKey)) { + recursiveSearch(value[searchKey], searchKey, r); + } + }); + return r; +}; + +function findDepVersion(deps, searchKey) { + let dep = {name: searchKey}; + for (const d of deps) { + if (d.name === searchKey) { + dep = d; + break; + } + }; + return dep +} + +const deepDeps = recursiveSearch(deps, 'dependencies'); +console.log("Deep dependency count:", deepDeps.length); + +// dedupe deps, keeping the highest semver version for each +const dedupedDeps = deepDeps.reduce((acc, dep) => { + const existing = acc.find(d => d.name === dep.name); + if (existing) { + console.log('exists, versions:', existing.version, dep.version); + if (semverValid(existing.version) && semverValid(dep.version) && semverGt(dep.version, existing.version)) { + acc.splice(acc.indexOf(existing), 1, dep); + } + } else { + acc.push(dep); + } + return acc; +}, []); +console.log("Deduped dependency count:", dedupedDeps.length); + + +// normal deps +output.deps = package.dependencies ? Object.keys(package.dependencies).map(d => findDepVersion(dedupedDeps, d)): []; +output.devDeps = package.devDependencies ? Object.keys(package.devDependencies).map(d => findDepVersion(dedupedDeps, d)) : []; +output.peerDeps = package.peerDependencies ? Object.keys(package.peerDependencies).map(d => findDepVersion(dedupedDeps, d)) : []; +// deps that aren't in deps, devDeps, or peerDeps +output.secondaryDeps = dedupedDeps.filter(d => !output.deps.includes(d) && !output.devDeps.includes(d) && !output.peerDeps.includes(d)); + +// Write to file +fs.writeFileSync(path.join(bpDir, 'deps.json.flat'), JSON.stringify(output, null, 2)); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ebf3498..4de20b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -308,6 +308,14 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -437,6 +445,14 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + }, "shelljs": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", @@ -492,6 +508,11 @@ "version": "9.0.7", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index 147db69..18c9d44 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,16 @@ "aws-sdk": "^2.5.0", "del": "^6.0.0", "glob": "^7.0.5", + "lodash": "^4.15.0", + "mime-types": "^2.1.11", + "semver": "^7.3.7", + "shelljs": "^0.8.4" + }, + "devDependencies": { + "async": "^3.2.0", + "aws-sdk": "^2.5.0" + }, + "peerDependencies": { "lodash": "^4.15.0", "mime-types": "^2.1.11", "shelljs": "^0.8.4"