Skip to content

Commit

Permalink
Output asset permissions (#211)
Browse files Browse the repository at this point in the history
* output asset permissions

* readme update

* update coverage tests

* test fixes
  • Loading branch information
guybedford authored and rauchg committed Jan 9, 2019
1 parent c813b07 commit ae96fbb
Show file tree
Hide file tree
Showing 43 changed files with 334 additions and 285 deletions.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ require('@zeit/ncc')('/path/to/input', {
watch: false // default
}).then(({ code, map, assets }) => {
console.log(code);
// assets is an object of asset file names to sources
// assets is an object of asset file names to { source, permissions }
// expected relative to the output code (if any)
})
```
Expand Down
4 changes: 2 additions & 2 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function renderSummary(code, assets, outDir, buildTime) {
let totalSize = codeSize;
let maxAssetNameLength = 8; // "index.js".length
for (const asset of Object.keys(assets)) {
const assetSource = assets[asset];
const assetSource = assets[asset].source;
const assetSize = Math.round(
(assetSource.byteLength || Buffer.byteLength(assetSource, "utf8")) / 1024
);
Expand Down Expand Up @@ -200,7 +200,7 @@ switch (args._[0]) {
for (const asset of Object.keys(assets)) {
const assetPath = outDir + "/" + asset;
mkdirp.sync(dirname(assetPath));
fs.writeFileSync(assetPath, assets[asset]);
fs.writeFileSync(assetPath, assets[asset].source, { mode: assets[asset].permissions });
}

if (!args["--quiet"]) {
Expand Down
43 changes: 32 additions & 11 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const shebangRegEx = require('./utils/shebang');
const { pkgNameRegEx } = require("./utils/get-package-base");
const nccCacheDir = require("./utils/ncc-cache-dir");
const FileCachePlugin = require("webpack/lib/cache/FileCachePlugin");

const nodeBuiltins = new Set([...require("repl")._builtinLibs, "constants", "module", "timers", "console", "_stream_writable", "_stream_readable", "_stream_duplex"]);

Expand All @@ -24,6 +23,9 @@ const hashOf = name => {
.slice(0, 10);
}

const nodeLoader = eval('require(__dirname + "/loaders/node-loader.js")');
const relocateLoader = eval('require(__dirname + "/loaders/relocate-loader.js")');

module.exports = (
entry,
{
Expand All @@ -35,12 +37,18 @@ module.exports = (
watch = false
} = {}
) => {
const shebangMatch = fs.readFileSync(resolve.sync(entry)).toString().match(shebangRegEx);
const resolvedEntry = resolve.sync(entry);
const shebangMatch = fs.readFileSync(resolvedEntry).toString().match(shebangRegEx);
const mfs = new MemoryFS();
const assetNames = Object.create(null);
const assets = Object.create(null);
const resolvePlugins = [];
let tsconfigMatchPath;
const assetState = {
assets: Object.create(null),
assetNames: Object.create(null),
assetPermissions: undefined
};
nodeLoader.setAssetState(assetState);
relocateLoader.setAssetState(assetState);
// add TsconfigPathsPlugin to support `paths` resolution in tsconfig
// we need to catch here because the plugin will
// error if there's no tsconfig in the working directory
Expand Down Expand Up @@ -115,15 +123,13 @@ module.exports = (
{
test: /\.node$/,
use: [{
loader: __dirname + "/loaders/node-loader.js",
options: { assetNames, assets }
loader: __dirname + "/loaders/node-loader.js"
}]
},
{
test: /\.(js|mjs|tsx?)$/,
use: [{
loader: __dirname + "/loaders/relocate-loader.js",
options: { assetNames, assets }
}]
},
{
Expand All @@ -149,12 +155,22 @@ module.exports = (
plugins: [
{
apply(compiler) {
/* compiler.hooks.afterCompile.tap("ncc", compilation => {
compilation.cache.store('/NccPlugin/' + resolvedEntry, null, JSON.stringify(assetState.assetPermissions.permissions), (err) => {
if (err) console.error(err);
});
}); */
compiler.hooks.watchRun.tap("ncc", () => {
if (rebuildHandler)
rebuildHandler();
});
// override "not found" context to try built require first
compiler.hooks.compilation.tap("ncc", compilation => {
assetState.assetPermissions = Object.create(null);
/* compilation.cache.get('/NccPlugin/' + resolvedEntry, null, (err, _assetPermissions) => {
if (err) console.error(err);
assetState.assetPermissions = JSON.parse(_assetPermissions || 'null') || Object.create(null);
}); */
// hack to ensure __webpack_require__ is added to empty context wrapper
compilation.hooks.additionalModuleRuntimeRequirements.tap("ncc", (module, runtimeRequirements) => {
if(module._contextDependencies)
Expand Down Expand Up @@ -272,7 +288,7 @@ module.exports = (

function finalizeHandler () {
const assets = Object.create(null);
getFlatFiles(mfs.data, assets);
getFlatFiles(mfs.data, assets, assetState.assetPermissions);
delete assets[filename];
delete assets[filename + ".map"];
let code = mfs.readFileSync("/index.js", "utf8");
Expand Down Expand Up @@ -309,13 +325,18 @@ module.exports = (
};

// this could be rewritten with actual FS apis / globs, but this is simpler
function getFlatFiles(mfsData, output, curBase = "") {
function getFlatFiles(mfsData, output, assetPermissions, curBase = "") {
for (const path of Object.keys(mfsData)) {
const item = mfsData[path];
const curPath = curBase + "/" + path;
// directory
if (item[""] === true) getFlatFiles(item, output, curPath);
if (item[""] === true) getFlatFiles(item, output, assetPermissions, curPath);
// file
else if (!curPath.endsWith("/")) output[curPath.substr(1)] = mfsData[path];
else if (!curPath.endsWith("/")) {
output[curPath.substr(1)] = {
source: mfsData[path],
permissions: assetPermissions[curPath.substr(1)]
}
}
}
}
16 changes: 12 additions & 4 deletions src/loaders/node-loader.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
const path = require('path');
const { getOptions } = require('loader-utils');
const getUniqueAssetName = require('../utils/dedupe-names');
const sharedlibEmit = require('../utils/sharedlib-emit');
const getPackageBase = require('../utils/get-package-base');
const fs = require('fs');

module.exports = async function (content) {
if (this.cacheable)
this.cacheable();
this.async();

const id = this.resourcePath;
const options = getOptions(this);

const pkgBase = getPackageBase(this.resourcePath) || path.dirname(id);
await sharedlibEmit(pkgBase, this.emitFile);
await sharedlibEmit(pkgBase, assetState, this.emitFile);

const name = getUniqueAssetName(id.substr(pkgBase.length + 1), id, options.assetNames);
const name = getUniqueAssetName(id.substr(pkgBase.length + 1), id, assetState.assetNames);

const permissions = await new Promise((resolve, reject) =>
fs.stat(id, (err, stats) => err ? reject(err) : resolve(stats.mode))
)
assetState.assetPermissions[name] = permissions;
this.emitFile(name, content);

this.callback(null, 'module.exports = __non_webpack_require__("./' + name + '")');
};
module.exports.raw = true;
let assetState;
module.exports.setAssetState = function (state) {
assetState = state;
}
42 changes: 28 additions & 14 deletions src/loaders/relocate-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const evaluate = require('static-eval');
const acorn = require('acorn');
const bindings = require('bindings');
const getUniqueAssetName = require('../utils/dedupe-names');
const { getOptions } = require('loader-utils');
const sharedlibEmit = require('../utils/sharedlib-emit');
const glob = require('glob');
const getPackageBase = require('../utils/get-package-base');
Expand Down Expand Up @@ -210,7 +209,6 @@ function handleWrappers (ast, scope, magicString, len) {

const relocateRegEx = /_\_dirname|_\_filename|require\.main|node-pre-gyp|bindings|define/;


module.exports = function (code) {
if (this.cacheable)
this.cacheable();
Expand All @@ -220,7 +218,6 @@ module.exports = function (code) {
if (id.endsWith('.json') || !code.match(relocateRegEx))
return this.callback(null, code);

const options = getOptions(this);
const emitAsset = (assetPath) => {
// JS assets to support require(assetPath) and not fs-based handling
// NB package.json is ambiguous here...
Expand All @@ -235,25 +232,31 @@ module.exports = function (code) {
outName = assetPath.substr(pkgBase.length);
// If the asset is a ".node" binary, then glob for possible shared
// libraries that should also be included
assetEmissionPromises = assetEmissionPromises.then(sharedlibEmit(pkgBase, this.emitFile));
assetEmissionPromises = assetEmissionPromises.then(sharedlibEmit(pkgBase, assetState, this.emitFile));
}

const name = options.assets[assetPath] ||
(options.assets[assetPath] = getUniqueAssetName(outName, assetPath, options.assetNames));
const name = assetState.assets[assetPath] ||
(assetState.assets[assetPath] = getUniqueAssetName(outName, assetPath, assetState.assetNames));

// console.log('Emitting ' + assetPath + ' for module ' + id);
assetEmissionPromises = assetEmissionPromises.then(async () => {
const source = await new Promise((resolve, reject) =>
fs.readFile(assetPath, (err, source) => err ? reject(err) : resolve(source))
);
const [source, permissions] = await Promise.all([
new Promise((resolve, reject) =>
fs.readFile(assetPath, (err, source) => err ? reject(err) : resolve(source))
),
await new Promise((resolve, reject) =>
fs.stat(assetPath, (err, stats) => err ? reject(err) : resolve(stats.mode))
)
]);
assetState.assetPermissions[name] = permissions;
this.emitFile(name, source);
});
return "__dirname + '/" + JSON.stringify(name).slice(1, -1) + "'";
};
const emitAssetDirectory = (assetDirPath) => {
const dirName = path.basename(assetDirPath);
const name = options.assets[assetDirPath] || (options.assets[assetDirPath] = getUniqueAssetName(dirName, assetDirPath, options.assetNames));
options.assets[assetDirPath] = name;
const name = assetState.assets[assetDirPath] || (assetState.assets[assetDirPath] = getUniqueAssetName(dirName, assetDirPath, assetState.assetNames));
assetState.assets[assetDirPath] = name;

assetEmissionPromises = assetEmissionPromises.then(async () => {
const files = await new Promise((resolve, reject) =>
Expand All @@ -263,9 +266,15 @@ module.exports = function (code) {
// dont emit empty directories or ".js" files
if (file.endsWith('/') || file.endsWith('.js'))
return;
const source = await new Promise((resolve, reject) =>
fs.readFile(file, (err, source) => err ? reject(err) : resolve(source))
);
const [source, permissions] = await Promise.all([
new Promise((resolve, reject) =>
fs.readFile(file, (err, source) => err ? reject(err) : resolve(source))
),
await new Promise((resolve, reject) =>
fs.stat(file, (err, stats) => err ? reject(err) : resolve(stats.mode))
)
]);
assetState.assetPermissions[name + file.substr(assetDirPath.length)] = permissions;
this.emitFile(name + file.substr(assetDirPath.length), source);
}));
});
Expand Down Expand Up @@ -585,3 +594,8 @@ module.exports = function (code) {
this.callback(null, code, map);
});
};

let assetState;
module.exports.setAssetState = function (state) {
assetState = state;
};
14 changes: 10 additions & 4 deletions src/utils/sharedlib-emit.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ switch (os.platform()) {
}

// helper for emitting the associated shared libraries when a binary is emitted
module.exports = async function (pkgPath, emitFile) {
module.exports = async function (pkgPath, assetState, emitFile) {
const files = await new Promise((resolve, reject) =>
glob(pkgPath + sharedlibGlob, { ignore: 'node_modules/**/*' }, (err, files) => err ? reject(err) : resolve(files))
);
await Promise.all(files.map(async file => {
const source = await new Promise((resolve, reject) =>
fs.readFile(file, (err, source) => err ? reject(err) : resolve(source))
);
const [source, permissions] = await Promise.all([
new Promise((resolve, reject) =>
fs.readFile(file, (err, source) => err ? reject(err) : resolve(source))
),
await new Promise((resolve, reject) =>
fs.stat(file, (err, stats) => err ? reject(err) : resolve(stats.mode))
)
]);
assetState.assetPermissions[file.substr(pkgPath.length)] = permissions;
emitFile(file.substr(pkgPath.length), source);
}));
};
4 changes: 2 additions & 2 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ for (const unitTest of fs.readdirSync(`${__dirname}/unit`)) {
// very simple asset validation in unit tests
if (unitTest.startsWith("asset-")) {
expect(Object.keys(assets).length).toBeGreaterThan(0);
expect(assets[Object.keys(assets)[0]] instanceof Buffer);
expect(assets[Object.keys(assets)[0].source] instanceof Buffer);
}
const actual = code
.trim()
Expand Down Expand Up @@ -78,7 +78,7 @@ for (const integrationTest of fs.readdirSync(__dirname + "/integration")) {
for (const asset of Object.keys(assets)) {
const assetPath = tmpDir + asset;
mkdirp.sync(dirname(assetPath));
fs.writeFileSync(assetPath, assets[asset]);
fs.writeFileSync(assetPath, assets[asset].source);
}
fs.writeFileSync(tmpDir + "index.js", code);
fs.writeFileSync(tmpDir + "index.js.map", map);
Expand Down
4 changes: 2 additions & 2 deletions test/unit/amd-disable/output-coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ module.exports =
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(901);
/******/ return __webpack_require__(549);
/******/ })
/************************************************************************/
/******/ ({

/***/ 901:
/***/ 549:
/***/ (function() {

if (typeof define === 'function' && define.amd)
Expand Down
4 changes: 2 additions & 2 deletions test/unit/amd-disable/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ module.exports =
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(947);
/******/ return __webpack_require__(819);
/******/ })
/************************************************************************/
/******/ ({

/***/ 947:
/***/ 819:
/***/ (function() {

if (typeof define === 'function' && define.amd)
Expand Down
4 changes: 2 additions & 2 deletions test/unit/asset-fs-inline-path-enc-es-2/output-coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module.exports =
/******/ runtime(__webpack_require__);
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(954);
/******/ return __webpack_require__(824);
/******/ })
/************************************************************************/
/******/ ({
Expand All @@ -53,7 +53,7 @@ module.exports = require("fs");

/***/ }),

/***/ 954:
/***/ 824:
/***/ (function(__unusedmodule, __webpack_exports__, __webpack_require__) {

"use strict";
Expand Down
Loading

0 comments on commit ae96fbb

Please sign in to comment.