diff --git a/.github/workflows/update-abi.yml b/.github/workflows/update-abi.yml new file mode 100644 index 0000000..bc86057 --- /dev/null +++ b/.github/workflows/update-abi.yml @@ -0,0 +1,41 @@ +name: Auto-update ABI JSON file +on: + schedule: + - cron: '0 0 * * *' +jobs: + autoupdate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: '12.x' + - name: Get npm cache directory + id: npm-cache + run: | + echo "::set-output name=dir::$(npm config get cache)" + - uses: actions/cache@v1 + with: + path: ${{ steps.npm-cache.outputs.dir }} + key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-node- + - run: npm install + - name: Update Releases JSON + run: npm run electron-releases + - name: Commit Changes to Releases JSON + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "machine github.com login $GITHUB_ACTOR password $GITHUB_TOKEN" > ~/.netrc + chmod 600 ~/.netrc + git add abi_registry.json + if test -n "$(git status -s)"; then + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + git diff --cached + git commit -m "build: update Electron releases JSON" + git push origin HEAD:$GITHUB_REF + else + echo No update needed + fi diff --git a/abi_registry.json b/abi_registry.json new file mode 100644 index 0000000..83bc587 --- /dev/null +++ b/abi_registry.json @@ -0,0 +1,78 @@ +[ + { + "runtime": "node", + "target": "11.0.0", + "lts": false, + "future": false, + "abi": "67" + }, + { + "runtime": "electron", + "target": "5.0.0", + "lts": false, + "future": false, + "abi": "70" + }, + { + "runtime": "node", + "target": "12.0.0", + "lts": [ + "2019-10-21", + "2020-10-20" + ], + "future": false, + "abi": "68" + }, + { + "runtime": "electron", + "target": "6.0.0", + "lts": false, + "future": false, + "abi": "73" + }, + { + "runtime": "electron", + "target": "7.0.0", + "lts": false, + "future": false, + "abi": "75" + }, + { + "runtime": "electron", + "target": "8.0.0", + "lts": false, + "future": false, + "abi": "76" + }, + { + "runtime": "node", + "target": "13.0.0", + "lts": false, + "future": false, + "abi": "74" + }, + { + "runtime": "electron", + "target": "9.0.0-beta.1", + "lts": false, + "future": true, + "abi": "80" + }, + { + "runtime": "node", + "target": "14.0.0", + "lts": [ + "2020-10-20", + "2021-10-19" + ], + "future": true, + "abi": "81" + }, + { + "runtime": "electron", + "target": "10.0.0-beta.1", + "lts": false, + "future": true, + "abi": "82" + } +] \ No newline at end of file diff --git a/index.js b/index.js index 3428044..8a25f25 100644 --- a/index.js +++ b/index.js @@ -48,6 +48,47 @@ function getTarget (abi, runtime) { throw new Error('Could not detect target for abi ' + abi + ' and runtime ' + runtime) } +function loadGeneratedTargets () { + var registry = require('./abi_registry.json') + var targets = { + supported: [], + additional: [], + future: [] + } + + registry.forEach(function (item) { + var target = { + runtime: item.runtime, + target: item.target, + abi: item.abi + } + if (item.lts) { + var startDate = new Date(Date.parse(item.lts[0])) + var endDate = new Date(Date.parse(item.lts[1])) + var currentDate = new Date() + target.lts = startDate < currentDate && currentDate < endDate + } else { + target.lts = false + } + + if (target.runtime === 'node-webkit') { + targets.additional.push(target) + } else if (item.future) { + targets.future.push(target) + } else { + targets.supported.push(target) + } + }) + + targets.supported.sort() + targets.additional.sort() + targets.future.sort() + + return targets +} + +var generatedTargets = loadGeneratedTargets() + var supportedTargets = [ {runtime: 'node', target: '5.0.0', abi: '47', lts: false}, {runtime: 'node', target: '6.0.0', abi: '48', lts: false}, @@ -55,10 +96,6 @@ var supportedTargets = [ {runtime: 'node', target: '8.0.0', abi: '57', lts: false}, {runtime: 'node', target: '9.0.0', abi: '59', lts: false}, {runtime: 'node', target: '10.0.0', abi: '64', lts: new Date(2018, 10, 1) < new Date() && new Date() < new Date(2020, 4, 31)}, - {runtime: 'node', target: '11.0.0', abi: '67', lts: false}, - {runtime: 'node', target: '12.0.0', abi: '72', lts: new Date(2019, 9, 21) < new Date() && new Date() < new Date(2020, 9, 31)}, - {runtime: 'node', target: '13.0.0', abi: '79', lts: false}, - {runtime: 'node', target: '14.0.0', abi: '83', lts: false}, {runtime: 'electron', target: '0.36.0', abi: '47', lts: false}, {runtime: 'electron', target: '1.1.0', abi: '48', lts: false}, {runtime: 'electron', target: '1.3.0', abi: '49', lts: false}, @@ -70,14 +107,11 @@ var supportedTargets = [ {runtime: 'electron', target: '2.0.0', abi: '57', lts: false}, {runtime: 'electron', target: '3.0.0', abi: '64', lts: false}, {runtime: 'electron', target: '4.0.0', abi: '64', lts: false}, - {runtime: 'electron', target: '4.0.4', abi: '69', lts: false}, - {runtime: 'electron', target: '5.0.0', abi: '70', lts: false}, - {runtime: 'electron', target: '6.0.0', abi: '73', lts: false}, - {runtime: 'electron', target: '7.0.0', abi: '75', lts: false}, - {runtime: 'electron', target: '8.0.0', abi: '76', lts: false}, - {runtime: 'electron', target: '9.0.0', abi: '80', lts: false} + {runtime: 'electron', target: '4.0.4', abi: '69', lts: false} ] +supportedTargets.push.apply(supportedTargets, generatedTargets.supported) + var additionalTargets = [ {runtime: 'node-webkit', target: '0.13.0', abi: '47', lts: false}, {runtime: 'node-webkit', target: '0.15.0', abi: '48', lts: false}, @@ -86,6 +120,8 @@ var additionalTargets = [ {runtime: 'node-webkit', target: '0.26.5', abi: '59', lts: false} ] +additionalTargets.push.apply(additionalTargets, generatedTargets.additional) + var deprecatedTargets = [ {runtime: 'node', target: '0.2.0', abi: '1', lts: false}, {runtime: 'node', target: '0.9.1', abi: '0x000A', lts: false}, @@ -104,9 +140,7 @@ var deprecatedTargets = [ {runtime: 'electron', target: '0.33.0', abi: '46', lts: false} ] -var futureTargets = [ - {runtime: 'electron', target: '10.0.0-beta.1', abi: '82', lts: false} -] +var futureTargets = generatedTargets.future var allTargets = deprecatedTargets .concat(supportedTargets) diff --git a/package.json b/package.json index 4e96274..0198e2c 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "Get the Node ABI for a given target and runtime, and vice versa.", "main": "index.js", "scripts": { - "test": "tape test/index.js", "semantic-release": "semantic-release", - "travis-deploy-once": "travis-deploy-once" + "test": "tape test/index.js", + "travis-deploy-once": "travis-deploy-once", + "update-abi-registry": "node --unhandled-rejections=strict scripts/update-abi-registry.js" }, "repository": { "type": "git", @@ -26,6 +27,7 @@ }, "homepage": "https://github.com/lgeiger/node-abi#readme", "devDependencies": { + "got": "^10.6.0", "semantic-release": "^15.8.0", "tape": "^4.6.3", "travis-deploy-once": "^5.0.1" diff --git a/scripts/update-abi-registry.js b/scripts/update-abi-registry.js new file mode 100644 index 0000000..2e56c21 --- /dev/null +++ b/scripts/update-abi-registry.js @@ -0,0 +1,93 @@ +const got = require('got') +const path = require('path') +const semver = require('semver') +const { writeFile } = require('fs').promises + +async function getJSONFromCDN (urlPath) { + const response = await got(`https://cdn.jsdelivr.net/gh/${urlPath}`) + return JSON.parse(response.body) +} + +async function fetchElectronVersions () { + return (await getJSONFromCDN('electron/releases/lite.json')).map(metadata => metadata.version) +} + +async function fetchNodeVersions () { + const schedule = await getJSONFromCDN('nodejs/Release/schedule.json') + const versions = {} + + for (const [majorVersion, metadata] of Object.entries(schedule)) { + if (majorVersion.startsWith('v0')) { + continue + } + const version = `${majorVersion.slice(1)}.0.0` + const lts = metadata.hasOwnProperty('lts') ? [metadata.lts, metadata.maintenance] : false + versions[version] = { + runtime: 'node', + target: version, + lts: lts, + future: new Date(Date.parse(metadata.start)) > new Date() + } + } + + return versions +} + +async function fetchAbiVersions () { + return (await getJSONFromCDN('nodejs/node/doc/abi_version_registry.json')).NODE_MODULE_VERSION +} + +async function main () { + const nodeVersions = await fetchNodeVersions() + const abiVersions = await fetchAbiVersions() + const electronVersions = await fetchElectronVersions() + + const abiVersionSet = new Set() + const supportedTargets = [] + for (const abiVersion of abiVersions) { + if (abiVersion.modules <= 66) { + // Don't try to parse any ABI versions older than 60 + break + } else if (abiVersion.runtime === 'electron' && abiVersion.modules < 70) { + // Don't try to parse Electron ABI versions below Electron 5 + continue + } + + let target + if (abiVersion.runtime === 'node') { + const nodeVersion = `${abiVersion.versions.replace('.0.0-pre', '')}.0.0` + target = nodeVersions[nodeVersion] + if (!target) { + continue + } + } else { + target = { + runtime: abiVersion.runtime === 'nw.js' ? 'node-webkit' : abiVersion.runtime, + target: abiVersion.versions, + lts: false, + future: false + } + if (target.runtime === 'electron') { + target.target = `${target.target}.0.0` + const constraint = /^[0-9]/.test(abiVersion.versions) ? `>= ${abiVersion.versions}` : abiVersion.versions + if (!electronVersions.find(electronVersion => semver.satisfies(electronVersion, constraint))) { + target.target = `${target.target}-beta.1` + target.future = true + } + } + } + target.abi = abiVersion.modules.toString() + + const key = [target.runtime, target.target].join('-') + if (abiVersionSet.has(key)) { + continue + } + + abiVersionSet.add(key) + supportedTargets.unshift(target) + } + + await writeFile(path.resolve(__dirname, '..', 'abi_registry.json'), JSON.stringify(supportedTargets, null, 2)) +} + +main() diff --git a/test/index.js b/test/index.js index fe37ee4..2cdbefa 100644 --- a/test/index.js +++ b/test/index.js @@ -5,7 +5,7 @@ var getTarget = require('../index').getTarget var getNextTarget = require('../index')._getNextTarget var allTargets = require('../index').allTargets -test('getNextTarget gets the next unsopported target', function (t) { +test('getNextTarget gets the next unsupported target', function (t) { var mockTargets = [ {runtime: 'node', target: '7.0.0', abi: '51', lts: false}, {runtime: 'node', target: '8.0.0', abi: '57', lts: false}, @@ -35,6 +35,7 @@ test('getTarget calculates correct Electron target', function (t) { t.equal(getTarget('48', 'electron'), '1.1.0') t.equal(getTarget('49', 'electron'), '1.3.0') t.equal(getTarget('50', 'electron'), '1.4.0') + t.equal(getTarget('76', 'electron'), '8.0.0') t.end() }) @@ -53,6 +54,7 @@ test('getAbi calculates correct Node ABI', function (t) { t.equal(getAbi(null), process.versions.modules) t.throws(function () { getAbi('a.b.c') }) t.throws(function () { getAbi(getNextTarget('node')) }) + t.equal(getAbi('12.0.0'), '68') t.equal(getAbi('7.2.0'), '51') t.equal(getAbi('7.0.0'), '51') t.equal(getAbi('6.9.9'), '48') @@ -155,8 +157,8 @@ test('allTargets are sorted', function (t) { return semver.compare(t1.target, t2.target) } - t.deepEqual(electron, electron.slice().sort(sort)) - t.deepEqual(node, node.slice().sort(sort)) - t.deepEqual(nodeWebkit, nodeWebkit.slice().sort(sort)) + t.deepEqual(electron, electron.slice().sort(sort), 'electron targets are sorted') + t.deepEqual(node, node.slice().sort(sort), 'node targets are sorted') + t.deepEqual(nodeWebkit, nodeWebkit.slice().sort(sort), 'node-webkit targets are sorted') t.end() })