From 6c7ca5f70eb857f7029c78a313fa2ccf96364657 Mon Sep 17 00:00:00 2001 From: joncloud Date: Tue, 8 Sep 2020 09:32:15 -0700 Subject: [PATCH 01/14] Adds support for running makensis on Linux/macOS * Creates abstraction for detecting paths by platform * Updates CI to run on linux and macOS --- .github/workflows/node.js.yml | 16 ++++++--- installer.js | 27 +++++---------- nsisPaths.js | 63 +++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 nsisPaths.js diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 979280f..3210c42 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -1,6 +1,3 @@ -# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions - name: Node.js CI on: @@ -12,16 +9,27 @@ on: jobs: build: - runs-on: windows-latest + runs-on: ${{ matrix.os }} strategy: matrix: + os: [ 'windows-latest', 'ubuntu-latest', 'macos-latest' ] node-version: [12.x] steps: - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} + + - name: 'Install makensis (apt)' + run: apt update && apt install -y nsis nsis-pluginapi + if: ${{ matrix.os == 'ubuntu-latest' }} + + - name: 'Install makensis (homebrew)' + run: brew update && brew install makensis + if: ${{ matrix.os == 'macos-latest' }} + - run: npm test diff --git a/installer.js b/installer.js index 8275907..34ab38f 100644 --- a/installer.js +++ b/installer.js @@ -1,6 +1,7 @@ const fs = require('fs'); const { execSync } = require('child_process'); const path = require('path'); +const nsisPaths = require('./nsisPaths'); const isDirectory = (item) => { const stats = fs.statSync(item); @@ -39,12 +40,6 @@ const copyDirectory = (src, dest) => { }); }; -const nsisInstallPath = path.join('C:', 'Program Files (x86)', 'NSIS'); - -const installerPathExists = () => { - return fs.existsSync(nsisInstallPath); -}; - class Installer { constructor(debugMode) { this.debugMode = debugMode; @@ -91,24 +86,20 @@ class Installer { } createInstaller(scriptPath) { - if (!installerPathExists()) { - const installerPathMessage = `Installer path does not exist at ${nsisInstallPath}`; - this.debugLog(installerPathMessage); - throw new Error(installerPathMessage); - } - console.log(`Creating installer for: ${scriptPath}`); // Include any of the plugins that may have been requested. - const nsisPluginPath = path.join(nsisInstallPath, 'plugins'); - this.pluginPaths.forEach(pluginPath => { - console.log('Including plugin path', pluginPath); - copyDirectory(pluginPath, nsisPluginPath); - }); + if (this.pluginPaths.length) { + const nsisPluginPath = nsisPaths.getPluginsPath(); + this.pluginPaths.forEach(pluginPath => { + console.log('Including plugin path', pluginPath); + copyDirectory(pluginPath, nsisPluginPath); + }); + } const args = this.getProcessArguments(scriptPath); - const nsis3Exe = path.join(nsisInstallPath, 'makensis.exe'); + const nsis3Exe = nsisPaths.getMakensisPath(); const makeCommand = `"${nsis3Exe}" ${args.join(' ')}`; this.debugLog(`Running ${makeCommand}`); const process = execSync(makeCommand); diff --git a/nsisPaths.js b/nsisPaths.js new file mode 100644 index 0000000..524d848 --- /dev/null +++ b/nsisPaths.js @@ -0,0 +1,63 @@ +const fs = require('fs'); +const path = require('path'); +const { platform } = require('process'); + +const getWin32Nsis = () => { + const root = path.join('C:', 'Program Files (x86)', 'NSIS'); + + return { + makensis: path.join(root, 'makensis.exe'), + plugins: path.join(root, 'plugins') + }; +}; + +const getLinuxNsis = () => { + const firstValidPath = (paths) => { + const possiblePaths = paths.filter(fs.existsSync); + + return possiblePaths.length ? possiblePaths[0] : ''; + }; + + return { + makensis: firstValidPath([ + '/usr/local/bin/makensis', + '/usr/bin/makensis' + ]), + plugins: firstValidPath([ + '/usr/local/share/nsis' + ]) + }; +}; + +class NsisPaths { + constructor(paths) { + this.paths = paths; + } + + validatePath(path, name) { + if (!fs.existsSync(path)) { + throw new Error(`${name} path does not exist at ${path}.`); + } + return path; + } + + getMakensisPath() { + return this.validatePath( + this.paths.makensis, + 'makensis' + ); + } + + getPluginsPath() { + return this.validatePath( + this.paths.plugins, + 'plugins' + ); + } +} + +const paths = platform === 'win32' + ? getWin32Nsis() + : getLinuxNsis(); + +module.exports = new NsisPaths(paths); From 049dd28c9e34466055236733999ca1674f11d3dd Mon Sep 17 00:00:00 2001 From: joncloud Date: Tue, 8 Sep 2020 09:36:16 -0700 Subject: [PATCH 02/14] Changes arguments to use hyphens for compatibility --- installer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/installer.js b/installer.js index 34ab38f..5185fa0 100644 --- a/installer.js +++ b/installer.js @@ -72,10 +72,10 @@ class Installer { const args = []; if (this.customArguments.indexOf('/V') === -1 && this.customArguments.indexOf('-V') === -1) { if (this.debugMode) { - args.push('/V4'); + args.push('-V4'); } else { - args.push('/V1'); + args.push('-V1'); } } From 9196fc2ae667eae7c977e066ef20903df2e0478e Mon Sep 17 00:00:00 2001 From: joncloud Date: Tue, 8 Sep 2020 09:37:07 -0700 Subject: [PATCH 03/14] Fixes tests --- test/installer.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/installer.spec.js b/test/installer.spec.js index 10817a5..ee2a640 100644 --- a/test/installer.spec.js +++ b/test/installer.spec.js @@ -36,7 +36,7 @@ describe('Installer', () => { const args = target.getProcessArguments(existingScriptPath); - expect(args).to.contain('/V1'); + expect(args).to.contain('-V1'); }); it('should default to all verbosity, given **debug** mode', () => { @@ -45,7 +45,7 @@ describe('Installer', () => { const args = target.getProcessArguments(existingScriptPath); - expect(args).to.contain('/V4'); + expect(args).to.contain('-V4'); }); it('should not add verbosity, given /V is in arguments', () => { @@ -56,7 +56,7 @@ describe('Installer', () => { const args = target.getProcessArguments(existingScriptPath); - expect(args).to.not.contain('/V1'); + expect(args).to.not.contain('-V1'); expect(args).to.contain('/V2'); }); @@ -68,7 +68,7 @@ describe('Installer', () => { const args = target.getProcessArguments(existingScriptPath); - expect(args).to.not.contain('/V1'); + expect(args).to.not.contain('-V1'); expect(args).to.contain('-V2'); }); From d6272a3d4140329f15d3b3d292e2d572198d20d8 Mon Sep 17 00:00:00 2001 From: joncloud Date: Tue, 8 Sep 2020 09:39:07 -0700 Subject: [PATCH 04/14] Sudo please --- .github/workflows/node.js.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 3210c42..0b0ad6d 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -25,7 +25,7 @@ jobs: node-version: ${{ matrix.node-version }} - name: 'Install makensis (apt)' - run: apt update && apt install -y nsis nsis-pluginapi + run: sudo apt update && apt install -y nsis nsis-pluginapi if: ${{ matrix.os == 'ubuntu-latest' }} - name: 'Install makensis (homebrew)' From 010d9c5809d7c6133ff04c6df30885208cbd5039 Mon Sep 17 00:00:00 2001 From: joncloud Date: Tue, 8 Sep 2020 09:41:03 -0700 Subject: [PATCH 05/14] Sudo pretty please --- .github/workflows/node.js.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 0b0ad6d..d607b4e 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -25,7 +25,7 @@ jobs: node-version: ${{ matrix.node-version }} - name: 'Install makensis (apt)' - run: sudo apt update && apt install -y nsis nsis-pluginapi + run: sudo apt update && sudo apt install -y nsis nsis-pluginapi if: ${{ matrix.os == 'ubuntu-latest' }} - name: 'Install makensis (homebrew)' From 1b06abeb160518aed56ef8c5614185e02e0265b1 Mon Sep 17 00:00:00 2001 From: joncloud Date: Tue, 8 Sep 2020 09:45:21 -0700 Subject: [PATCH 06/14] Adds clean up permissions since node_modules are committed --- .github/workflows/node.js.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index d607b4e..1e1a0c2 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -32,4 +32,8 @@ jobs: run: brew update && brew install makensis if: ${{ matrix.os == 'macos-latest' }} + - name: 'Clean up permissions' + if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' }} + run: sudo chown -R $(whoami) . + - run: npm test From 52f9eb1765bc3c42211b91a8f6e4d2f0c1c6fec4 Mon Sep 17 00:00:00 2001 From: joncloud Date: Tue, 8 Sep 2020 09:48:56 -0700 Subject: [PATCH 07/14] Sets executable flag --- node_modules/mocha/bin/_mocha | 0 node_modules/mocha/bin/mocha | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 node_modules/mocha/bin/_mocha mode change 100644 => 100755 node_modules/mocha/bin/mocha diff --git a/node_modules/mocha/bin/_mocha b/node_modules/mocha/bin/_mocha old mode 100644 new mode 100755 diff --git a/node_modules/mocha/bin/mocha b/node_modules/mocha/bin/mocha old mode 100644 new mode 100755 From 9b6723f4b2d4a011053b0db21e25ba55f1d59fc1 Mon Sep 17 00:00:00 2001 From: joncloud Date: Tue, 8 Sep 2020 09:51:42 -0700 Subject: [PATCH 08/14] Cleans up node_modules for non-windows --- .github/workflows/node.js.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 1e1a0c2..f90a38f 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -32,8 +32,8 @@ jobs: run: brew update && brew install makensis if: ${{ matrix.os == 'macos-latest' }} - - name: 'Clean up permissions' + - name: 'Clean up' if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' }} - run: sudo chown -R $(whoami) . + run: rm -rf node_modules && npm install - run: npm test From 567facfef1ee70e78ad9df105320aa7917567985 Mon Sep 17 00:00:00 2001 From: joncloud Date: Tue, 8 Sep 2020 09:53:41 -0700 Subject: [PATCH 09/14] Adds missing dev dependency --- package-lock.json | 6 ++++++ package.json | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 3b5ed95..d88ae6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1335,6 +1335,12 @@ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true + }, "yargs": { "version": "13.3.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", diff --git a/package.json b/package.json index 3b51bd7..b87304a 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "devDependencies": { "chai": "^4.2.0", - "mocha": "^8.1.3" + "mocha": "^8.1.3", + "yaml": "^1.10.0" } } From ef527283fccc9f97a317a9ae38f30e7685eefce6 Mon Sep 17 00:00:00 2001 From: joncloud Date: Tue, 8 Sep 2020 10:02:04 -0700 Subject: [PATCH 10/14] Updates readme to account for Linux and macOS compatibility --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 38106f2..0de295d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,20 @@ # Nullsoft scriptable install system GitHub action -This action calls `C:\Program Files (x86)\NSIS\makensis.exe` to create an installer. This action is currently only compatible with Windows. +This action calls `makensis` to create a Windows installer. This codebase was ported from the Azure DevOps Extension [dev-maxima/nsis-extension][]. [dev-maxima/nsis-extension]: https://github.com/dev-maxima/nsis-extension +## Platforms +### Windows +Windows platforms requires makensis to be installed to the `C:\Program Files (x86)\NSIS\` directory, where `makensis.exe` and `Plugins` exist. + +### Linux and macOS +Linux and macOS require the following paths to exist: +* makensis must exist at `/usr/local/bin/makensis` or `/usr/bin/makensis` +* Plugins must exist at `/usr/local/share/nsis` + ## Inputs ### `script-file` From 8f2ff21f1a21a23493219c6b15f439e0f284ad20 Mon Sep 17 00:00:00 2001 From: joncloud Date: Wed, 9 Sep 2020 18:41:39 -0700 Subject: [PATCH 11/14] Updates Plugin path to use makensis to determine * Replaces paths type with wrapper around makensis executable * Changing plugin calculation to use HDRINFO / NSISDIR --- installer.js | 22 +++++++----- makensis.js | 80 +++++++++++++++++++++++++++++++++++++++++ node_modules/.bin/mocha | 0 nsisPaths.js | 63 -------------------------------- 4 files changed, 93 insertions(+), 72 deletions(-) create mode 100644 makensis.js mode change 100644 => 100755 node_modules/.bin/mocha delete mode 100644 nsisPaths.js diff --git a/installer.js b/installer.js index 5185fa0..66eaf70 100644 --- a/installer.js +++ b/installer.js @@ -1,7 +1,6 @@ const fs = require('fs'); -const { execSync } = require('child_process'); const path = require('path'); -const nsisPaths = require('./nsisPaths'); +const makensis = require('./makensis'); const isDirectory = (item) => { const stats = fs.statSync(item); @@ -90,19 +89,24 @@ class Installer { // Include any of the plugins that may have been requested. if (this.pluginPaths.length) { - const nsisPluginPath = nsisPaths.getPluginsPath(); + const nsisdir = makensis.getSymbols().NSISDIR; + if (!nsisdir) { + throw new Error('Unable to determine NSISDIR. Check makensis -HDRINFO output'); + } + const nsisPluginPath = path.join(nsisdir, 'Plugins'); + this.debugLog(`Using system Plugins path ${nsisPluginPath}`); + this.pluginPaths.forEach(pluginPath => { console.log('Including plugin path', pluginPath); copyDirectory(pluginPath, nsisPluginPath); }); } - const args = this.getProcessArguments(scriptPath); - - const nsis3Exe = nsisPaths.getMakensisPath(); - const makeCommand = `"${nsis3Exe}" ${args.join(' ')}`; - this.debugLog(`Running ${makeCommand}`); - const process = execSync(makeCommand); + const args = this.getProcessArguments(scriptPath) + .join(' '); + + this.debugLog(`Running ${args}`); + const _ = makensis.execSync(args); } }; diff --git a/makensis.js b/makensis.js new file mode 100644 index 0000000..52d136c --- /dev/null +++ b/makensis.js @@ -0,0 +1,80 @@ +const fs = require('fs'); +const { execSync } = require('child_process'); +const path = require('path'); +const { platform, env } = require('process'); + +const firstValidPath = (paths) => { + const possiblePaths = paths.filter(fs.existsSync); + + return possiblePaths.length ? possiblePaths[0] : ''; +}; + +const getWin32Path = () => { + const evaluationPaths = env.PATH.split(';').concat([ + path.join('C:', 'Program Files (x86)', 'NSIS') + ]).map(p => path.join(p, 'makensis.exe')); + + return firstValidPath( + evaluationPaths + ); +}; + +const getLinuxPath = () => { + const evaluationPaths = env.PATH.split(':').concat([ + '/usr/local/bin', + '/usr/bin' + ]).map(p => path.join(p, 'makensis')); + + return firstValidPath(evaluationPaths); +}; + +class Makensis { + constructor(path) { + if (!fs.existsSync(path)) { + throw new Error('Unable to find makensis executable'); + } + this.path = path; + } + + execSync(args) { + const buffer = execSync(`${this.path} ${args}`); + + return buffer; + } + + getSymbols() { + const buffer = this.execSync('-HDRINFO'); + + // Look for the defined symbols in the output. + const exp = /Defined symbols: (.*)/g; + const matches = exp.exec(buffer.toString('utf-8')); + if (!matches || !matches.length || matches.length < 2) { + throw new Error('Unable to get symbols'); + } + + // Create a map of all of the symbols. + const symbols = { }; + + // Get all of the symbols, which are comma delimited + // keys or key value pairs separated by =. + matches[1].split(',').forEach(text => { + const index = text.indexOf('='); + if (index === -1) { + symbols[text] = ''; + } + else { + const name = text.substr(0, index); + const value = text.substr(index + 1); + symbols[name] = value; + } + }); + + return symbols; + } +} + +const makensisPath = platform === 'win32' + ? getWin32Path() + : getLinuxPath(); + +module.exports = new Makensis(makensisPath); diff --git a/node_modules/.bin/mocha b/node_modules/.bin/mocha old mode 100644 new mode 100755 diff --git a/nsisPaths.js b/nsisPaths.js deleted file mode 100644 index 524d848..0000000 --- a/nsisPaths.js +++ /dev/null @@ -1,63 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const { platform } = require('process'); - -const getWin32Nsis = () => { - const root = path.join('C:', 'Program Files (x86)', 'NSIS'); - - return { - makensis: path.join(root, 'makensis.exe'), - plugins: path.join(root, 'plugins') - }; -}; - -const getLinuxNsis = () => { - const firstValidPath = (paths) => { - const possiblePaths = paths.filter(fs.existsSync); - - return possiblePaths.length ? possiblePaths[0] : ''; - }; - - return { - makensis: firstValidPath([ - '/usr/local/bin/makensis', - '/usr/bin/makensis' - ]), - plugins: firstValidPath([ - '/usr/local/share/nsis' - ]) - }; -}; - -class NsisPaths { - constructor(paths) { - this.paths = paths; - } - - validatePath(path, name) { - if (!fs.existsSync(path)) { - throw new Error(`${name} path does not exist at ${path}.`); - } - return path; - } - - getMakensisPath() { - return this.validatePath( - this.paths.makensis, - 'makensis' - ); - } - - getPluginsPath() { - return this.validatePath( - this.paths.plugins, - 'plugins' - ); - } -} - -const paths = platform === 'win32' - ? getWin32Nsis() - : getLinuxNsis(); - -module.exports = new NsisPaths(paths); From 3a2cb778b972b74597c892d3d8cb2854de6ff392 Mon Sep 17 00:00:00 2001 From: joncloud Date: Wed, 9 Sep 2020 18:43:39 -0700 Subject: [PATCH 12/14] Fixes windows pathing problem --- makensis.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makensis.js b/makensis.js index 52d136c..58e250d 100644 --- a/makensis.js +++ b/makensis.js @@ -37,7 +37,7 @@ class Makensis { } execSync(args) { - const buffer = execSync(`${this.path} ${args}`); + const buffer = execSync(`"${this.path}" ${args}`); return buffer; } From 10b42c3761491d48a458bea1171ae310f61090a1 Mon Sep 17 00:00:00 2001 From: joncloud Date: Wed, 9 Sep 2020 18:46:08 -0700 Subject: [PATCH 13/14] Adds MacPorts well known path --- makensis.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/makensis.js b/makensis.js index 58e250d..57b1870 100644 --- a/makensis.js +++ b/makensis.js @@ -22,7 +22,8 @@ const getWin32Path = () => { const getLinuxPath = () => { const evaluationPaths = env.PATH.split(':').concat([ '/usr/local/bin', - '/usr/bin' + '/usr/bin', + '/opt/local/bin' ]).map(p => path.join(p, 'makensis')); return firstValidPath(evaluationPaths); From e83f8b26e07033139aec5e0e402204cee9e8488b Mon Sep 17 00:00:00 2001 From: joncloud Date: Wed, 9 Sep 2020 18:56:12 -0700 Subject: [PATCH 14/14] Updates readme to account for lookup --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0de295d..f984fa8 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,14 @@ This codebase was ported from the Azure DevOps Extension [dev-maxima/nsis-extens [dev-maxima/nsis-extension]: https://github.com/dev-maxima/nsis-extension ## Platforms -### Windows -Windows platforms requires makensis to be installed to the `C:\Program Files (x86)\NSIS\` directory, where `makensis.exe` and `Plugins` exist. -### Linux and macOS -Linux and macOS require the following paths to exist: -* makensis must exist at `/usr/local/bin/makensis` or `/usr/bin/makensis` -* Plugins must exist at `/usr/local/share/nsis` +This action looks for `makensis` or `makensis.exe` in the environment path, and if not found it will attempt to look in a couple of different places: + +* Windows - `C:\Program Files (x86)\NSIS\` +* Linux and macOS: + * `/usr/local/bin/` + * `/usr/bin/` + * `/opt/local/bin/` ## Inputs