pax_global_header00006660000000000000000000000064141621664640014524gustar00rootroot0000000000000052 comment=67c07a556b321c8b65cade138fa00f1dbda6ece9 install-artifact-from-github-1.3.0/000077500000000000000000000000001416216646400172075ustar00rootroot00000000000000install-artifact-from-github-1.3.0/.editorconfig000066400000000000000000000003061416216646400216630ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 2 [*.{h,cc,cpp}] indent_style = tab indent_size = 4 install-artifact-from-github-1.3.0/.github/000077500000000000000000000000001416216646400205475ustar00rootroot00000000000000install-artifact-from-github-1.3.0/.github/workflows/000077500000000000000000000000001416216646400226045ustar00rootroot00000000000000install-artifact-from-github-1.3.0/.github/workflows/codeql-analysis.yml000066400000000000000000000050361416216646400264230ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. name: "CodeQL" on: push: branches: [master] pull_request: # The branches below must be a subset of the branches above branches: [master] schedule: - cron: '0 16 * * 5' jobs: analyze: name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: # Override automatic language detection by changing the below list # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] language: ['javascript'] # Learn more... # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection steps: - name: Checkout repository uses: actions/checkout@v2 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. fetch-depth: 2 # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 if: ${{ github.event_name == 'pull_request' }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v1 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 install-artifact-from-github-1.3.0/.gitignore000066400000000000000000000000331416216646400211730ustar00rootroot00000000000000node_modules/ .AppleDouble install-artifact-from-github-1.3.0/.prettierrc000066400000000000000000000001771416216646400214000ustar00rootroot00000000000000{ "printWidth": 160, "singleQuote": true, "bracketSpacing": false, "arrowParens": "avoid", "trailingComma": "none" } install-artifact-from-github-1.3.0/LICENSE000066400000000000000000000035651416216646400202250ustar00rootroot00000000000000This library is available under the terms of the modified BSD license. No external contributions are allowed under licenses which are fundamentally incompatible with the BSD license that this library is distributed under. The text of the BSD license is reproduced below. ------------------------------------------------------------------------------- The "New" BSD License: ********************** Copyright (c) 2005-2020, Eugene Lazutkin All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Eugene Lazutkin nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. install-artifact-from-github-1.3.0/README.md000066400000000000000000000052371416216646400204750ustar00rootroot00000000000000# install-artifact-from-github [![NPM version][npm-img]][npm-url] [npm-img]: https://img.shields.io/npm/v/install-artifact-from-github.svg [npm-url]: https://npmjs.org/package/install-artifact-from-github This is a no-dependency micro helper for developers of binary addons for Node. It is literally two small one-file utilities integrated with [GitHub releases](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/about-releases). The project solves two problems: * [save-to-github-cache](./Saving) saves a binary artifact to a Github release according to the platform, architecture, and Node ABI. * Designed to be used with [GitHub actions](https://github.com/features/actions). * [install-from-cache](./Installing) retrieves a previously saved artifact, tests if it works properly, and rebuilds a project from sources in the case of failure. In general, it can save your users from a long recompilation and, in some cases, even save them from installing build tools. By using GitHub facilities ([Releases](https://docs.github.com/en/github/administering-a-repository/about-releases) and [Actions](https://github.com/features/actions)) the whole process of publishing and subsequent installations are secure, transparent, painless, inexpensive, or even free for public repositories. ## How to install Installation: ``` npm install --save install-artifact-from-github ``` ## How to use In your `package.json` (pseudo-code with comments): ```js { // your custom package.json stuff // ... "scripts": { // your scripts go here // ... // saves an artifact "save-to-github": "save-to-github-cache --artifact build/Release/ABC.node", // installs using pre-created artifacts "install": "install-from-cache --artifact build/Release/ABC.node", // used by "install" to test the artifact "verify-build": "node scripts/verify-build.js" // used by "install" to rebuild from sources "rebuild": "node-gyp rebuild" } } ``` Examples of GitHub actions can be found in the documentation. ## Documentation The full documentation is available in the [wiki](https://github.com/uhop/install-artifact-from-github/wiki). ## Release history - 1.3.0 *enhanced support for custom mirrors.* - 1.2.0 *support for NPM >= 7.* - 1.1.3 *technical release: updated docs.* - 1.1.2 *technical release: updated docs.* - 1.1.1 *numerous bugfixes to please Github REST API.* - 1.1.0 *moved `save-to-github` here from a separate project, reduced 3rd-party dependencies.* - 1.0.2 *fixed a `yarn`-specific bug.* - 1.0.1 *fixed a bug in the environment variable parameter.* - 1.0.0 *initial release (extracted from [node-re2](https://github.com/uhop/node-re2)).* install-artifact-from-github-1.3.0/bin/000077500000000000000000000000001416216646400177575ustar00rootroot00000000000000install-artifact-from-github-1.3.0/bin/install-from-cache.js000077500000000000000000000164011416216646400237720ustar00rootroot00000000000000#!/usr/bin/env node 'use strict'; const {promises: fsp} = require('fs'); const path = require('path'); const zlib = require('zlib'); const {promisify} = require('util'); const http = require('http'); const https = require('https'); const {exec, spawnSync} = require('child_process'); const spawnOptions = {encoding: 'utf8', env: process.env}; const getPlatform = () => { const platform = process.platform; if (platform !== 'linux') return platform; // detecting musl using algorithm from https://github.com/lovell/detect-libc under Apache License 2.0 let result = spawnSync('getconf', ['GNU_LIBC_VERSION'], spawnOptions); if (!result.status && !result.signal) return platform; result = spawnSync('ldd', ['--version'], spawnOptions); if (result.signal) return platform; if ((!result.status && result.stdout.toString().indexOf('musl') >= 0) || (result.status === 1 && result.stderr.toString().indexOf('musl') >= 0)) return platform + '-musl'; return platform; }; const platform = getPlatform(); const isParamPresent = name => process.argv.indexOf('--' + name) > 0; const getParam = (name, defaultValue = '') => { const index = process.argv.indexOf('--' + name); if (index > 0) return process.argv[index + 1] || ''; return defaultValue; }; const artifactPath = getParam('artifact'), prefix = getParam('prefix'), suffix = getParam('suffix'), mirrorHost = getParam('host'), mirrorEnvVar = getParam('host-var') || 'DOWNLOAD_HOST', skipPath = isParamPresent('skip-path'), skipPathVar = getParam('skip-path-var') || 'DOWNLOAD_SKIP_PATH', skipVer = isParamPresent('skip-ver'), skipVerVar = getParam('skip-ver-var') || 'DOWNLOAD_SKIP_VER'; const parseUrl = [ /^(?:https?|git|git\+ssh|git\+https?):\/\/github.com\/([^\/]+)\/([^\/\.]+)(?:\/|\.git\b|$)/i, /^github:([^\/]+)\/([^#]+)(?:#|$)/i, /^([^:\/]+)\/([^#]+)(?:#|$)/i ]; const isHttp = /^http:\/\//i, isHttps = /^https:\/\//i; const getRepo = url => { if (!url) return null; for (const re of parseUrl) { const result = re.exec(url); if (result) return result; } return null; }; const getAssetUrlPrefix = () => { const url = process.env.npm_package_github || (process.env.npm_package_repository_type === 'git' && process.env.npm_package_repository_url), result = getRepo(url); if (!result) return null; const host = mirrorHost || process.env[mirrorEnvVar] || 'https://github.com'; let assetUrl = host; if (!skipPath && !process.env[skipPathVar]) { assetUrl += `/${result[1]}/${result[2]}/releases/download`; } if (!skipVer && !process.env[skipVerVar]) { assetUrl += '/' + process.env.npm_package_version; } assetUrl += `/${prefix}${platform}-${process.arch}-${process.versions.modules}${suffix}`; return assetUrl; }; const isDev = async () => { if (process.env.DEVELOPMENT_SKIP_GETTING_ASSET) return true; try { await fsp.access('.development'); return true; } catch (e) { // squelch } return false; }; const run = async (cmd, suppressOutput) => new Promise((resolve, reject) => { const p = exec(cmd); let closed = false; p.on('exit', (code, signal) => { if (closed) return; closed = true; (signal || code) && reject(signal || code); resolve(0); }); p.on('error', error => !closed && ((closed = true), reject(error))); if (!suppressOutput || process.env.DEVELOPMENT_SHOW_VERIFICATION_RESULTS) { p.stdout.on('data', data => process.stdout.write(data)); p.stderr.on('data', data => process.stderr.write(data)); } }); const isVerified = async () => { try { if (process.env.npm_package_scripts_verify_build) { await run('npm run verify-build', true); } else if (process.env.npm_package_scripts_test) { await run('npm test', true); } else { console.log('No verify-build nor test scripts were found -- no way to verify the build automatically.'); return false; } } catch (e) { console.log('The verification has failed: building from sources ...'); return false; } return true; }; const get = async url => new Promise((resolve, reject) => { const httpLib = isHttps.test(url) ? https : isHttp.test(url) ? http : null; if (!httpLib) { // local file fsp.readFile(url).then(resolve, reject); return; } let buffer = null; httpLib .get(url, res => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers && res.headers.location) { get(res.headers.location).then(resolve, reject); return; } if (res.statusCode != 200) { reject(Error(`Status ${res.statusCode} for ${url}`)); return; } res.on('data', data => { if (buffer) { buffer = Buffer.concat([buffer, data]); } else { buffer = data; } }); res.on('end', () => resolve(buffer)); }) .on('error', e => reject(e)); }); const write = async (name, data) => { await fsp.mkdir(path.dirname(name), {recursive: true}); await fsp.writeFile(name, data); }; const main = async () => { checks: { if (process.env.npm_package_json && /\bpackage\.json$/i.test(process.env.npm_package_json)) { // for NPM >= 7 try { // read the package info const pkg = JSON.parse(await fsp.readFile(process.env.npm_package_json)); // populate necessary environment variables locally process.env.npm_package_github = pkg.github || ''; process.env.npm_package_repository_type = (pkg.repository && pkg.repository.type) || ''; process.env.npm_package_repository_url = (pkg.repository && pkg.repository.url) || ''; process.env.npm_package_version = pkg.version || ''; process.env.npm_package_scripts_verify_build = (pkg.scripts && pkg.scripts['verify-build']) || ''; process.env.npm_package_scripts_test = (pkg.scripts && pkg.scripts.test) || ''; } catch (error) { console.log('Could not retrieve and parse package.json.'); break checks; } } if (!artifactPath) { console.log('No artifact path was specified with --artifact.'); break checks; } if (await isDev()) { console.log('Development flag was detected.'); break checks; } const prefix = getAssetUrlPrefix(); if (!prefix) { console.log('No github repository was identified.'); break checks; } let copied = false; // let's try brotli if (zlib.brotliDecompress) { try { console.log(`Trying ${prefix}.br ...`); const artifact = await get(prefix + '.br'); console.log(`Writing to ${artifactPath} ...`); await write(artifactPath, await promisify(zlib.brotliDecompress)(artifact)); copied = true; } catch (e) { // squelch } } // let's try gzip if (!copied && zlib.gunzip) { try { console.log(`Trying ${prefix}.gz ...`); const artifact = await get(prefix + '.gz'); console.log(`Writing to ${artifactPath} ...`); await write(artifactPath, await promisify(zlib.gunzip)(artifact)); copied = true; } catch (e) { // squelch } } // verify the install if (copied && (await isVerified())) return console.log('Done.'); } console.log('Building locally ...'); await run('npm run rebuild'); }; main(); install-artifact-from-github-1.3.0/bin/save-to-github-cache.js000077500000000000000000000122331416216646400242200ustar00rootroot00000000000000#!/usr/bin/env node 'use strict'; const {promises: fsp} = require('fs'); const path = require('path'); const zlib = require('zlib'); const {promisify} = require('util'); const https = require('https'); const {spawnSync} = require('child_process'); const spawnOptions = {encoding: 'utf8', env: process.env}; const getPlatform = () => { const platform = process.platform; if (platform !== 'linux') return platform; // detecting musl using algorithm from https://github.com/lovell/detect-libc under Apache License 2.0 let result = spawnSync('getconf', ['GNU_LIBC_VERSION'], spawnOptions); if (!result.status && !result.signal) return platform; result = spawnSync('ldd', ['--version'], spawnOptions); if (result.signal) return platform; if ((!result.status && result.stdout.toString().indexOf('musl') >= 0) || (result.status === 1 && result.stderr.toString().indexOf('musl') >= 0)) return platform + '-musl'; return platform; }; const platform = getPlatform(); const getParam = (name, defaultValue = '') => { const index = process.argv.indexOf('--' + name); if (index > 0) return process.argv[index + 1] || ''; return defaultValue; }; const io = async (url, options = {}, data) => new Promise((resolve, reject) => { let buffer = null; const req = https .request(url, options, res => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers && res.headers.location) { io(res.headers.location, options, data).then(resolve, reject); return; } if (res.statusCode != 200) { reject(Error(`Status ${res.statusCode} for ${url}`)); return; } res.on('data', data => { if (buffer) { buffer = Buffer.concat([buffer, data]); } else { buffer = data; } }); res.on('end', () => resolve(buffer)); }) .on('error', e => reject(e)); data && req.write(data); req.end(); }); const get = async (url, options) => io(url, {...options, method: 'GET'}); const post = async (url, options, data) => io(url, {...options, method: 'POST'}, data); function url(parts) { let result = parts[0] || ''; for (let i = 1; i < parts.length; ++i) { result += encodeURIComponent(arguments[i]) + parts[i]; } return result; } const artifactPath = getParam('artifact'), prefix = getParam('prefix'), suffix = getParam('suffix'); const main = async () => { const [OWNER, REPO] = process.env.GITHUB_REPOSITORY.split('/'), TAG = /^refs\/tags\/(.*)$/.exec(process.env.GITHUB_REF)[1], TOKEN = process.env.GITHUB_TOKEN; const fileName = `${prefix}${platform}-${process.arch}-${process.versions.modules}${suffix}`; console.log('Preparing artifact', fileName, '...'); const [data, uploadUrl] = await Promise.all([ fsp.readFile(path.normalize(artifactPath)), get(url`https://api.github.com/repos/${OWNER}/${REPO}/releases/tags/${TAG}`, { auth: OWNER + ':' + TOKEN, headers: {Accept: 'application/vnd.github.v3+json', 'User-Agent': 'uhop/install-artifact-from-github'} }).then(response => { const data = JSON.parse(response.toString()), p = data.upload_url.indexOf('{'); return p > 0 ? data.upload_url.substr(0, p) : data.upload_url; }) ]); console.log('Compressing and uploading ...'); await Promise.all([ (async () => { if (!zlib.brotliCompress) return null; const compressed = await promisify(zlib.brotliCompress)(data, {params: {[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY}}), name = fileName + '.br', label = `Binary artifact: ${artifactPath} (${platform}, ${process.arch}, ${process.versions.modules}, brotli).`; return post( uploadUrl + '?' + url`name=${name}&label=${label}`, { auth: OWNER + ':' + TOKEN, headers: { Accept: 'application/vnd.github.v3+json', 'Content-Type': 'application/brotli', 'Content-Length': compressed.length, 'User-Agent': 'uhop/install-artifact-from-github' } }, compressed ) .then(() => console.log('Uploaded BR.')) .catch(() => console.log('BR has failed to upload.')); })(), (async () => { if (!zlib.gzip) return null; const compressed = await promisify(zlib.gzip)(data, {level: zlib.constants.Z_BEST_COMPRESSION}), name = fileName + '.gz', label = `Binary artifact: ${artifactPath} (${platform}, ${process.arch}, ${process.versions.modules}, gzip).`; return post( uploadUrl + '?' + url`name=${name}&label=${label}`, { auth: OWNER + ':' + TOKEN, headers: { Accept: 'application/vnd.github.v3+json', 'Content-Type': 'application/gzip', 'Content-Length': compressed.length, 'User-Agent': 'uhop/install-artifact-from-github' } }, compressed ) .then(() => console.log('Uploaded GZ.')) .catch(() => console.log('GZ has failed to upload.')); })() ]); console.log('Done.'); }; main().catch(e => { console.log('::error::' + ((e && e.message) || 'save-to-github has failed')); process.exit(1); }); install-artifact-from-github-1.3.0/package-lock.json000066400000000000000000000006151416216646400224250ustar00rootroot00000000000000{ "name": "install-artifact-from-github", "version": "1.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "install-artifact-from-github", "version": "1.3.0", "license": "BSD-3-Clause", "bin": { "install-from-cache": "bin/install-from-cache.js", "save-to-github-cache": "bin/save-to-github-cache.js" } } } } install-artifact-from-github-1.3.0/package.json000066400000000000000000000016041416216646400214760ustar00rootroot00000000000000{ "name": "install-artifact-from-github", "version": "1.3.0", "description": "Create binary artifacts hosted by github and install them without compiling.", "homepage": "https://github.com/uhop/install-artifact-from-github", "bugs": "https://github.com/uhop/install-artifact-from-github/issues", "github": "https://github.com/uhop/install-artifact-from-github", "repository": { "type": "git", "url": "git://github.com/uhop/install-artifact-from-github.git" }, "files": [ "/*.js", "/bin", "/src", "/utils" ], "bin": { "install-from-cache": "bin/install-from-cache.js", "save-to-github-cache": "bin/save-to-github-cache.js" }, "keywords": [ "helper", "node addon", "node addons", "github", "github action" ], "author": "Eugene Lazutkin (https://lazutkin.com/)", "license": "BSD-3-Clause" }