├── tests ├── resources │ ├── list │ │ ├── assets │ │ │ ├── 1 │ │ │ └── b │ │ ├── README.md │ │ └── index.js │ ├── read │ │ ├── README.md │ │ └── index.js │ ├── stream │ │ ├── README.md │ │ └── index.js │ ├── list.noda │ ├── read.noda │ ├── stream.noda │ ├── read-should-work.js │ ├── list-should-work.js │ └── stream-should-work.js ├── zip-oddities │ ├── symlinks │ │ ├── index.js │ │ └── symlinked.js │ ├── symlinks.noda │ └── should-handle-symlinks.js ├── basic │ ├── index-js │ │ └── index.js │ ├── inner-require │ │ ├── inner.js │ │ └── index.js │ ├── package-json │ │ ├── main.js │ │ └── package.json │ ├── global-archive-prefix │ │ └── index.js │ ├── require-inside-module │ │ ├── index.js │ │ └── node_modules │ │ │ └── parent │ │ │ └── child.js │ ├── fs.noda │ ├── fs │ │ └── index.js │ ├── index-js.noda │ ├── inner-require.noda │ ├── package-json.noda │ ├── nested-archive.noda │ ├── relative-require │ │ └── index.js │ ├── absolute-archive.noda │ ├── absolute-archive │ │ └── index.js │ ├── relative-require.noda │ ├── global-archive-prefix.noda │ ├── require-inside-module.noda │ ├── nested-archive │ │ ├── package-json.noda │ │ ├── index.js │ │ └── npm-debug.log │ ├── should-find-index-js.js │ ├── should-find-package-json.js │ ├── should-allow-inner-require.js │ ├── should-not-allow-nested-archives.js │ ├── should-allow-require-inside-modules.js │ ├── should-skip-global-archive-prefixes.js │ ├── should-allow-builtins.js │ ├── should-not-allow-relative-require-outside.js │ └── should-allow-absolute-archives-require-from-inside-archive.js └── build.sh ├── examples ├── zmq-omdp-0.0.92.noda ├── http-server-0.7.2.noda ├── http-server.js └── zmq-omdp.js ├── .gitignore ├── .npmignore ├── generate-single-binary ├── generate-third-party-main.sh └── main.js ├── package.json ├── README.md ├── lib ├── noda-resource-system.js ├── zip.js └── noda-module-system.js └── SPEC.md /tests/resources/list/assets/1: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/resources/list/assets/b: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/resources/list/README.md: -------------------------------------------------------------------------------- 1 | # read 2 | -------------------------------------------------------------------------------- /tests/resources/read/README.md: -------------------------------------------------------------------------------- 1 | # read 2 | -------------------------------------------------------------------------------- /tests/resources/stream/README.md: -------------------------------------------------------------------------------- 1 | # read 2 | -------------------------------------------------------------------------------- /tests/zip-oddities/symlinks/index.js: -------------------------------------------------------------------------------- 1 | symlinked.js -------------------------------------------------------------------------------- /tests/basic/index-js/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 'index-js'; 2 | -------------------------------------------------------------------------------- /tests/basic/inner-require/inner.js: -------------------------------------------------------------------------------- 1 | module.exports = 'inner-require'; 2 | -------------------------------------------------------------------------------- /tests/basic/package-json/main.js: -------------------------------------------------------------------------------- 1 | module.exports = 'package-json'; 2 | -------------------------------------------------------------------------------- /tests/basic/package-json/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "main.js" 3 | } 4 | -------------------------------------------------------------------------------- /tests/basic/inner-require/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./inner.js'); 2 | -------------------------------------------------------------------------------- /tests/zip-oddities/symlinks/symlinked.js: -------------------------------------------------------------------------------- 1 | module.exports = 'symlinked'; 2 | -------------------------------------------------------------------------------- /tests/basic/global-archive-prefix/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 'global-archive-prefix'; 2 | -------------------------------------------------------------------------------- /tests/basic/require-inside-module/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('parent/child'); 2 | -------------------------------------------------------------------------------- /tests/basic/fs.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/basic/fs.noda -------------------------------------------------------------------------------- /tests/basic/fs/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('fs').readdirSync(process.env.HOME); 2 | -------------------------------------------------------------------------------- /tests/resources/stream/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require.createReadStream('./README.md'); 2 | -------------------------------------------------------------------------------- /tests/basic/require-inside-module/node_modules/parent/child.js: -------------------------------------------------------------------------------- 1 | module.exports = 'not the main' 2 | -------------------------------------------------------------------------------- /tests/basic/index-js.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/basic/index-js.noda -------------------------------------------------------------------------------- /tests/resources/list.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/resources/list.noda -------------------------------------------------------------------------------- /tests/resources/read.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/resources/read.noda -------------------------------------------------------------------------------- /tests/resources/stream.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/resources/stream.noda -------------------------------------------------------------------------------- /examples/zmq-omdp-0.0.92.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/examples/zmq-omdp-0.0.92.noda -------------------------------------------------------------------------------- /tests/basic/inner-require.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/basic/inner-require.noda -------------------------------------------------------------------------------- /tests/basic/package-json.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/basic/package-json.noda -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node 2 | /node_modules 3 | !/node_modules/single-binary-transform 4 | .node 5 | .swp 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /examples/http-server-0.7.2.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/examples/http-server-0.7.2.noda -------------------------------------------------------------------------------- /tests/basic/nested-archive.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/basic/nested-archive.noda -------------------------------------------------------------------------------- /tests/basic/relative-require/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../should-not-allow-relative-require-outside.js'); 2 | -------------------------------------------------------------------------------- /tests/resources/list/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (cb) { 2 | require.listResources('./assets', cb); 3 | } 4 | -------------------------------------------------------------------------------- /tests/zip-oddities/symlinks.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/zip-oddities/symlinks.noda -------------------------------------------------------------------------------- /tests/basic/absolute-archive.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/basic/absolute-archive.noda -------------------------------------------------------------------------------- /tests/basic/absolute-archive/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (abs_path) { 2 | return require(abs_path); 3 | } 4 | -------------------------------------------------------------------------------- /tests/basic/relative-require.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/basic/relative-require.noda -------------------------------------------------------------------------------- /tests/resources/read/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (cb) { 2 | require.readResource('./README.md', cb); 3 | } 4 | -------------------------------------------------------------------------------- /tests/basic/global-archive-prefix.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/basic/global-archive-prefix.noda -------------------------------------------------------------------------------- /tests/basic/require-inside-module.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/basic/require-inside-module.noda -------------------------------------------------------------------------------- /tests/basic/nested-archive/package-json.noda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmeck/noda-loader/HEAD/tests/basic/nested-archive/package-json.noda -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /examples 2 | /tests 3 | npm-debug.log 4 | .DS_Store 5 | *.swp 6 | *.swo 7 | *.bak 8 | *.zip 9 | *.tgz 10 | *.tar.gz 11 | *.noda 12 | 13 | -------------------------------------------------------------------------------- /tests/basic/nested-archive/index.js: -------------------------------------------------------------------------------- 1 | try { 2 | module.exports = require('./package-json.noda'); 3 | } 4 | catch (e) { 5 | module.exports = undefined; 6 | } 7 | -------------------------------------------------------------------------------- /tests/basic/should-find-index-js.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | 5 | assert.equal('index-js', require('./index-js.noda')); 6 | -------------------------------------------------------------------------------- /tests/basic/should-find-package-json.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | 5 | assert.equal('package-json', require('./package-json.noda')); 6 | -------------------------------------------------------------------------------- /tests/zip-oddities/should-handle-symlinks.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | 5 | assert.equal('symlinked', require('./symlinks.noda')); 6 | -------------------------------------------------------------------------------- /tests/basic/should-allow-inner-require.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | 5 | assert.equal('inner-require', require('./inner-require.noda')); 6 | -------------------------------------------------------------------------------- /tests/basic/should-not-allow-nested-archives.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | 5 | assert.equal(undefined, require('./nested-archive.noda')); 6 | -------------------------------------------------------------------------------- /tests/basic/should-allow-require-inside-modules.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | 5 | assert.equal('not the main', require('./require-inside-module.noda')); 6 | -------------------------------------------------------------------------------- /tests/basic/should-skip-global-archive-prefixes.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | 5 | assert.equal('global-archive-prefix', require('./global-archive-prefix.noda')); 6 | -------------------------------------------------------------------------------- /tests/resources/read-should-work.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | require('./read.noda')(function (err, data) { 5 | assert(!err); 6 | assert.equal('# read\n', String(data)); 7 | }); 8 | -------------------------------------------------------------------------------- /examples/http-server.js: -------------------------------------------------------------------------------- 1 | //require('../'); 2 | var server = require('./http-server-0.7.2.noda').createServer(); 3 | server.server.listen(process.env.PORT); 4 | console.log('Server listening on ', server.server.address()); 5 | -------------------------------------------------------------------------------- /tests/basic/should-allow-builtins.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | 5 | var fs = require('fs'); 6 | assert.equal(JSON.stringify(fs.readdirSync(process.env.HOME)), JSON.stringify(require('./fs.noda'))); 7 | -------------------------------------------------------------------------------- /tests/basic/should-not-allow-relative-require-outside.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | 5 | try { 6 | require('./relative-require.noda'); 7 | } 8 | catch (e) { 9 | assert.equal(e.code, 'ENOENT'); 10 | } 11 | -------------------------------------------------------------------------------- /tests/resources/list-should-work.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | require('./list.noda')(function (err, resources) { 5 | assert(!err); 6 | assert.equal(JSON.stringify(['1', 'b']), JSON.stringify(resources)); 7 | }); 8 | -------------------------------------------------------------------------------- /generate-single-binary/generate-third-party-main.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | echo ';(function (_require, _process) {' 3 | PATH="$PATH:../node_modules/.bin" browserify --bare -t single-binary-transform -e main.js --ignore-missing 4 | echo '})(require, process);' 5 | -------------------------------------------------------------------------------- /tests/resources/stream-should-work.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | var data = []; 5 | require('./stream.noda').on('data', function (chunk) { 6 | data.push(chunk); 7 | }).on('end', function () { 8 | assert.equal('# read\n', data.join('')); 9 | }); 10 | -------------------------------------------------------------------------------- /tests/basic/should-allow-absolute-archives-require-from-inside-archive.js: -------------------------------------------------------------------------------- 1 | require('../../') 2 | 3 | var assert = require('assert'); 4 | var path = require('path'); 5 | 6 | var abs_path = path.join(__dirname, 'package-json.noda'); 7 | 8 | assert.equal('package-json', require('./absolute-archive.noda')(abs_path)); 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "noda-loader", 3 | "version": "2.0.0", 4 | "description": "", 5 | "main": "lib/noda-module-system.js", 6 | "scripts": { 7 | "test": "cd tests; sh build.sh" 8 | }, 9 | "author": "bradleymeck", 10 | "license": "ISC", 11 | "dependencies": { 12 | "debug": "^2.1.0", 13 | "has": "^1.0.0", 14 | "pako": "^0.2.5", 15 | "module-system": "^1.0.0" 16 | }, 17 | "devDependencies": { 18 | "resource-shim": "1.x.x", 19 | "browserify": "^6.2.0", 20 | "through": "^2.3.6" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/build.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | cd basic; 4 | # generate .noda files 5 | for dir in $(ls -d */); do 6 | zip -r -y $(basename $dir).noda $dir; 7 | done 8 | 9 | # run .js 10 | for js in $(ls *.js); do 11 | node $js 12 | done 13 | 14 | cd ..; 15 | 16 | cd resources; 17 | # generate .noda files 18 | for dir in $(ls -d */); do 19 | zip -r -y $(basename $dir).noda $dir; 20 | done 21 | 22 | # run .js 23 | for js in $(ls *.js); do 24 | node $js 25 | done 26 | 27 | cd ..; 28 | 29 | cd zip-oddities; 30 | # generate .noda files 31 | for dir in $(ls -d */); do 32 | zip -r -y $(basename $dir).noda $dir; 33 | done 34 | 35 | # run .js 36 | for js in $(ls *.js); do 37 | node $js 38 | done 39 | 40 | cd ..; 41 | 42 | -------------------------------------------------------------------------------- /tests/basic/nested-archive/npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ 'node', '/usr/local/bin/npm', 'test' ] 3 | 2 info using npm@1.4.28 4 | 3 info using node@v0.10.36 5 | 4 error Error: ENOENT, open '/Users/bradleymeck/Documents/noda-loader/tests/basic/nested-archive/package.json' 6 | 5 error If you need help, you may report this *entire* log, 7 | 5 error including the npm and node versions, at: 8 | 5 error 9 | 6 error System Darwin 14.0.0 10 | 7 error command "node" "/usr/local/bin/npm" "test" 11 | 8 error cwd /Users/bradleymeck/Documents/noda-loader/tests/basic/nested-archive 12 | 9 error node -v v0.10.36 13 | 10 error npm -v 1.4.28 14 | 11 error path /Users/bradleymeck/Documents/noda-loader/tests/basic/nested-archive/package.json 15 | 12 error code ENOENT 16 | 13 error errno 34 17 | 14 verbose exit [ 34, true ] 18 | -------------------------------------------------------------------------------- /examples/zmq-omdp.js: -------------------------------------------------------------------------------- 1 | require('../'); 2 | var zmq = require('./zmq-omdp-0.0.92/'); 3 | 4 | var broker = new zmq.Broker('tcp://*:55555'); 5 | broker.start(function () { 6 | 7 | 8 | var worker = new zmq.Worker('tcp://localhost:55555', 'echo'); 9 | 10 | worker.on('error', function(e) { 11 | console.log('ERROR', e); 12 | }); 13 | 14 | worker.on('request', function(inp, rep) { 15 | console.error('GOT REQUEST'); 16 | rep.end({msg:inp}); 17 | }); 18 | 19 | worker.start(); 20 | 21 | var client = new zmq.Client('tcp://localhost:55555'); 22 | 23 | client.on('error', function(e) { 24 | console.log('ERROR', e); 25 | }); 26 | 27 | 28 | client.start(); 29 | 30 | setInterval(function () { 31 | 32 | var msg = new Date()+''; 33 | console.log('SENDING', msg); 34 | 35 | var req = client.request( 36 | 'echo', msg, 37 | function(err, data) { 38 | console.log("PARTIAL", err, data); 39 | }, 40 | function(err, data) { 41 | console.log("END", err, data); 42 | }, { timeout: 60000 } 43 | ); 44 | 45 | }, 5e3); 46 | }); 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | See releases for playing with single binaries or ... do the steps listed below. 2 | 3 | ## Generating a .noda from npm 4 | 5 | ``` 6 | FILE=$(npm pack $mypackage) 7 | tar -xzf $FILE 8 | zip -y -r $mypackage.noda package 9 | ``` 10 | 11 | ## If you get errors in your .noda 12 | 13 | You most likely want to swap your current fs operation resource operations from the spec. 14 | 15 | ## Generating a single bootstrapping binary with .noda support 16 | 17 | If you don't want to compile things, use a release. :). 18 | 19 | Bear with me. 20 | 21 | Checkout node and pull in 2 commits from `github.com:bmeck/node`'s third-party-main branch 22 | 23 | ``` 24 | git clone git@github.com:joyent/node 25 | cd node 26 | git checkout v0.10 27 | git remote add bmeck git@github.com:bmeck/node.git 28 | git fetch bmeck third-party-main 29 | # expose the --third-party-main configure option (was already in the code lol) 30 | git cherry-pick 18a842705a427e00d9b80ee4d39f5573f4565847 31d34180feda7a27aee729421eb68c44b7073585 31 | ``` 32 | 33 | Checkout noda-loader and generate our `_third_party_main.js` for node. 34 | 35 | ``` 36 | git clone git@github.com:bmeck/noda-loader 37 | cd noda-loader 38 | npm i 39 | cd generate-single-binary 40 | sh generate-third-party-main.sh > "$PATH_TO_NODE_REPO"/lib/_third_party_main.js 41 | ``` 42 | 43 | Configure and build node 44 | 45 | ``` 46 | ./configure --third-party-main && make 47 | ``` 48 | 49 | You now have a working node with `.noda` support. 50 | 51 | Concat your `.noda` to the end of the `node` binary you built in order to use it as a bootstrap. 52 | You can check process.mainModule to check if you are the main module. 53 | 54 | ** NOTE ** This will prevent the repl / debugger / etc. unless you invoke those things yourself. 55 | 56 | ``` 57 | cat ./node myapp.noda > myapp 58 | chmod +x myapp 59 | ``` 60 | -------------------------------------------------------------------------------- /lib/noda-resource-system.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var has = require('has'); 3 | var debug = function () {}; 4 | var Readable = require('stream').Readable; 5 | var absolute = function (path) { return normalize(path) == path }; 6 | var join = path.join; 7 | var normalize = path.normalize; 8 | var extname = path.extname; 9 | var dirname = path.dirname; 10 | exports.createNodaResourceSystem = function createNodaResourceSystem(archive_path, archive_directory, prefix, hasPrefix) { 11 | function in_archive(filepath) { 12 | return filepath.indexOf(archive_path) === 0 && filepath[archive_path.length] === path.sep; 13 | } 14 | 15 | function to_archive_path(filepath) { 16 | debug('finding resource %s', filepath); 17 | return in_archive(filepath) ? (hasPrefix ? prefix + path.sep : '') + filepath.substring(archive_path.length + 1).replace(/[\/\\]/g, '/').replace(/^\//,'') : null; 18 | } 19 | return { 20 | resourceExists: function resourceExists(target, cb) { 21 | setImmediate(function () { 22 | cb(resourceExistsSync(target)); 23 | }); 24 | }, 25 | resourceExistsSync: function resourceExistsSync(target) { 26 | var archive_target = to_archive_path(target); 27 | return has(archive_directory, archive_target); 28 | }, 29 | listResourcesSync: function listResources(dir, cb) { 30 | // absolute paths are not allowed 31 | var archive_target = to_archive_path(dir); 32 | if (archive_target == null) { 33 | throw new Error('could not find resource'); 34 | } 35 | else { 36 | if (archive_target.slice(-1) == '/') archive_target = archive_target.slice(0, -1); 37 | var entries = Object.keys(archive_directory).filter(function (entry_path) { 38 | return path.dirname(entry_path) === archive_target; 39 | }).map(path.basename); 40 | if (entries.length === 0 && !(archive_directory[archive_target] && archive_directory[archive_target].isDirectory())) { 41 | throw new Error('could not find resource'); 42 | } 43 | return entries; 44 | } 45 | }, 46 | listResources: function listResources(dir, cb) { 47 | var ret; 48 | try { 49 | ret = this.listResourcesSync(dir); 50 | } 51 | catch (e) { 52 | setImmediate(function () { 53 | cb(e, null); 54 | }); 55 | return; 56 | } 57 | setImmediate(function () { 58 | cb(null, ret); 59 | }); 60 | }, 61 | readResource: function readResource(target, opts, cb) { 62 | if (typeof opts === 'function') { 63 | cb = opts; 64 | opts = null; 65 | } 66 | var content; 67 | try { 68 | content = this.readResourceSync(target, opts); 69 | } 70 | catch (e) { 71 | setImmediate(function () { 72 | cb(e, null); 73 | }); 74 | return; 75 | } 76 | setImmediate(function () { 77 | cb(null, content); 78 | }); 79 | }, 80 | readResourceSync: function (target, opts) { 81 | var archive_target = to_archive_path(target); 82 | if (!archive_target || !has(archive_directory, archive_target)) { 83 | var err = new Error('ENOENT: ' + target); 84 | err.code = 'ENOENT'; 85 | throw err; 86 | } 87 | else { 88 | var buff = archive_directory[archive_target].entrySync().readFileSync(opts); 89 | if (opts) { 90 | return buff.toString(opts); 91 | } 92 | else { 93 | return buff; 94 | } 95 | } 96 | }, 97 | createReadStream: function (target, opts) { 98 | var content = this.readResourceSync(target, opts); 99 | var stream = new Readable(); 100 | var index = 0; 101 | stream._read = function (n) { 102 | if (index === content.length) { 103 | return null; 104 | } 105 | var end = Math.min(index+n, content.length); 106 | index = end; 107 | return content.slice(index, end); 108 | }; 109 | stream.push(content); 110 | return stream; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /generate-single-binary/main.js: -------------------------------------------------------------------------------- 1 | require('../'); 2 | require('resource-shim/polyfill.js')(require); 3 | /** 4 | * All the code below is from node/lib/module.js except for very minor tweaks 5 | */ 6 | function evalScript(name) { 7 | var Module = _require('module'); 8 | var path = _require('path'); 9 | var cwd = process.cwd(); 10 | 11 | var module = new Module(name); 12 | module.filename = path.join(cwd, name); 13 | module.paths = Module._nodeModulePaths(cwd); 14 | var script = process._eval; 15 | if (!Module._contextLoad) { 16 | var body = script; 17 | script = 'global.__filename = ' + JSON.stringify(name) + ';\n' + 18 | 'global.exports = exports;\n' + 19 | 'global.module = module;\n' + 20 | 'global.__dirname = __dirname;\n' + 21 | 'global.require = require;\n' + 22 | 'return require("vm").runInThisContext(' + 23 | JSON.stringify(body) + ', { filename: ' + 24 | JSON.stringify(name) + ' });\n'; 25 | } 26 | var result = module._compile(script, name + '-wrapper'); 27 | if (process._print_eval) console.log(result); 28 | } 29 | var loaded_as_zip = false; 30 | try { 31 | var zip = require('../lib/zip.js').openSync(process.execPath) 32 | var mod = new (_require('module'))(process.execPath, null); 33 | mod.parent = null; 34 | process.mainModule = mod; 35 | process.argv.splice(1, 0, process.execPath); 36 | loaded_as_zip = true; 37 | require('module')._extensions['.noda'](mod, process.execPath); 38 | } 39 | catch (e) { 40 | // something happened during loading the .noda, bail 41 | if (loaded_as_zip) throw e; 42 | if (process.argv[1] == 'debug') { 43 | // Start the debugger agent 44 | var d = _require('_debugger'); 45 | d.start(); 46 | 47 | } else if (process._eval != null) { 48 | // User passed '-e' or '--eval' arguments to Node. 49 | evalScript('[eval]'); 50 | } else if (process.argv[1]) { 51 | // make process.argv[1] into a full path 52 | var path = _require('path'); 53 | process.argv[1] = path.resolve(process.argv[1]); 54 | 55 | // If this is a worker in cluster mode, start up the communication 56 | // channel. 57 | if (process.env.NODE_UNIQUE_ID) { 58 | var cluster = _require('cluster'); 59 | cluster._setupWorker(); 60 | 61 | // Make sure it's not accidentally inherited by child processes. 62 | delete process.env.NODE_UNIQUE_ID; 63 | } 64 | 65 | var Module = _require('module'); 66 | 67 | if (global.v8debug && 68 | process.execArgv.some(function(arg) { 69 | return arg.match(/^--debug-brk(=[0-9]*)?$/); 70 | })) { 71 | 72 | // XXX Fix this terrible hack! 73 | // 74 | // Give the client program a few ticks to connect. 75 | // Otherwise, there's a race condition where `node debug foo.js` 76 | // will not be able to connect in time to catch the first 77 | // breakpoint message on line 1. 78 | // 79 | // A better fix would be to somehow get a message from the 80 | // global.v8debug object about a connection, and runMain when 81 | // that occurs. --isaacs 82 | 83 | var debugTimeout = +process.env.NODE_DEBUG_TIMEOUT || 50; 84 | setTimeout(Module.runMain, debugTimeout); 85 | 86 | } else { 87 | // Main entry point into most programs: 88 | _require('module').runMain(); 89 | } 90 | 91 | } else { 92 | var Module = _require('module'); 93 | 94 | // If -i or --interactive were passed, or stdin is a TTY. 95 | if (process._forceRepl || _require('tty').isatty(0)) { 96 | // REPL 97 | var opts = { 98 | useGlobal: true, 99 | ignoreUndefined: false 100 | }; 101 | if (parseInt(process.env['NODE_NO_READLINE'], 10)) { 102 | opts.terminal = false; 103 | } 104 | if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) { 105 | opts.useColors = false; 106 | } 107 | var repl = Module.requireRepl().start(opts); 108 | repl.on('exit', function() { 109 | process.exit(); 110 | }); 111 | 112 | } else { 113 | // Read all of stdin - execute it. 114 | process.stdin.setEncoding('utf8'); 115 | 116 | var code = ''; 117 | process.stdin.on('data', function(d) { 118 | code += d; 119 | }); 120 | 121 | process.stdin.on('end', function() { 122 | process._eval = code; 123 | evalScript('[stdin]'); 124 | }); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /SPEC.md: -------------------------------------------------------------------------------- 1 | 2 | de Archive Specification 3 | 4 | The Node Archive Specification defines a format and semantics for loading multiple files in [Node.js](//nodejs.org) via a single archive file. These files will be kept in memory only when possible. 5 | 6 | **Note:** Most OSs are unable to load a shared library via memory, this means temporary files will be used. 7 | 8 | ## File extension 9 | 10 | The `.noda` extension is used in order to prevent colisions and represents `NOD`e `A`rchive. 11 | 12 | ## File format 13 | 14 | The `.noda` file format is a standard `.zip` archive with some special expectations. 15 | 16 | 1. The archive is not separated into multiple parts 17 | 2. The archive is structured in the same manner as a `npm` module 18 | 1. If a prefix is found to be on all files in an archive it will be optional when using the archive. 19 | 20 | The choice of the Zip file format relies on several aspects, but having a central directory and the directory being located at the end of the file are the main reasons. 21 | 22 | ## File Semantics 23 | 24 | There are certain semantics that need to be discussed due to using in memory files as well as encapsulation. 25 | 26 | 1. Files within an archive cannot use **relative** paths to require a resource outside of the archive. To do so, turn the path into an absolute one. 27 | 2. Files within an archive cannot be accessed using the `fs` module. See [Resources](#resources). 28 | 3. Archives cannot be required if nested in another archive. 29 | 30 | ## Modified `require` semantics 31 | 32 | Some features of the archive loader modify how `require` works internally. 33 | 34 | 1. Files within an archive cannot be required directly (e.g. `require('./test.noda/internal.js')` is not possible). 35 | 2. Archives cannot be accessed without file extension. 36 | 37 | ## Resources 38 | 39 | While working with in-memory files such as those loaded via archives, problems arrise from not having real OS level files. In order to abstract from this, a concept called "Resources" is used. 40 | 41 | The term "Resource" will describe any data that is not configured on a per-run basis for applications. Generally, resources will be read only. They provide a way to access assets without needing to know if a file was loaded inside an archive. Many use cases are valid resources, but common examples include: 42 | 43 | * Pre-built templates 44 | * Pre-built images 45 | * Files used by libraries without dynamic paths 46 | 47 | ### String[] require.listResources(targetPath) 48 | 49 | This function signature matches `fs.readdir` but works in archives as well. 50 | 51 | ### void require.readResource(targetPath[, String encoding], callback(Error?, Buffer|String)) 52 | 53 | This function signature matches `fs.readFile` but works in archives as well. 54 | 55 | ### ReadableStream require.createReadStream(targetPath) 56 | 57 | This function signature matches `fs.createReadStream` but works in archives as well. 58 | 59 | ## Examples 60 | 61 | Consider the following directory structure. 62 | 63 | ``` 64 | C:\ 65 | my_app\ 66 | node_modules\ 67 | express\ 68 | logging.noda\ 69 | index.js 70 | logic.noda\ 71 | node_modules\ 72 | async\ 73 | package.json -> main:server.js 74 | routes.js -> require(async) 75 | server.js -> require(routes.js, express) 76 | bad.noda 77 | main.js -> require(logging.noda, logic.noda) 78 | ``` 79 | 80 | ### Index detection in an archive 81 | 82 | ``` 83 | C:\my_app\main.js require("./logging.noda") -> C:\my_app\logic.noda\index.js 84 | ``` 85 | 86 | ### Package detection in an archive 87 | 88 | ``` 89 | C:\my_app\main.js require("./logic.noda") -> C:\my_app\logic.noda\package.json -> C:\my_app\logic.noda\server.js 90 | ``` 91 | 92 | ### Internal modules in an archive 93 | 94 | ``` 95 | C:\my_app\logic.noda\server.js require("async") -> C:\my_app\logic.noda\node_modues\async\ 96 | C:\my_app\logic.noda\server.js require("./routes.js") -> C:\my_app\logic.noda\routes.js 97 | ``` 98 | 99 | ### Shared (external) modules outside an archive 100 | 101 | ``` 102 | C:\my_app\logic.noda\server.js require("express") -> C:\my_app\node_modues\express\ 103 | ``` 104 | 105 | ### Absolute modules outside an archive 106 | 107 | ``` 108 | C:\my_app\logic.noda\server.js require("C:\\my_app\\main.js") -> C:\my_app\main.js 109 | ``` 110 | 111 | ### Using a resource 112 | 113 | ``` 114 | C:\my_app\logic.noda\server.js require.readResourceSync("./package.json") -> C:\my_app\logic.noda\package.json's content 115 | ``` 116 | 117 | ### Errors 118 | 119 | #### Modules cannot escape archive 120 | 121 | ``` 122 | C:\my_app\logic.noda\server.js require("../main.js") throws 123 | ``` 124 | 125 | #### Archives are not allowed to be nested 126 | 127 | ``` 128 | C:\my_app\logic.noda\server.js require("./bad.noda") throws 129 | ``` 130 | 131 | #### Filesystem breaking 132 | 133 | See [Resources](#resources) 134 | 135 | ``` 136 | C:\my_app\logic.noda\server.js require("fs").readFileSync("./package.json") throws 137 | ``` 138 | 139 | -------------------------------------------------------------------------------- /lib/zip.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var inflateRawSync = require('zlib').inflateRawSync; 3 | if (!inflateRawSync) { 4 | var pako = require('pako'); 5 | inflateRawSync = function (buf) { 6 | return new Buffer(new Uint8Array(pako.inflateRaw(buf))); 7 | } 8 | } 9 | 10 | var MAX_COMMENT_SIZE = 65535; 11 | var CENTRAL_DIRECTORY_HEADER_SIZE = 22; 12 | var PREF_BUFFER_SIZE = 4096; 13 | 14 | // 15 | // used to make sub-sections of a file 16 | // 17 | function fdUtilSync(fd, start, stop) { 18 | if (typeof start === 'number' && typeof stop !== 'number') { 19 | throw new Error('if you provide a start, you must provide a stop'); 20 | } 21 | //this.start = start; 22 | //this.stop = stop; 23 | this.fstatSync = function () { 24 | var result = require('fs').fstatSync(fd); 25 | if (start === start && stop === stop) { 26 | result.size = stop - start; 27 | } 28 | return result; 29 | }; 30 | start = +start; 31 | stop = +stop; 32 | // NaN checks 33 | if (start !== start) { 34 | start = 0; 35 | } 36 | if (stop !== stop) { 37 | stop = this.fstatSync().size; 38 | } 39 | this.fd = fd; 40 | this.start = start; 41 | this.stop = stop; 42 | this.close = function () { 43 | require('fs').close(fd); 44 | } 45 | this.readSync = function (buff, buff_offset, amount, file_offset) { 46 | file_offset += start; 47 | if (file_offset + amount > stop) { 48 | var err = new Error('EINVAL'); 49 | throw err; 50 | } 51 | var read = 0; 52 | while (read < amount) { 53 | read += require('fs').readSync(fd, buff, buff_offset, amount, file_offset); 54 | } 55 | return read; 56 | }; 57 | this.sub = function (substart, substop) { 58 | var offset = start || 0; 59 | return new fdUtilSync(fd, offset + substart, offset + substop); 60 | }; 61 | this.readStreamSync = function (ondata) { 62 | var needle = start; 63 | while (needle < stop) { 64 | var read_len = stop - needle; 65 | if (read_len > PREF_BUFFER_SIZE) { 66 | read_len = PREF_BUFFER_SIZE; 67 | } 68 | if (needle + read_len > stop) { 69 | read_len = stop - needle; 70 | } 71 | var buff = new Buffer(read_len); 72 | var bytes_read = require('fs').readSync(fd, buff, 0, buff.length, needle); 73 | if (ondata) ondata(buff.slice(0, bytes_read)); 74 | needle += bytes_read; 75 | } 76 | }; 77 | return this; 78 | } 79 | 80 | function Zip(file, header) { 81 | this.file = file; 82 | this.header = header; 83 | return this; 84 | } 85 | module.exports = Zip; 86 | Object.defineProperties(Zip.prototype, { 87 | centralDirectoryEntries: { 88 | get: function () { 89 | return this.header.readUInt16LE(10); 90 | } 91 | }, 92 | centralDirectoryLength: { 93 | get: function () { 94 | return this.header.readUInt32LE(12); 95 | } 96 | }, 97 | centralDirectoryOffset: { 98 | get: function () { 99 | return this.header.readUInt32LE(16); 100 | } 101 | }, 102 | length: { 103 | get: function () { 104 | return this.centralDirectoryLength + this.centralDirectoryOffset + this.header.length; 105 | } 106 | } 107 | }); 108 | Zip.openSync = function (opts) { 109 | if (typeof opts === 'string') { 110 | var fd = require('fs').openSync(opts, 'r'); 111 | return fopenZipSync(new fdUtilSync(fd)); 112 | } 113 | else if (opts.file instanceof fdUtilSync) { 114 | return fopenZipSync(opts.file, cb); 115 | } 116 | else if (typeof opts === 'object' && opts && typeof opts.fd === 'number') { 117 | return fopenZipSync(new fdUtilSync(opts.fd)); 118 | } 119 | throw new Error('invalid arguments'); 120 | } 121 | Zip.prototype.mapEntriesSync = function mapEntriesSync() { 122 | var results = Object.create(null); 123 | this.directoryStreamSync(function (directory_entry) { 124 | results[directory_entry.name] = directory_entry; 125 | }); 126 | return results; 127 | } 128 | 129 | function fopenZipSync(file, cb) { 130 | var stat = file.fstatSync(); 131 | var len = stat.size; 132 | var central_dir_position = 0; 133 | var found = 0; 134 | var buffers = []; 135 | while (true) { 136 | var read_len = CENTRAL_DIRECTORY_HEADER_SIZE + MAX_COMMENT_SIZE - central_dir_position; 137 | if (read_len > PREF_BUFFER_SIZE) { 138 | read_len = PREF_BUFFER_SIZE; 139 | } 140 | var read_pos = len - central_dir_position - read_len; 141 | if (read_pos < 0) { 142 | read_len += read_pos; 143 | read_pos = 0; 144 | } 145 | if (read_len <= 0) { 146 | file.close(); 147 | throw new Error('not a zip'); 148 | } 149 | var buff = new Buffer(read_len); 150 | var bytes_read = file.readSync(buff, 0, read_len, read_pos); 151 | for (var i = bytes_read; i-->0;) { 152 | if (found === 0 && buff[i] === 0x06) {found = 1;} 153 | else if (found === 1 && buff[i] === 0x05) {found = 2;} 154 | else if (found === 2 && buff[i] === 0x4b) {found = 3;} 155 | else if (found === 3 && buff[i] === 0x50) { 156 | found=4; 157 | break; 158 | } 159 | else {found = 0;} 160 | } 161 | central_dir_position += bytes_read-i-1; 162 | if (found === 4) { 163 | buffers.push(buff.slice(i)); 164 | var zip = new Zip(null, Buffer.concat(buffers)); 165 | zip.file = file.sub(len - zip.length, len); 166 | return zip; 167 | } 168 | buffers.push(buff); 169 | } 170 | } 171 | Zip.prototype.directoryStreamSync = function (onEntry) { 172 | var file = this.file; 173 | var needle = this.centralDirectoryOffset; 174 | for (var count = this.centralDirectoryEntries; count-->0;) { 175 | var entry_header_buff = new Buffer(46); 176 | file.readSync(entry_header_buff, 0, 46, needle); 177 | needle += 46; 178 | 179 | var file_name_size = entry_header_buff.readUInt16LE(0x1c); 180 | var extra_field_size = entry_header_buff.readUInt16LE(0x1e); 181 | var comment_size = entry_header_buff.readUInt16LE(0x20); 182 | var variable_size = file_name_size + extra_field_size + comment_size; 183 | var buff = new Buffer(variable_size); 184 | file.readSync(buff, 0, variable_size, needle); 185 | needle += variable_size; 186 | 187 | var file_name_buff = new Buffer(file_name_size); 188 | buff.copy(file_name_buff, 0, 0, file_name_size); 189 | 190 | var extra_field_buff = new Buffer(extra_field_size); 191 | buff.copy(extra_field_buff, 0, file_name_size, file_name_size + extra_field_size); 192 | 193 | var comment_buff = new Buffer(comment_size); 194 | buff.copy(comment_buff, 0, file_name_size + extra_field_size, variable_size); 195 | 196 | var entry = new ZipDirectoryEntry(file, entry_header_buff, file_name_buff, extra_field_buff, comment_buff); 197 | if (onEntry) onEntry(entry); 198 | } 199 | }; 200 | 201 | function ZipDirectoryEntry(file, header, entryName, extraField, comment) { 202 | this.file = file; 203 | this.header = header; 204 | this.name = entryName; 205 | this.extraField = extraField; 206 | this.comment = comment; 207 | } 208 | Object.defineProperties(ZipDirectoryEntry.prototype, { 209 | isSymlink: { 210 | value: function () { 211 | return this.externalData >>> 28 === 0xa; 212 | } 213 | }, 214 | isDirectory: { 215 | value: function () { 216 | return this.externalData >>> 28 === 0x4; 217 | } 218 | }, 219 | externalData: { 220 | get: function () { 221 | return this.header.readUInt32LE(0x26); 222 | } 223 | }, 224 | localHeaderOffset: { 225 | get: function () { 226 | return this.header.readUInt32LE(42); 227 | } 228 | }, 229 | compressedSize: { 230 | get: function () { 231 | return this.header.readUInt32LE(20); 232 | } 233 | }, 234 | compressionMethod: { 235 | get: function () { 236 | return this.header.readUInt16LE(10); 237 | } 238 | } 239 | }); 240 | ZipDirectoryEntry.prototype.entrySync = function () { 241 | var file = this.file; 242 | var localHeaderOffset = this.localHeaderOffset; 243 | var entry_header_buff = new Buffer(0x1e); 244 | file.readSync(entry_header_buff, 0, 0x1e, localHeaderOffset); 245 | 246 | var file_name_size = entry_header_buff.readUInt16LE(0x1a); 247 | var extra_field_size = entry_header_buff.readUInt16LE(0x1c); 248 | var variable_size = file_name_size + extra_field_size; 249 | var buff = new Buffer(variable_size); 250 | 251 | file.readSync(new Buffer(variable_size), 0, variable_size, localHeaderOffset + 0x2e); 252 | 253 | var file_name_buff = new Buffer(file_name_size); 254 | buff.copy(file_name_buff, 0, 0, file_name_size); 255 | 256 | var extra_field_buff = new Buffer(extra_field_size); 257 | buff.copy(extra_field_buff, 0, file_name_size, file_name_size + extra_field_size); 258 | 259 | var _entry = new ZipEntry(this, null, entry_header_buff, file_name_buff, extra_field_buff); 260 | // need to read compressedSize :( 261 | _entry.file = file.sub(localHeaderOffset, localHeaderOffset + 30 + variable_size + _entry.compressedSize); 262 | return _entry; 263 | }; 264 | 265 | function ZipEntry(central, file, header, entryName, extraField) { 266 | this.central = central; 267 | this.file = file; 268 | this.header = header; 269 | this.name = entryName; 270 | this.extraField = extraField; 271 | return this; 272 | } 273 | Object.defineProperties(ZipEntry.prototype, { 274 | signature: { 275 | get: function () { 276 | return this.header.readUInt32LE(0); 277 | } 278 | }, 279 | version: { 280 | get: function () { 281 | return this.header.readUInt16LE(4); 282 | } 283 | }, 284 | flags: { 285 | get: function () { 286 | return this.header.readUInt16LE(6); 287 | } 288 | }, 289 | compressionMethod: { 290 | get: function () { 291 | return this.header.readUInt16LE(8); 292 | } 293 | }, 294 | mtime: { 295 | get: function () { 296 | return this.header.readUInt16LE(10); 297 | } 298 | }, 299 | mdate: { 300 | get: function () { 301 | return this.header.readUInt16LE(12); 302 | } 303 | }, 304 | checksum: { 305 | get: function () { 306 | return this.header.readUInt32LE(14); 307 | } 308 | }, 309 | compressedSize: { 310 | get: function () { 311 | return this.header.readUInt32LE(18); 312 | } 313 | }, 314 | uncompressedSize: { 315 | get: function () { 316 | return this.header.readUInt32LE(22); 317 | } 318 | }, 319 | nameSize: { 320 | get: function () { 321 | return this.header.readUInt16LE(0x1a); 322 | } 323 | }, 324 | extraFieldSize: { 325 | get: function () { 326 | return this.header.readUInt16LE(0x1c); 327 | } 328 | }, 329 | headerSize: { 330 | get: function () { 331 | return this.header.length + this.nameSize + this.extraFieldSize; 332 | } 333 | }, 334 | readFileSync: { 335 | value: function (options) { 336 | var buffers = []; 337 | var _ondata = function (buff) { 338 | buffers.push(buff); 339 | }; 340 | var file = this.file.sub(this.headerSize, this.headerSize + this.central.compressedSize) 341 | file.readStreamSync(_ondata); 342 | if (this.compressionMethod === 8) { 343 | return inflateRawSync(Buffer.concat(buffers))//, options))); 344 | } 345 | else if (this.compressionMethod === 0) { 346 | return Buffer.concat(buffers); 347 | } 348 | throw new Error('Unknown compression'); 349 | } 350 | } 351 | }); 352 | 353 | -------------------------------------------------------------------------------- /lib/noda-module-system.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var Module = require('module'); 3 | var vm = require('vm'); 4 | var debug = function () {}; 5 | var path = require('path'); 6 | var createModuleSystem = require('module-system').createModuleSystem; 7 | var createNodaResourceSystem = require('./noda-resource-system').createNodaResourceSystem; 8 | var has = require('has'); 9 | var zip = require('./zip.js'); 10 | 11 | var absolute = function (path) { return normalize(path) == path }; 12 | var join = path.join; 13 | var normalize = path.normalize; 14 | var extname = path.extname; 15 | var dirname = path.dirname; 16 | 17 | function _load(module, main) { 18 | module.require.main = main; 19 | module.load(); 20 | module.loaded = true; 21 | return module.exports; 22 | } 23 | 24 | var nodaExtension = require('module')._extensions['.noda'] = function (module, filename) { 25 | var modSys = exports.createNodaModuleSystem(filename, filename); 26 | var ret = process.mainModule == module ? modSys.runMain('./') : modSys.run('./'); 27 | module.exports = ret; 28 | } 29 | 30 | exports.createNodaModuleSystem = function createNodaModuleSystem(archive_path, handle_or_path, builtin_overrides) { 31 | var archive = zip.openSync(handle_or_path); 32 | var directory = archive.mapEntriesSync(); 33 | var prefix = null; 34 | // assume prefixed 35 | var hasPrefix = true; 36 | directory = Object.keys(directory).reduce(function (dir, k) { 37 | var key = k.replace(/[\/\\]/g, '/').replace(/^\//, ''); 38 | var _prefix = key.match(/[^\/]*/); 39 | if (hasPrefix) { 40 | if (prefix === null) { 41 | prefix = _prefix[0]; 42 | } 43 | else if (prefix !== _prefix[0]) { 44 | hasPrefix = false; 45 | } 46 | } 47 | dir[key] = directory[k]; 48 | return dir; 49 | }, {}); 50 | var realpath_cache = Object.create(null); 51 | var archiveResourceSystem = createNodaResourceSystem(archive_path, directory, prefix, hasPrefix); 52 | 53 | function in_archive(filepath) { 54 | if (filepath == null) return false; 55 | return filepath.indexOf(archive_path) === 0 && filepath[archive_path.length] === path.sep; 56 | } 57 | 58 | function to_archive_path(filepath) { 59 | debug('converting path to archive', filepath); 60 | return in_archive(filepath) ? (hasPrefix ? prefix + path.sep : '') + filepath.substring(archive_path.length + 1).replace(/[\/\\]/g, '/').replace(/^\//,'') : null; 61 | } 62 | 63 | function realpath(filepath) { 64 | if (!in_archive(filepath)) { 65 | throw new Error('Cannot realpath outside of archive'); 66 | } 67 | return realpath_entry(to_archive_path(filepath)); // fs.realpathSync(filepath); 68 | } 69 | 70 | function fileExists(possibleFile, isAbsolute) { 71 | debug('checking exists? %s in_archive: %s is_absolute: %s', possibleFile, in_archive(possibleFile), isAbsolute) 72 | if (!in_archive(possibleFile)) { 73 | if (isAbsolute) { 74 | fs.statSync(possibleFile); 75 | return true; 76 | } 77 | debug('no'); 78 | var e = new Error('ENOENT: ' + possibleFile); 79 | e.code = 'ENOENT'; 80 | throw e; 81 | } 82 | else { 83 | var exists = has(directory, to_archive_path(possibleFile)); 84 | debug('exists', exists); 85 | return exists; 86 | } 87 | return false; 88 | } 89 | 90 | function readSync(possibleFile) { 91 | var real = realpath(possibleFile) 92 | debug('reading sync %s -> %s', possibleFile, real); 93 | if (!in_archive(possibleFile)) { 94 | var e = new Error('ENOENT: ' + possibleFile); 95 | e.code = 'ENOENT'; 96 | throw e; 97 | } 98 | return directory[real].entrySync().readFileSync(); 99 | } 100 | 101 | function realpath_entry(filepath) { 102 | debug('realpathing entry %s', filepath); 103 | if (has(realpath_cache, filepath)) { 104 | return realpath_cache[filepath]; 105 | } 106 | if (has(directory, filepath)) { 107 | var central_entry = directory[filepath]; 108 | if (central_entry.isSymlink()) { 109 | var rel_path = central_entry.entrySync().readFileSync().toString(); 110 | // if we are an absolute path bail since it is outside of the archive 111 | if (path.resolve(rel_path) === rel_path) { 112 | return null; 113 | } 114 | var next_path = path.normalize(path.join(path.dirname(filepath), rel_path)); 115 | var true_path = realpath_entry(next_path); 116 | debug('realpathed %s -> %s', filepath, true_path); 117 | realpath_cache[filepath] = true_path; 118 | return true_path; 119 | } 120 | debug('realpath %s unecessary', filepath); 121 | return filepath; 122 | } 123 | return null; 124 | } 125 | 126 | 127 | function resolve(module, modulePath) { 128 | debug('resolving %s', modulePath); 129 | if (has(builtins, modulePath)) { 130 | debug('builtin'); 131 | return modulePath; 132 | } 133 | var isFile = /^\.\.?\//.test(modulePath); 134 | var isAbsolute = path.resolve(modulePath) === modulePath; 135 | if (isAbsolute) isFile = true; 136 | var resolvedPath; 137 | var modDir = module == null ? '' : dirname(module.filename); 138 | var refPath; 139 | if (isFile) { 140 | refPath = path.resolve(archive_path, modDir, modulePath); 141 | resolvedPath = resolveFile(refPath, false, isAbsolute); 142 | } 143 | else { 144 | refPath = path.resolve(archive_path, modDir); 145 | resolvedPath = resolveNodeModule(modulePath, refPath); 146 | } 147 | if (resolvedPath) { 148 | debug('found %s -> %s', modulePath, resolvedPath); 149 | if (isAbsolute || ( refPath === archive_path && in_archive(resolvedPath) ) || in_archive(refPath) === in_archive(resolvedPath)) { 150 | return resolvedPath; 151 | } 152 | } 153 | throw new Error('cound not find module '+JSON.stringify(modulePath)); 154 | } 155 | 156 | function resolveFileExtension(possibleFile, isAbsolute) { 157 | var extension, stat, entry; 158 | debug('resolving extensions for %s', possibleFile); 159 | if (fileExists(possibleFile, isAbsolute)) { 160 | return possibleFile; 161 | } 162 | for (extension in extensions) { 163 | var withExtension = possibleFile + extension; 164 | if (fileExists(withExtension, isAbsolute)) { 165 | return withExtension; 166 | } 167 | } 168 | return null; 169 | } 170 | 171 | function resolveFile(possibleFile, skipExact, isAbsolute) { 172 | var stat; 173 | // EXACT 174 | if (!skipExact ) { 175 | var foundFile = resolveFileExtension(possibleFile, isAbsolute); 176 | if (foundFile) { 177 | return foundFile; 178 | } 179 | } 180 | var dir = possibleFile; 181 | var pkg; 182 | possibleFile = path.join(dir, 'package.json'); 183 | try { 184 | debug('Finding package.json'); 185 | if (fileExists(possibleFile)) { 186 | pkg = JSON.parse(readSync(possibleFile).toString()); 187 | // PACKAGE.MAIN? 188 | debug('Finding package.json#main'); 189 | if (pkg.main) { 190 | possibleFile = path.normalize(path.join(dir, pkg.main)); 191 | foundFile = resolveFileExtension(possibleFile); 192 | debug('Resolved %s found: %s', possibleFile, foundFile); 193 | if (foundFile) { 194 | return foundFile; 195 | } 196 | // PACKAGE.MAIN INDEX? 197 | possibleFile = path.join(possibleFile, 'index'); 198 | } 199 | else { 200 | // PACKAGE INDEX? 201 | possibleFile = path.normalize(path.join(dir, 'index')); 202 | } 203 | foundFile = resolveFileExtension(possibleFile); 204 | debug('Resolved %s found: %s', possibleFile, foundFile); 205 | if (foundFile) { 206 | return foundFile; 207 | } 208 | } 209 | } 210 | catch (e) { 211 | if (e.code !== 'ENOENT') throw e; 212 | } 213 | // INDEX? 214 | possibleFile = path.join(dir, 'index'); 215 | foundFile = resolveFileExtension(possibleFile); 216 | debug('Resolved %s found: %s', possibleFile, foundFile); 217 | if (foundFile) { 218 | return foundFile; 219 | } 220 | if (skipExact && pkg) { 221 | var err = new Error('package does not contain a main'); 222 | err.code = 'ENOENT'; 223 | throw err; 224 | } 225 | return null; 226 | } 227 | 228 | function resolveNodeModule(name, modulesFolder) { 229 | var olddir, stat; 230 | do { 231 | if (path.dirname(modulesFolder) != 'node_modules') { 232 | var possiblePackage = path.join(modulesFolder, 'node_modules'); 233 | try { 234 | var modulePath = resolveFile(path.join(modulesFolder, 'node_modules', name)); 235 | if (modulePath) return modulePath; 236 | } 237 | catch (e) { 238 | if (e.code !== 'ENOENT') throw e; 239 | } 240 | } 241 | olddir = modulesFolder; 242 | modulesFolder = path.dirname(modulesFolder); 243 | } while (modulesFolder !== olddir); 244 | return null; 245 | } 246 | 247 | function cached(module, modulePath) { 248 | if (has(builtins, modulePath)) { 249 | debug('cached', modulePath); 250 | return builtins[modulePath]; 251 | } 252 | if (has(cache, modulePath)) { 253 | debug('cached', modulePath); 254 | return cache[modulePath]; 255 | } 256 | var resolvedPath = resolve(module, modulePath); 257 | return builtins[resolvedPath] || cache[resolvedPath] || null; 258 | } 259 | 260 | function load(module, resolvedPath) { 261 | debug('loaded', resolvedPath); 262 | var extension = extname(resolvedPath); 263 | var handler = extensions[extension] || extensions['.js']; 264 | module.require.extensions = extensions; 265 | module.require.cache = cache; 266 | module.require.resourceExistsSync = function (target) { 267 | var isAbsolute = path.resolve(target) == target; 268 | if (isAbsolute) { 269 | if (in_archive(target)) { 270 | return archiveResourceSystem.resourceExistsSync(target); 271 | } 272 | else { 273 | throw new Error('cannot access outside of archive'); 274 | } 275 | } 276 | else { 277 | var resolvedTarget = path.resolve(dirname(resolvedPath), target); 278 | if (!in_archive(resolvedTarget)) { 279 | throw new Error('cannot access relative resource outside of archive'); 280 | } 281 | else { 282 | return archiveResourceSystem.resourceExistsSync(resolvedTarget); 283 | } 284 | } 285 | } 286 | module.require.listResourcesSync = function (target) { 287 | var isAbsolute = path.resolve(target) == target; 288 | if (isAbsolute) { 289 | if (in_archive(target)) { 290 | return archiveResourceSystem.listResourcesSync(target); 291 | } 292 | else { 293 | throw new Error('cannot access outside of archive'); 294 | } 295 | } 296 | else { 297 | var resolvedTarget = path.resolve(dirname(resolvedPath), target); 298 | if (!in_archive(resolvedTarget)) { 299 | throw new Error('cannot access relative resource outside of archive'); 300 | } 301 | else { 302 | return archiveResourceSystem.listResourcesSync(resolvedTarget); 303 | } 304 | } 305 | } 306 | module.require.resourceExists = function (target, cb) { 307 | var isAbsolute = path.resolve(target) == target; 308 | if (isAbsolute) { 309 | if (in_archive(target)) { 310 | archiveResourceSystem.resourceExists(target, cb); 311 | } 312 | else { 313 | cb(new Error('cannot access outside of archive'), null); 314 | } 315 | } 316 | else { 317 | var resolvedTarget = path.resolve(dirname(resolvedPath), target); 318 | if (!in_archive(resolvedTarget)) { 319 | cb(new Error('cannot access relative resource outside of archive'), null); 320 | } 321 | else { 322 | archiveResourceSystem.resourceExists(resolvedTarget, cb); 323 | } 324 | } 325 | } 326 | module.require.listResources = function (target, cb) { 327 | var isAbsolute = path.resolve(target) == target; 328 | if (isAbsolute) { 329 | if (in_archive(target)) { 330 | archiveResourceSystem.listResources(target, cb); 331 | } 332 | else { 333 | cb(new Error('cannot access outside of archive'), null); 334 | } 335 | } 336 | else { 337 | var resolvedTarget = path.resolve(dirname(resolvedPath), target); 338 | if (!in_archive(resolvedTarget)) { 339 | cb(new Error('cannot access relative resource outside of archive'), null); 340 | } 341 | else { 342 | archiveResourceSystem.listResources(resolvedTarget, cb); 343 | } 344 | } 345 | } 346 | module.require.readResourceSync = function (target, opts) { 347 | var resolvedTarget = path.resolve(dirname(resolvedPath), target); 348 | return archiveResourceSystem.readResourceSync(resolvedTarget, opts); 349 | } 350 | module.require.readResource = function (target, opts, cb) { 351 | var resolvedTarget = path.resolve(dirname(resolvedPath), target); 352 | archiveResourceSystem.readResource(resolvedTarget, opts, cb); 353 | } 354 | module.require.createReadStream = function (target, opts) { 355 | var resolvedTarget = path.resolve(dirname(resolvedPath), target); 356 | return archiveResourceSystem.createReadStream(resolvedTarget, opts); 357 | } 358 | module.require.main = process.mainModule; 359 | cache[resolvedPath] = module; 360 | handler(module, resolvedPath); 361 | return module; 362 | } 363 | 364 | var builtins = {}; 365 | for (var k in process.binding('natives')) { 366 | builtins[k] = {exports:require(k),children:[]}; 367 | } 368 | 369 | if (builtin_overrides && typeof builtin_overrides === 'object') { 370 | for (var k in builtin_overrides) { 371 | builtins[k] = {exports: builtin_overrides[k], children: []}; 372 | } 373 | } 374 | var cache = Object.create(null); 375 | 376 | var extensions = { 377 | '.js': function (module, filename) { 378 | var buffer = readSync(filename).toString().replace(/^#!.*/, ''); 379 | //debug('loading', buffer.toString()); 380 | var fn = new Function('__filename', '__dirname', 'require', 'module', 'exports', buffer); 381 | var script = vm.createScript('('+fn+')', filename); 382 | script.runInThisContext()(filename, dirname(filename), module.require, module, module.exports); 383 | }, 384 | '.json': function (module, filename) { 385 | var buffer = readSync(filename); 386 | module.exports = JSON.parse(buffer.toString()); 387 | }, 388 | '.noda': nodaExtension, 389 | '.node': function (module, filename) { 390 | if (in_archive(filename)) { 391 | var tmpdir = process.platform === 'win32' ? process.env.TMP || process.cwd() : process.env.TMPDIR || '/var/tmp'; 392 | var dldir; 393 | var tmpfile; 394 | var rmdir = false; 395 | var unlink = false; 396 | var content = module.require.readResourceSync(filename); 397 | tmpdir = tmpdir+''; 398 | for (var i = 0; ; i++) { 399 | dldir = path.join(tmpdir, i+''); 400 | try { 401 | fs.mkdirSync(dldir); 402 | rmdir = true; 403 | fs.writeFileSync(tmpfile = path.join(dldir, path.basename(filename)), content, {flag: 'wx'}); 404 | debug('made file'); 405 | unlink = true; 406 | process.dlopen(module, tmpfile); 407 | fs.unlinkSync(tmpfile); 408 | fs.rmdirSync(dldir); 409 | break; 410 | } 411 | catch (e) { 412 | debug('unable to create tmp file for native module', e); 413 | if (unlink) fs.unlinkSync(tmpfile); 414 | if (rmdir) fs.rmdirSync(dldir); 415 | if (i > 1000) { 416 | throw new Error('Unable to create temporary file'); 417 | } 418 | } 419 | i++; 420 | } 421 | } 422 | else { 423 | return process.dlopen.apply(this, arguments); 424 | } 425 | } 426 | }; 427 | 428 | var ModuleSystem = createModuleSystem(cached, resolve, load); 429 | builtins.module = { 430 | exports: ModuleSystem, 431 | children: [] 432 | }; 433 | ModuleSystem._extensions = extensions; 434 | ModuleSystem.Module = ModuleSystem; 435 | ModuleSystem.wrap = Module.wrap; 436 | ModuleSystem.run = function (_path) { 437 | var resolvedPath = resolveFile(path.join(archive_path, _path), true); 438 | debug('.noda main found as %s', resolvedPath); 439 | var module = new ModuleSystem(resolvedPath, null); 440 | module.parent = null; 441 | if (!process.mainModule) process.mainModule = module; 442 | return _load(module, module); 443 | } 444 | ModuleSystem._resolveFilename = function (request, parent) { 445 | return resolve(parent, request); 446 | } 447 | ModuleSystem._nodeModulePaths = Module._nodeModulePaths; 448 | ModuleSystem.runMain = function (_path) { 449 | var resolvedPath = resolveFile(path.join(archive_path, _path), true); 450 | debug('.noda main found as %s', resolvedPath); 451 | var module = new ModuleSystem(resolvedPath, null); 452 | module.parent = null; 453 | process.mainModule = module; 454 | return _load(module, module); 455 | } 456 | return ModuleSystem; 457 | } 458 | --------------------------------------------------------------------------------