From 17a786eb36a110f7936d2d62f2558d8ef74f43c8 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Mon, 20 Mar 2017 22:24:36 +0200 Subject: [PATCH] Ensure that `.bin` files maintain executable bits in Windows bundles. Windows has no concept of the executable bit so it is not applied by the `fstream` `Reader` when building the tarball which is used in both `meteor build` and `meteor deploy`. For Windows users, this causes important scripts (such as `node-pre-gyp`) to not be executable when the bundles are deployed to Unix platforms (such as Galaxy). To avoid giving every file executable bits, this applies an executable bit to the file only if it has read permission (something Windows _is_ aware of) and if it is in a location that Node bin links are typically placed, the `/node_modules/.bin/` directories. --- tools/fs/files.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tools/fs/files.js b/tools/fs/files.js index 5be613c173..f5b5a8ca99 100644 --- a/tools/fs/files.js +++ b/tools/fs/files.js @@ -879,6 +879,14 @@ function tryExtractWithNpmTar(buffer, tempDir, options) { }); } +// In the same fashion as node-pre-gyp does, add the executable +// bit but only if the read bit was present. Same as: +// https://github.com/mapbox/node-pre-gyp/blob/7a28f4b0f562ba4712722fefe4eeffb7b20fbf7a/lib/install.js#L71-L77 +// and others reported in: https://github.com/npm/node-tar/issues/7 +function addExecBitWhenReadBitPresent(fileMode) { + return fileMode |= (fileMode >>> 2) & 0o111; +} + // Tar-gzips a directory, returning a stream that can then be piped as // needed. The tar archive will contain a top-level directory named // after dirPath. @@ -887,6 +895,12 @@ files.createTarGzStream = function (dirPath, options) { var fstream = require('fstream'); var zlib = require("zlib"); + // Create a segment of the file path which we will look for to + // identify exactly what we think is a "bin" file (that is, something + // which should be expected to work within the context of an + // 'npm run-script'). + var binPathMatch = ["", "node_modules", ".bin", ""].join(path.sep); + // Don't use `{ path: dirPath, type: 'Directory' }` as an argument to // fstream.Reader. This triggers a collection of odd behaviors in fstream // (which might be bugs or might just be weirdnesses). @@ -926,8 +940,13 @@ files.createTarGzStream = function (dirPath, options) { // setting it in an 'entry' handler is the same strategy that npm // does, so we do that here too. if (entry.type === "Directory") { - entry.mode = (entry.mode || entry.props.mode) | 0o500; - entry.props.mode = entry.mode; + entry.props.mode = addExecBitWhenReadBitPresent(entry.props.mode); + } + + // In a similar way as for directories, but only if is in a path + // location that is expected to be executable (npm "bin" links) + if (entry.type === "File" && entry.path.indexOf(binPathMatch) > -1) { + entry.props.mode = addExecBitWhenReadBitPresent(entry.props.mode); } return true;