Skip to content

Commit

Permalink
first
Browse files Browse the repository at this point in the history
  • Loading branch information
toyobayashi committed Nov 18, 2018
0 parents commit 7082ca6
Show file tree
Hide file tree
Showing 27 changed files with 467 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "standard"
}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
.DS_Store
npm-debug.log
*~
*.swp
11 changes: 11 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.npmignore
*.coffee
.DS_Store
npm-debug.log
spec/
tasks/
bin/node
.travis.yml
.pairs
.vscode/
.eslintrc.json
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动程序",
"program": "${workspaceFolder}\\spec\\index.js"
}
]
}
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# asar-node

Enable require('./path/to/any-node-project.asar') & require('./path/to/any-node-project.asar/any/file') in your nodejs app.

`asar-node` can not be used in Electron.

Or just run `asar-node ./path/to/any-node-project.asar`

## Usage

Exists `./path/to/any-node-project.asar`

``` bash
$ asar-node ./path/to/any-node-project # OK!
$ asar-node ./path/to/any-node-project.asar # OK!

$ asar-node ./path/to/any-node-project.asar/any/file # OK!
$ asar-node ./path/to/any-node-project.asar/any/file.js # OK!
$ asar-node ./path/to/any-node-project.asar/any/file.json # OK!
$ asar-node ./path/to/any-node-project.asar/any/file.node # OK!
```

Or

```js
require('asar-node')

require('./path/to/any-node-project') // like require a nodejs directory
// or require('./path/to/any-node-project.asar')
require('./path/to/any-node-project.asar/any/file')
```

If require a asar file, make sure there is `package.json` and `main` field or `index.js` in the asar root.

**Note: Only these fs api functions are available and you should use absolute path. Also `child_process` api is not supported in asar file.**

* fs.readFileSync()
* fs.createReadStream()
* fs.statSync()
* fs.existsSync()
* fs.realpathSync()
3 changes: 3 additions & 0 deletions bin/asar-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

require('child_process').spawnSync('node', process.argv.slice(1).unshift('--require=asar-node'), { stdio: 'inherit' })
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
if (process.versions.electron) {
throw new Error('asar-node can not be used in Electron.')
}

require('./lib/module.js')
138 changes: 138 additions & 0 deletions lib/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
const fs = process.versions.electron ? require('original-fs') : require('fs')
const asar = require('asar')
const path = require('path')
const pickle = require('chromium-pickle-js')
const { splitPath } = require('./util.js')

let nextInode = 0
const uid = process.getuid != null ? process.getuid() : 0
const gid = process.getgid != null ? process.getgid() : 0
const fakeTime = new Date()
const asarStatsToFsStats = (stats) => {
const isFile = !stats.files
return {
dev: 1,
ino: ++nextInode,
mode: 33188,
nlink: 1,
uid: uid,
gid: gid,
rdev: 0,
atime: stats.atime || fakeTime,
birthtime: stats.birthtime || fakeTime,
mtime: stats.mtime || fakeTime,
ctime: stats.ctime || fakeTime,
size: stats.size,
isFile: () => isFile,
isDirectory: () => !isFile,
isSymbolicLink: () => false,
isBlockDevice: () => false,
isCharacterDevice: () => false,
isFIFO: () => false,
isSocket: () => false
}
}

// Start overriding fs methods.
const readFileSync = fs.readFileSync
fs.readFileSync = function (p, options) {
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) return readFileSync.apply(this, arguments)

if (!options) {
options = { encoding: null, flag: 'r' }
} else if (typeof options === 'string') {
options = { encoding: options, flag: 'r' }
} else if (typeof options !== 'object') {
throw new TypeError('Bad arguments')
}

let content
try {
content = asar.extractFile(asarPath, filePath.replace(/\\/g, '/'))
} catch (_error) {
throw new Error('ENOENT: no such file or directory, open \'' + p + '\'')
}
if (options.encoding) {
return content.toString(options.encoding)
} else {
return content
}
}

const createReadStream = fs.createReadStream
fs.createReadStream = function (p, options) {
if (!p || (options && options.fd)) return createReadStream.apply(this, arguments)
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) return createReadStream.apply(this, arguments)

const fd = fs.openSync(asarPath, 'r')

const sizeBuf = Buffer.alloc(8)
if (fs.readSync(fd, sizeBuf, 0, 8, null) !== 8) {
throw new Error('Unable to read header size')
}

const sizePickle = pickle.createFromBuffer(sizeBuf)
let headerSize = sizePickle.createIterator().readUInt32()

let stats
try {
stats = asar.statFile(asarPath, filePath)
} catch (_error) {
throw new Error('Not found \'' + p + '\'')
}

let defaultOption = {
fd,
autoClose: true,
start: 8 + headerSize + parseInt(stats.offset, 10),
end: 8 + headerSize + parseInt(stats.offset, 10) + stats.size - 1
}

if (Object.prototype.toString.call(options) === '[object Object]') {
if (typeof options.end === 'number') {
defaultOption.end = defaultOption.start + options.end
delete options.end
}
if (typeof options.start === 'number') {
defaultOption.start += options.start
delete options.start
}
options = Object.assign({}, defaultOption, options)
} else {
options = defaultOption
}

return createReadStream('', options)
}

const statSync = fs.statSync
fs.statSync = function (p) {
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) return statSync.apply(this, arguments)
return asarStatsToFsStats(asar.statFile(asarPath, filePath))
}

const existsSync = fs.existsSync
fs.existsSync = function (p) {
const [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) return existsSync.apply(this, arguments)
try {
asar.statFile(asarPath, filePath)
return true
} catch (_error) {
return false
}
}

const realpathSync = fs.realpathSync
fs.realpathSync = function (p) {
let [isAsar, asarPath, filePath] = splitPath(p)
if (!isAsar) return realpathSync.apply(this, arguments)
const stat = asar.statFile(asarPath, filePath)
if (stat.link) filePath = stat.link
return path.join(realpathSync(asarPath), filePath)
}

module.exports = fs
127 changes: 127 additions & 0 deletions lib/module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
let Module
try {
Module = require('module')
} catch (_error) {
Module = null
}

const path = require('path')
const fs = require('./fs.js')
const { toAbsolute, splitPath } = require('./util.js')

function checkFilename (request, absolutePath) {
if (!path.isAbsolute(absolutePath)) throw new Error('Not absolute path.')
if (path.extname(absolutePath) !== '') {
if (fs.existsSync(absolutePath)) return absolutePath
throw new Error('Cannot find module \'' + request + '\'')
}
if (fs.existsSync(absolutePath + '.js')) return absolutePath + '.js'
if (fs.existsSync(absolutePath + '.json')) return absolutePath + '.json'
if (fs.existsSync(absolutePath + '.node')) return absolutePath + '.node'
throw new Error('Cannot find module \'' + request + '\'')
}

// if (Module && Module._resolveLookupPaths) {
// const oldResolveLookupPaths = Module._resolveLookupPaths
// Module._resolveLookupPaths = function (request, parent, newReturn) {
// const result = oldResolveLookupPaths(request, parent, newReturn)

// const paths = newReturn ? result : result[1]
// let length = paths.length
// for (let i = 0; i < length; i++) {
// if (path.basename(paths[i]) === 'node_modules') {
// paths.splice(i + 1, 0, paths[i] + '.asar')
// length = paths.length
// }
// }

// return result
// }
// }

if (Module && Module._findPath) {
const oldFindPath = Module._findPath
Module._findPath = function (request, paths, isMain) {
const [isAsar, asarPath, filePath] = splitPath(request)
if (!isAsar) return oldFindPath.apply(this, arguments)
if (filePath === '') {
const pkgPath = toAbsolute(path.join(asarPath, 'package.json'))
const indexjs = toAbsolute(path.join(asarPath, 'index.js'))
if (fs.existsSync(pkgPath)) {
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
pkg.main = pkg.main || 'index.js'
const main = toAbsolute(path.join(asarPath, pkg.main))
return checkFilename(request, main)
} else if (fs.existsSync(indexjs)) {
return indexjs
} else {
throw new Error('Cannot find module \'' + request + '\'')
}
}

const absoluteRequest = toAbsolute(request)
return checkFilename(request, absoluteRequest)
}
}

if (Module && Module._resolveFilename) {
const oldResolveFilename = Module._resolveFilename
Module._resolveFilename = function (request, parent, isMain, options) {
if (path.isAbsolute(request) || request.charAt(0) === '.') {
if (parent) {
let paths
if (typeof options === 'object' && options !== null &&
Array.isArray(options.paths)) {
const fakeParent = new Module('', null)

paths = []

for (let i = 0; i < options.paths.length; i++) {
const path = options.paths[i]
fakeParent.paths = Module._nodeModulePaths(path)
const lookupPaths = Module._resolveLookupPaths(request, fakeParent, true)

if (!paths.includes(path)) paths.push(path)

for (let j = 0; j < lookupPaths.length; j++) {
if (!paths.includes(lookupPaths[j])) paths.push(lookupPaths[j])
}
}
} else {
paths = Module._resolveLookupPaths(request, parent, true)
}
const index = parent.filename.lastIndexOf('.asar')
if (index !== -1) {
const absoluteRequest = toAbsolute(path.join(path.extname(parent.filename) === '.asar' ? parent.filename : path.dirname(parent.filename), request))
return checkFilename(request, absoluteRequest)
}
}
}

return oldResolveFilename.apply(this, arguments)
}
}

if (Module && Module._extensions) {
Module._extensions['.asar'] = Module._extensions['.asar'] || function (module, filename) {
const pkgPath = toAbsolute(path.join(filename, 'package.json'))
const indexjs = toAbsolute(path.join(filename, 'index.js'))
if (fs.existsSync(pkgPath)) {
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
pkg.main = pkg.main || 'index.js'
const main = toAbsolute(path.join(filename, pkg.main))
if (path.extname(main) !== '') {
if (fs.existsSync(main)) return Module._extensions[path.extname(main)](module, main)
throw new Error('Cannot find module \'' + filename + '\'')
}
if (fs.existsSync(main + '.js')) return Module._extensions['.js'](module, main + '.js')
if (fs.existsSync(main + '.json')) return Module._extensions['.json'](module, main + '.json')
if (fs.existsSync(main + '.node')) return Module._extensions['.node'](module, main + '.node')
throw new Error('Cannot find module \'' + filename + '\'')
} else if (fs.existsSync(indexjs)) {
return Module._extensions['.js'](module, indexjs)
} else {
throw new Error('Cannot find module \'' + filename + '\'')
}
}
}
17 changes: 17 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const path = require('path')

const mainDir = require.main ? path.dirname(require.main.filename) : process.cwd()

exports.toAbsolute = function (p) {
return path.isAbsolute(p) ? p : path.join(mainDir, p)
}

exports.splitPath = function (p) {
if (typeof p !== 'string') return [false]
if (p.substr(-5) === '.asar') return [true, p, '']
const indexWindows = p.lastIndexOf('.asar\\')
const indexPosix = p.lastIndexOf('.asar/')
if (indexWindows === -1 && indexPosix === -1) return [false]
const index = indexPosix === -1 ? indexWindows : indexPosix
return [true, p.substr(0, index + 5), p.substr(index + 6).replace(/\\/g, '/')]
}
Loading

0 comments on commit 7082ca6

Please sign in to comment.