Use yarn's fslib to implement the fake filesystem.
Leverage the work done by yarn, rather than reimplementing from scratch. This means the fake fs is now reading from a zip file in the binary. A few methods need custom implementations as the fake fs is not writable. These were copied and adjusted from the yarn ZipOpenFS.
This commit is contained in:
Generated
+2462
-210
File diff suppressed because it is too large
Load Diff
+7
-3
@@ -14,7 +14,7 @@
|
||||
"lint": "tslint \"{src,plugins,tasks}/**/*.ts\" --fix",
|
||||
"prepare": "npm run lint && npm run build && npm test",
|
||||
"prebuild": "rimraf lib",
|
||||
"build": "tsc --declaration && tsc tasks/*.ts --noEmit --skipLibCheck",
|
||||
"build": "tsc --declaration && tsc tasks/*.ts --noEmit --skipLibCheck && webpack",
|
||||
"postbuild": "ts-node tasks/post-build"
|
||||
},
|
||||
"repository": {
|
||||
@@ -42,6 +42,8 @@
|
||||
"dependencies": {
|
||||
"@calebboyd/semaphore": "^1.3.1",
|
||||
"app-builder": "^7.0.4",
|
||||
"@yarnpkg/fslib": "^2.6.0-rc.8",
|
||||
"@yarnpkg/libzip": "^2.2.2",
|
||||
"archiver": "^5.3.0",
|
||||
"caw": "^2.0.1",
|
||||
"chalk": "^2.4.2",
|
||||
@@ -56,7 +58,8 @@
|
||||
"multistream": "^4.1.0",
|
||||
"ora": "^3.4.0",
|
||||
"resolve-dependencies": "^6.0.8",
|
||||
"rimraf": "^3.0.2"
|
||||
"rimraf": "^3.0.2",
|
||||
"webpack-config-prefabs": "0.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "^5.1.1",
|
||||
@@ -82,6 +85,7 @@
|
||||
"tslint": "^6.1.1",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"tslint-plugin-prettier": "^2.3.0",
|
||||
"typescript": "^4.6.4"
|
||||
"typescript": "^4.6.4",
|
||||
"webpack-cli": "^4.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ export class NexeCompiler {
|
||||
}
|
||||
|
||||
@bound
|
||||
addResource(absoluteFileName: string, content?: Buffer | string | File | null) {
|
||||
addResource(absoluteFileName: string, content?: Buffer | string | File) {
|
||||
return this.bundle.addResource(absoluteFileName, content)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
import { Libzip } from '@yarnpkg/libzip'
|
||||
import {
|
||||
FakeFS,
|
||||
PortablePath,
|
||||
ZipFS,
|
||||
ZipOpenFS,
|
||||
RmdirOptions,
|
||||
OpendirOptions,
|
||||
} from '@yarnpkg/fslib'
|
||||
import { ZipOpenFSOptions } from '@yarnpkg/fslib/lib/ZipOpenFS'
|
||||
import {
|
||||
WriteFileOptions,
|
||||
CreateWriteStreamOptions,
|
||||
BasePortableFakeFS,
|
||||
Dirent,
|
||||
MkdirOptions,
|
||||
Dir,
|
||||
} from '@yarnpkg/fslib/lib/FakeFS'
|
||||
import { CustomDir } from '@yarnpkg/fslib/lib/algorithms/opendir'
|
||||
import { FSPath, ppath, npath, Filename } from '@yarnpkg/fslib/lib/path'
|
||||
// @ts-ignore
|
||||
import { resolve, toNamespacedPath } from 'path'
|
||||
import { WriteStream, constants } from 'fs'
|
||||
|
||||
export type SnapshotZipFSOptions = {
|
||||
baseFs: FakeFS<PortablePath>
|
||||
libzip: Libzip | (() => Libzip)
|
||||
zipFs: ZipFS
|
||||
}
|
||||
|
||||
import { uniqBy } from 'lodash'
|
||||
function uniqReaddir(arr: Array<string | Dirent>) {
|
||||
return uniqBy(arr, (s: string | Dirent) => (typeof s === 'string' ? s : s.name))
|
||||
}
|
||||
|
||||
export class SnapshotZipFS extends BasePortableFakeFS {
|
||||
zipFs: ZipFS
|
||||
baseFs: FakeFS<PortablePath>
|
||||
constructor(opts: SnapshotZipFSOptions) {
|
||||
super()
|
||||
this.zipFs = opts.zipFs
|
||||
this.baseFs = opts.baseFs
|
||||
}
|
||||
private readonly fdMap: Map<number, [ZipFS, number]> = new Map()
|
||||
private nextFd = 3
|
||||
|
||||
async makeCallPromise<T>(
|
||||
p: FSPath<PortablePath>,
|
||||
discard: () => Promise<T>,
|
||||
accept: (zipFS: ZipFS, zipInfo: { subPath: PortablePath }) => Promise<T>,
|
||||
{ requireSubpath = true }: { requireSubpath?: boolean } = {}
|
||||
): Promise<T> {
|
||||
if (typeof p !== 'string') return await discard()
|
||||
|
||||
const normalizedP = this.resolve(p)
|
||||
|
||||
const zipInfo = this.findZip(normalizedP)
|
||||
if (!zipInfo) return await discard()
|
||||
|
||||
if (requireSubpath && zipInfo.subPath === '/') return await discard()
|
||||
|
||||
return await accept(this.zipFs, zipInfo)
|
||||
}
|
||||
|
||||
makeCallSync<T>(
|
||||
p: FSPath<PortablePath>,
|
||||
discard: () => T,
|
||||
accept: (zipFS: ZipFS, zipInfo: { subPath: PortablePath; archivePath: string }) => T,
|
||||
{ requireSubpath = true }: { requireSubpath?: boolean } = {}
|
||||
): T {
|
||||
if (typeof p !== 'string') return discard()
|
||||
|
||||
const normalizedP = this.resolve(p)
|
||||
|
||||
const zipInfo = this.findZip(normalizedP)
|
||||
if (!zipInfo) return discard()
|
||||
|
||||
if (requireSubpath && zipInfo.subPath === '/') return discard()
|
||||
|
||||
return accept(this.zipFs, { archivePath: '', ...zipInfo })
|
||||
}
|
||||
|
||||
async realpathPromise(p: PortablePath) {
|
||||
return await this.makeCallPromise(
|
||||
p,
|
||||
async () => {
|
||||
return await this.baseFs.realpathPromise(p)
|
||||
},
|
||||
async (zipFs, { subPath }) => {
|
||||
return await zipFs.realpathPromise(subPath)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
realpathSync(p: PortablePath) {
|
||||
return this.makeCallSync(
|
||||
p,
|
||||
() => {
|
||||
return this.baseFs.realpathSync(p)
|
||||
},
|
||||
(zipFs, { subPath }) => {
|
||||
return zipFs.realpathSync(subPath)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
findZip(p: PortablePath) {
|
||||
p = this.resolve(p)
|
||||
const pathsToTry = Array.from(
|
||||
new Set([
|
||||
p,
|
||||
resolve('/snapshot', p),
|
||||
ppath.resolve(
|
||||
npath.toPortablePath('/snapshot'),
|
||||
npath.toPortablePath(
|
||||
npath.relative(npath.fromPortablePath(process.cwd()), npath.fromPortablePath(p))
|
||||
)
|
||||
),
|
||||
ppath.resolve(
|
||||
npath.toPortablePath('/snapshot'),
|
||||
npath.toPortablePath(
|
||||
npath.relative(
|
||||
toNamespacedPath(npath.fromPortablePath(process.cwd())),
|
||||
toNamespacedPath(npath.fromPortablePath(p))
|
||||
)
|
||||
)
|
||||
),
|
||||
])
|
||||
)
|
||||
for (const path of pathsToTry) {
|
||||
const portablePath = npath.toPortablePath(path)
|
||||
if (this.zipFs.existsSync(portablePath)) {
|
||||
return {
|
||||
subPath: portablePath,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async copyFilePromise(sourceP: PortablePath, destP: PortablePath, flags: number = 0) {
|
||||
const fallback = async (
|
||||
sourceFs: FakeFS<PortablePath>,
|
||||
sourceP: PortablePath,
|
||||
destFs: FakeFS<PortablePath>,
|
||||
destP: PortablePath
|
||||
) => {
|
||||
if ((flags & constants.COPYFILE_FICLONE_FORCE) !== 0)
|
||||
throw Object.assign(
|
||||
new Error(`EXDEV: cross-device clone not permitted, copyfile '${sourceP}' -> ${destP}'`),
|
||||
{ code: `EXDEV` }
|
||||
)
|
||||
if (flags & constants.COPYFILE_EXCL && (await this.existsPromise(sourceP)))
|
||||
throw Object.assign(
|
||||
new Error(`EEXIST: file already exists, copyfile '${sourceP}' -> '${destP}'`),
|
||||
{ code: `EEXIST` }
|
||||
)
|
||||
|
||||
let content
|
||||
try {
|
||||
content = await sourceFs.readFilePromise(sourceP)
|
||||
} catch (error) {
|
||||
throw Object.assign(
|
||||
new Error(`EINVAL: invalid argument, copyfile '${sourceP}' -> '${destP}'`),
|
||||
{ code: `EINVAL` }
|
||||
)
|
||||
}
|
||||
|
||||
await destFs.writeFilePromise(destP, content)
|
||||
}
|
||||
|
||||
return await this.makeCallPromise(
|
||||
sourceP,
|
||||
async () => {
|
||||
return await this.baseFs.copyFilePromise(sourceP, destP, flags)
|
||||
},
|
||||
async (zipFsS, { subPath: subPathS }) => {
|
||||
return await fallback(zipFsS, subPathS, this.baseFs, destP)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
copyFileSync(sourceP: PortablePath, destP: PortablePath, flags: number = 0) {
|
||||
const fallback = (
|
||||
sourceFs: FakeFS<PortablePath>,
|
||||
sourceP: PortablePath,
|
||||
destFs: FakeFS<PortablePath>,
|
||||
destP: PortablePath
|
||||
) => {
|
||||
if ((flags & constants.COPYFILE_FICLONE_FORCE) !== 0)
|
||||
throw Object.assign(
|
||||
new Error(`EXDEV: cross-device clone not permitted, copyfile '${sourceP}' -> ${destP}'`),
|
||||
{ code: `EXDEV` }
|
||||
)
|
||||
if (flags & constants.COPYFILE_EXCL && this.existsSync(sourceP))
|
||||
throw Object.assign(
|
||||
new Error(`EEXIST: file already exists, copyfile '${sourceP}' -> '${destP}'`),
|
||||
{ code: `EEXIST` }
|
||||
)
|
||||
|
||||
let content
|
||||
try {
|
||||
content = sourceFs.readFileSync(sourceP)
|
||||
} catch (error) {
|
||||
throw Object.assign(
|
||||
new Error(`EINVAL: invalid argument, copyfile '${sourceP}' -> '${destP}'`),
|
||||
{ code: `EINVAL` }
|
||||
)
|
||||
}
|
||||
|
||||
destFs.writeFileSync(destP, content)
|
||||
}
|
||||
|
||||
return this.makeCallSync(
|
||||
sourceP,
|
||||
() => {
|
||||
return this.baseFs.copyFileSync(sourceP, destP, flags)
|
||||
},
|
||||
(zipFsS, { subPath: subPathS }) => {
|
||||
return fallback(zipFsS, subPathS, this.baseFs, destP)
|
||||
}
|
||||
)
|
||||
}
|
||||
async readdirPromise(p: PortablePath): Promise<Array<Filename>>
|
||||
async readdirPromise(
|
||||
p: PortablePath,
|
||||
opts: { withFileTypes: false } | null
|
||||
): Promise<Array<Filename>>
|
||||
async readdirPromise(p: PortablePath, opts: { withFileTypes: true }): Promise<Array<Dirent>>
|
||||
async readdirPromise(
|
||||
p: PortablePath,
|
||||
opts: { withFileTypes: boolean }
|
||||
): Promise<Array<Filename> | Array<Dirent>>
|
||||
async readdirPromise(
|
||||
p: PortablePath,
|
||||
opts?: { withFileTypes?: boolean } | null
|
||||
): Promise<Array<string | Dirent>> {
|
||||
const fallback = async () => {
|
||||
return await this.baseFs.readdirPromise(p, opts as any)
|
||||
}
|
||||
return await this.makeCallPromise(
|
||||
p,
|
||||
fallback,
|
||||
async (zipFs, { subPath }) => {
|
||||
const fallbackPaths: Array<string | Dirent> = await fallback().catch(() => [])
|
||||
return Promise.resolve(
|
||||
uniqReaddir(
|
||||
(fallbackPaths as any[]).concat(await zipFs.readdirPromise(subPath, opts as any))
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
requireSubpath: false,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
readdirSync(p: PortablePath): Array<Filename>
|
||||
readdirSync(p: PortablePath, opts: { withFileTypes: false } | null): Array<Filename>
|
||||
readdirSync(p: PortablePath, opts: { withFileTypes: true }): Array<Dirent>
|
||||
readdirSync(p: PortablePath, opts: { withFileTypes: boolean }): Array<Filename> | Array<Dirent>
|
||||
readdirSync(p: PortablePath, opts?: { withFileTypes?: boolean } | null): Array<string | Dirent> {
|
||||
const fallback = () => {
|
||||
return this.baseFs.readdirSync(p, opts as any)
|
||||
}
|
||||
return this.makeCallSync(
|
||||
p,
|
||||
fallback,
|
||||
(zipFs, { subPath }) => {
|
||||
let fallbackPaths: Array<string | Dirent> = []
|
||||
try {
|
||||
fallbackPaths = fallback()
|
||||
} catch (e) {}
|
||||
return (fallbackPaths as any[]).concat(uniqReaddir(zipFs.readdirSync(subPath, opts as any)))
|
||||
},
|
||||
{
|
||||
requireSubpath: false,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async mkdirPromise(p: PortablePath, opts?: MkdirOptions) {
|
||||
return await this.baseFs.mkdirPromise(p, opts)
|
||||
}
|
||||
|
||||
mkdirSync(p: PortablePath, opts?: MkdirOptions) {
|
||||
return this.baseFs.mkdirSync(p, opts)
|
||||
}
|
||||
|
||||
async rmdirPromise(p: PortablePath, opts?: RmdirOptions) {
|
||||
return await this.baseFs.rmdirPromise(p, opts)
|
||||
}
|
||||
|
||||
rmdirSync(p: PortablePath, opts?: RmdirOptions) {
|
||||
return this.baseFs.rmdirSync(p, opts)
|
||||
}
|
||||
|
||||
async opendirPromise(p: PortablePath, opts?: OpendirOptions) {
|
||||
return Promise.resolve(this.zipFs.opendirSync(p, opts))
|
||||
}
|
||||
|
||||
opendirSync(p: PortablePath, opts?: OpendirOptions) {
|
||||
const zipInfo = this.findZip(p)
|
||||
let zipFsDir: Dir<PortablePath> | null = null
|
||||
if (zipInfo) {
|
||||
zipFsDir = this.zipFs.opendirSync(zipInfo.subPath)
|
||||
}
|
||||
let realFsDir: Dir<PortablePath> | null = null
|
||||
try {
|
||||
realFsDir = this.baseFs.opendirSync(p)
|
||||
} catch (e) {
|
||||
if (!zipFsDir) throw e
|
||||
}
|
||||
const seen = new Set()
|
||||
const nextDirent = () => {
|
||||
const entry = realFsDir?.readSync() || zipFsDir?.readSync()
|
||||
if (entry && !seen.has(entry.name)) {
|
||||
seen.add(entry.name)
|
||||
return entry
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
zipFsDir?.closeSync()
|
||||
realFsDir?.closeSync()
|
||||
}
|
||||
|
||||
return new CustomDir(p, nextDirent, { onClose })
|
||||
}
|
||||
|
||||
accessPromise = ZipOpenFS.prototype.accessPromise
|
||||
accessSync = ZipOpenFS.prototype.accessSync
|
||||
appendFilePromise = ZipOpenFS.prototype.appendFilePromise
|
||||
appendFileSync = ZipOpenFS.prototype.appendFileSync
|
||||
chmodPromise = ZipOpenFS.prototype.chmodPromise
|
||||
chmodSync = ZipOpenFS.prototype.chmodSync
|
||||
fchmodPromise = ZipOpenFS.prototype.fchmodPromise
|
||||
fchmodSync = ZipOpenFS.prototype.fchmodSync
|
||||
chownPromise = ZipOpenFS.prototype.chownPromise
|
||||
chownSync = ZipOpenFS.prototype.chownSync
|
||||
fchownPromise = ZipOpenFS.prototype.fchownPromise
|
||||
fchownSync = ZipOpenFS.prototype.fchownSync
|
||||
closePromise = ZipOpenFS.prototype.closePromise
|
||||
closeSync = ZipOpenFS.prototype.closeSync
|
||||
createReadStream = ZipOpenFS.prototype.createReadStream
|
||||
createWriteStream = ZipOpenFS.prototype.createWriteStream
|
||||
existsPromise = ZipOpenFS.prototype.existsPromise
|
||||
existsSync = ZipOpenFS.prototype.existsSync
|
||||
fstatPromise = ZipOpenFS.prototype.fstatPromise
|
||||
fstatSync = ZipOpenFS.prototype.fstatSync
|
||||
getExtractHint = ZipOpenFS.prototype.getExtractHint
|
||||
getRealPath = ZipOpenFS.prototype.getRealPath
|
||||
linkPromise = ZipOpenFS.prototype.linkPromise
|
||||
linkSync = ZipOpenFS.prototype.linkSync
|
||||
lstatPromise = ZipOpenFS.prototype.lstatPromise
|
||||
lstatSync = ZipOpenFS.prototype.lstatSync
|
||||
openPromise = ZipOpenFS.prototype.openPromise
|
||||
openSync = ZipOpenFS.prototype.openSync
|
||||
readFilePromise = ZipOpenFS.prototype.readFilePromise
|
||||
readFileSync = ZipOpenFS.prototype.readFileSync
|
||||
readlinkPromise = ZipOpenFS.prototype.readlinkPromise
|
||||
readlinkSync = ZipOpenFS.prototype.readlinkSync
|
||||
readPromise = ZipOpenFS.prototype.readPromise
|
||||
readSync = ZipOpenFS.prototype.readSync
|
||||
renamePromise = ZipOpenFS.prototype.renamePromise
|
||||
renameSync = ZipOpenFS.prototype.renameSync
|
||||
resolve = ZipOpenFS.prototype.resolve
|
||||
statPromise = ZipOpenFS.prototype.statPromise
|
||||
statSync = ZipOpenFS.prototype.statSync
|
||||
symlinkPromise = ZipOpenFS.prototype.symlinkPromise
|
||||
symlinkSync = ZipOpenFS.prototype.symlinkSync
|
||||
truncatePromise = ZipOpenFS.prototype.truncatePromise
|
||||
truncateSync = ZipOpenFS.prototype.truncateSync
|
||||
ftruncatePromise = ZipOpenFS.prototype.ftruncatePromise
|
||||
ftruncateSync = ZipOpenFS.prototype.ftruncateSync
|
||||
unlinkPromise = ZipOpenFS.prototype.unlinkPromise
|
||||
unlinkSync = ZipOpenFS.prototype.unlinkSync
|
||||
unwatchFile = ZipOpenFS.prototype.unwatchFile
|
||||
utimesPromise = ZipOpenFS.prototype.utimesPromise
|
||||
utimesSync = ZipOpenFS.prototype.utimesSync
|
||||
watch = ZipOpenFS.prototype.watch
|
||||
watchFile = ZipOpenFS.prototype.watchFile
|
||||
writeFilePromise = ZipOpenFS.prototype.writeFilePromise
|
||||
writeFileSync = ZipOpenFS.prototype.writeFileSync
|
||||
writePromise = ZipOpenFS.prototype.writePromise
|
||||
writeSync = ZipOpenFS.prototype.writeSync
|
||||
|
||||
// @ts-ignore
|
||||
remapFd = ZipOpenFS.prototype.remapFd
|
||||
}
|
||||
+1
-1
@@ -28,7 +28,7 @@ export class Bundle {
|
||||
return Array.from(this.files)
|
||||
}
|
||||
|
||||
public addResource(absoluteFileName: string, content?: File | Buffer | string | null) {
|
||||
public addResource(absoluteFileName: string, content?: File | Buffer | string) {
|
||||
const destPath = makeRelativeToZip(this.cwd, absoluteFileName)
|
||||
if (!this.files.has(destPath)) {
|
||||
if (content == null) {
|
||||
|
||||
+54
-385
@@ -1,329 +1,76 @@
|
||||
import { Stats } from 'fs'
|
||||
import { getLatestGitRelease } from '../releases'
|
||||
import { getLibzipSync } from '@yarnpkg/libzip'
|
||||
import { patchFs, npath, PosixFS, NodeFS, ZipFS } from '@yarnpkg/fslib'
|
||||
import { SnapshotZipFS } from './SnapshotZipFS'
|
||||
import * as assert from 'assert'
|
||||
import * as constants from 'constants'
|
||||
|
||||
export interface NexeBinary {
|
||||
export interface NexeHeader {
|
||||
blobPath: string
|
||||
resources: { [key: string]: number[] }
|
||||
layout: {
|
||||
stat: Stats
|
||||
resourceStart: number
|
||||
contentSize?: number
|
||||
contentStart?: number
|
||||
resourceSize?: number
|
||||
resourceSize: number
|
||||
contentSize: number
|
||||
contentStart: number
|
||||
}
|
||||
}
|
||||
|
||||
let originalFsMethods: any = null
|
||||
let lazyRestoreFs = () => {}
|
||||
const patches = (process as any).nexe.patches || {}
|
||||
delete (process as any).nexe
|
||||
|
||||
// optional Win32 file namespace prefix followed by drive letter and colon
|
||||
const windowsFullPathRegex = /^(\\{2}\?\\)?([a-zA-Z]):/
|
||||
|
||||
const upcaseDriveLetter = (s: string): string =>
|
||||
s.replace(windowsFullPathRegex, (_match, ns, drive) => `${ns || ''}${drive.toUpperCase()}:`)
|
||||
|
||||
function shimFs(binary: NexeBinary, fs: any = require('fs')) {
|
||||
function shimFs(binary: NexeHeader, fs: typeof import('fs') = require('fs')) {
|
||||
if (originalFsMethods !== null) {
|
||||
return
|
||||
}
|
||||
|
||||
originalFsMethods = Object.assign({}, fs)
|
||||
const { blobPath, resources: manifest } = binary,
|
||||
{ resourceStart, stat } = binary.layout,
|
||||
directories: { [key: string]: { [key: string]: boolean } } = {},
|
||||
notAFile = '!@#$%^&*',
|
||||
isWin = process.platform.startsWith('win'),
|
||||
isString = (x: any): x is string => typeof x === 'string' || x instanceof String,
|
||||
noop = () => {},
|
||||
path = require('path'),
|
||||
winPath: (key: string) => string = isWin ? upcaseDriveLetter : (s) => s,
|
||||
baseDir = winPath(path.dirname(process.execPath))
|
||||
|
||||
const realFs: typeof fs = { ...fs }
|
||||
const nodeFs = new NodeFS(realFs)
|
||||
|
||||
const blob = Buffer.allocUnsafe(binary.layout.resourceSize)
|
||||
const blobFd = realFs.openSync(binary.blobPath, 'r')
|
||||
const bytesRead = realFs.readSync(
|
||||
blobFd,
|
||||
blob,
|
||||
0,
|
||||
binary.layout.resourceSize,
|
||||
binary.layout.resourceStart
|
||||
)
|
||||
assert.equal(bytesRead, binary.layout.resourceSize)
|
||||
|
||||
const zipFs = new ZipFS(blob, {
|
||||
libzip: getLibzipSync(),
|
||||
readOnly: true,
|
||||
})
|
||||
const snapshotZipFS = new SnapshotZipFS({
|
||||
libzip: getLibzipSync(),
|
||||
zipFs,
|
||||
baseFs: nodeFs,
|
||||
})
|
||||
const posixSnapshotZipFs = new PosixFS(snapshotZipFS)
|
||||
patchFs(fs, posixSnapshotZipFs)
|
||||
const { readFileSync } = fs
|
||||
|
||||
let log = (_: string) => true
|
||||
let loggedManifest = false
|
||||
if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {
|
||||
log = (text: string) => {
|
||||
setupManifest()
|
||||
if (!loggedManifest) {
|
||||
process.stderr.write('[nexe] - MANIFEST' + JSON.stringify(manifest, null, 4) + '\n')
|
||||
process.stderr.write('[nexe] - DIRECTORIES' + JSON.stringify(directories, null, 4) + '\n')
|
||||
// TODO anything meaningful to log here?
|
||||
loggedManifest = true
|
||||
}
|
||||
return process.stderr.write('[nexe] - ' + text + '\n')
|
||||
}
|
||||
}
|
||||
|
||||
const getKey = function getKey(filepath: string | Buffer | null): string {
|
||||
if (Buffer.isBuffer(filepath)) {
|
||||
filepath = filepath.toString()
|
||||
}
|
||||
if (!isString(filepath)) {
|
||||
return notAFile
|
||||
}
|
||||
let key = path.resolve(baseDir, filepath)
|
||||
|
||||
return winPath(key)
|
||||
}
|
||||
|
||||
const statTime = function () {
|
||||
return {
|
||||
atime: new Date(stat.atime),
|
||||
mtime: new Date(stat.mtime),
|
||||
ctime: new Date(stat.ctime),
|
||||
birthtime: new Date(stat.birthtime),
|
||||
}
|
||||
}
|
||||
|
||||
let BigInt: Function
|
||||
try {
|
||||
BigInt = eval('BigInt')
|
||||
} catch (ignored) {}
|
||||
|
||||
const minBlocks = Math.max(Math.ceil(stat.blksize / 512), 1)
|
||||
const createStat = function (extensions: any, options: any) {
|
||||
const stat = Object.assign(new fs.Stats(), binary.layout.stat, statTime(), extensions)
|
||||
if ('size' in extensions) {
|
||||
//Assume non adjustable allocation size for file system
|
||||
stat.blocks = Math.ceil(stat.size / stat.blksize) * minBlocks
|
||||
}
|
||||
if (options && options.bigint && typeof BigInt !== 'undefined') {
|
||||
for (const k in stat) {
|
||||
if (Object.prototype.hasOwnProperty.call(stat, k) && typeof stat[k] === 'number') {
|
||||
stat[k] = BigInt(stat[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
return stat
|
||||
}
|
||||
|
||||
const ownStat = function (filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (directories[key]) {
|
||||
let mode = binary.layout.stat.mode
|
||||
mode |= fs.constants.S_IFDIR
|
||||
mode &= ~fs.constants.S_IFREG
|
||||
return createStat({ mode, size: 0 }, options)
|
||||
}
|
||||
if (manifest[key]) {
|
||||
return createStat({ size: manifest[key][1] }, options)
|
||||
}
|
||||
}
|
||||
|
||||
const getStat = function (fn: string) {
|
||||
return function stat(filepath: string | Buffer, options: any, callback: any) {
|
||||
let stat: any
|
||||
if (typeof options === 'function') {
|
||||
callback = options
|
||||
stat = ownStat(filepath, null)
|
||||
} else {
|
||||
stat = ownStat(filepath, options)
|
||||
}
|
||||
if (stat) {
|
||||
process.nextTick(() => {
|
||||
callback(null, stat)
|
||||
})
|
||||
} else {
|
||||
return originalFsMethods[fn].apply(fs, arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeLong(filepath: string) {
|
||||
return (path as any)._makeLong && (path as any)._makeLong(filepath)
|
||||
}
|
||||
|
||||
function fileOpts(options: any) {
|
||||
return !options ? {} : isString(options) ? { encoding: options } : options
|
||||
}
|
||||
|
||||
let setupManifest = () => {
|
||||
Object.keys(manifest).forEach((filepath) => {
|
||||
const entry = manifest[filepath]
|
||||
const absolutePath = getKey(filepath)
|
||||
const longPath = makeLong(absolutePath)
|
||||
const normalizedPath = winPath(path.normalize(filepath))
|
||||
|
||||
if (!manifest[absolutePath]) {
|
||||
manifest[absolutePath] = entry
|
||||
}
|
||||
if (longPath && !manifest[longPath]) {
|
||||
manifest[longPath] = entry
|
||||
}
|
||||
if (!manifest[normalizedPath]) {
|
||||
manifest[normalizedPath] = manifest[filepath]
|
||||
}
|
||||
|
||||
let currentDir = path.dirname(absolutePath)
|
||||
let prevDir = absolutePath
|
||||
|
||||
while (currentDir !== prevDir) {
|
||||
directories[currentDir] = directories[currentDir] || {}
|
||||
directories[currentDir][path.basename(prevDir)] = true
|
||||
const longDir = makeLong(currentDir)
|
||||
if (longDir && !directories[longDir]) {
|
||||
directories[longDir] = directories[currentDir]
|
||||
}
|
||||
prevDir = currentDir
|
||||
currentDir = path.dirname(currentDir)
|
||||
}
|
||||
})
|
||||
;(manifest[notAFile] as any) = false
|
||||
;(directories[notAFile] as any) = false
|
||||
setupManifest = noop
|
||||
}
|
||||
|
||||
//naive patches intended to work for most use cases
|
||||
const nfs: any = {
|
||||
existsSync: function existsSync(filepath: string) {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (manifest[key] || directories[key]) {
|
||||
return true
|
||||
}
|
||||
return originalFsMethods.existsSync.apply(fs, arguments)
|
||||
},
|
||||
realpath: function realpath(filepath: any, options: any, cb: any): void {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (isString(filepath) && (manifest[filepath] || manifest[key])) {
|
||||
return process.nextTick(() => cb(null, filepath))
|
||||
}
|
||||
return originalFsMethods.realpath.call(fs, filepath, options, cb)
|
||||
},
|
||||
realpathSync: function realpathSync(filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (manifest[key]) {
|
||||
return filepath
|
||||
}
|
||||
return originalFsMethods.realpathSync.call(fs, filepath, options)
|
||||
},
|
||||
readdir: function readdir(filepath: string | Buffer, options: any, callback: any) {
|
||||
setupManifest()
|
||||
const dir = directories[getKey(filepath)]
|
||||
if (dir) {
|
||||
if ('function' === typeof options) {
|
||||
callback = options
|
||||
options = { encoding: 'utf8' }
|
||||
}
|
||||
process.nextTick(() => callback(null, Object.keys(dir)))
|
||||
} else {
|
||||
return originalFsMethods.readdir.apply(fs, arguments)
|
||||
}
|
||||
},
|
||||
readdirSync: function readdirSync(filepath: string | Buffer, options: any) {
|
||||
setupManifest()
|
||||
const dir = directories[getKey(filepath)]
|
||||
if (dir) {
|
||||
return Object.keys(dir)
|
||||
}
|
||||
return originalFsMethods.readdirSync.apply(fs, arguments)
|
||||
},
|
||||
|
||||
readFile: function readFile(filepath: any, options: any, callback: any) {
|
||||
setupManifest()
|
||||
const entry = manifest[getKey(filepath)]
|
||||
if (!entry) {
|
||||
return originalFsMethods.readFile.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const encoding = fileOpts(options).encoding
|
||||
callback = typeof options === 'function' ? options : callback
|
||||
|
||||
originalFsMethods.open(blobPath, 'r', function (err: Error, fd: number) {
|
||||
if (err) return callback(err, null)
|
||||
originalFsMethods.read(
|
||||
fd,
|
||||
Buffer.alloc(length),
|
||||
0,
|
||||
length,
|
||||
resourceOffset,
|
||||
function (error: Error, bytesRead: number, result: Buffer) {
|
||||
if (error) {
|
||||
return originalFsMethods.close(fd, function () {
|
||||
callback(error, null)
|
||||
})
|
||||
}
|
||||
originalFsMethods.close(fd, function (err: Error) {
|
||||
if (err) {
|
||||
return callback(err, result)
|
||||
}
|
||||
callback(err, encoding ? result.toString(encoding) : result)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
},
|
||||
createReadStream: function createReadStream(filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const entry = manifest[getKey(filepath)]
|
||||
if (!entry) {
|
||||
return originalFsMethods.createReadStream.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const opts = fileOpts(options)
|
||||
|
||||
return originalFsMethods.createReadStream(
|
||||
blobPath,
|
||||
Object.assign({}, opts, {
|
||||
start: resourceOffset,
|
||||
end: resourceOffset + length - 1,
|
||||
})
|
||||
)
|
||||
},
|
||||
readFileSync: function readFileSync(filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const entry = manifest[getKey(filepath)]
|
||||
if (!entry) {
|
||||
return originalFsMethods.readFileSync.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const encoding = fileOpts(options).encoding
|
||||
const fd = originalFsMethods.openSync(process.execPath, 'r')
|
||||
const result = Buffer.alloc(length)
|
||||
originalFsMethods.readSync(fd, result, 0, length, resourceOffset)
|
||||
originalFsMethods.closeSync(fd)
|
||||
return encoding ? result.toString(encoding) : result
|
||||
},
|
||||
statSync: function statSync(filepath: string | Buffer, options: any) {
|
||||
const stat = ownStat(filepath, options)
|
||||
if (stat) {
|
||||
return stat
|
||||
}
|
||||
return originalFsMethods.statSync.apply(fs, arguments)
|
||||
},
|
||||
stat: getStat('stat'),
|
||||
lstat: getStat('lstat'),
|
||||
lstatSync: function statSync(filepath: string | Buffer, options: any) {
|
||||
const stat = ownStat(filepath, options)
|
||||
if (stat) {
|
||||
return stat
|
||||
}
|
||||
return originalFsMethods.lstatSync.apply(fs, arguments)
|
||||
},
|
||||
}
|
||||
if (typeof fs.exists === 'function') {
|
||||
nfs.exists = function (filepath: string, cb: Function) {
|
||||
cb = cb || noop
|
||||
const exists = nfs.existsSync(filepath)
|
||||
process.nextTick(() => cb(exists))
|
||||
}
|
||||
}
|
||||
|
||||
const patches = (process as any).nexe.patches || {}
|
||||
delete (process as any).nexe
|
||||
patches.internalModuleReadFile = function (this: any, original: any, ...args: any[]) {
|
||||
setupManifest()
|
||||
const filepath = getKey(args[0])
|
||||
if (manifest[filepath]) {
|
||||
log('read (hit) ' + filepath)
|
||||
return nfs.readFileSync(filepath, 'utf-8')
|
||||
log(`internalModuleReadFile ${args[0]}`)
|
||||
try {
|
||||
return fs.readFileSync(args[0], 'utf-8')
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
log('read (miss) ' + filepath)
|
||||
return original.call(this, ...args)
|
||||
}
|
||||
let returningArray: boolean
|
||||
patches.internalModuleReadJSON = function (this: any, original: any, ...args: any[]) {
|
||||
@@ -334,99 +81,21 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
|
||||
: res
|
||||
}
|
||||
patches.internalModuleStat = function (this: any, original: any, ...args: any[]) {
|
||||
setupManifest()
|
||||
const filepath = getKey(args[0])
|
||||
if (manifest[filepath]) {
|
||||
log('stat (hit) ' + filepath + ' ' + 0)
|
||||
log(`internalModuleStat ${args[0]}`)
|
||||
try {
|
||||
const stat = fs.statSync(args[0])
|
||||
if (stat.isDirectory()) return 1
|
||||
return 0
|
||||
}
|
||||
if (directories[filepath]) {
|
||||
log('stat dir (hit) ' + filepath + ' ' + 1)
|
||||
return 1
|
||||
}
|
||||
const res = original.call(this, ...args)
|
||||
if (res === 0) {
|
||||
log('stat (miss) ' + filepath + ' ' + res)
|
||||
} else if (res === 1) {
|
||||
log('stat dir (miss) ' + filepath + ' ' + res)
|
||||
} else {
|
||||
log('stat (fail) ' + filepath + ' ' + res)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
if (typeof fs.exists === 'function') {
|
||||
nfs.exists = function (filepath: string, cb: Function) {
|
||||
cb = cb || noop
|
||||
const exists = nfs.existsSync(filepath)
|
||||
if (!exists) {
|
||||
return originalFsMethods.exists(filepath, cb)
|
||||
}
|
||||
process.nextTick(() => cb(exists))
|
||||
} catch (e) {
|
||||
return -constants.ENOENT
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof fs.copyFile === 'function') {
|
||||
nfs.copyFile = function (filepath: string, dest: string, flags: number, callback: Function) {
|
||||
setupManifest()
|
||||
const entry = manifest[getKey(filepath)]
|
||||
if (!entry) {
|
||||
return originalFsMethods.copyFile.apply(fs, arguments)
|
||||
}
|
||||
if (typeof flags === 'function') {
|
||||
callback = flags
|
||||
flags = 0
|
||||
}
|
||||
nfs.readFile(filepath, (err: any, buffer: any) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
originalFsMethods.writeFile(dest, buffer, (err: any) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
callback(null)
|
||||
})
|
||||
})
|
||||
}
|
||||
nfs.copyFileSync = function (filepath: string, dest: string) {
|
||||
setupManifest()
|
||||
const entry = manifest[getKey(filepath)]
|
||||
if (!entry) {
|
||||
return originalFsMethods.copyFileSync.apply(fs, arguments)
|
||||
}
|
||||
return originalFsMethods.writeFileSync(dest, nfs.readFileSync(filepath))
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof fs.realpath.native === 'function') {
|
||||
nfs.realpath.native = function realpathNative(filepath: any, options: any, cb: any): void {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (isString(filepath) && (manifest[filepath] || manifest[key])) {
|
||||
return process.nextTick(() => cb(null, filepath))
|
||||
}
|
||||
return originalFsMethods.realpath.native.call(fs, filepath, options, cb)
|
||||
}
|
||||
nfs.realpathSync.native = function realpathSyncNative(filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (manifest[key]) {
|
||||
return filepath
|
||||
}
|
||||
return originalFsMethods.realpathSync.native.call(fs, filepath, options)
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(fs, nfs)
|
||||
// TODO restore patches in restoreFs
|
||||
|
||||
lazyRestoreFs = () => {
|
||||
Object.keys(nfs).forEach((key) => {
|
||||
fs[key] = originalFsMethods[key]
|
||||
})
|
||||
Object.assign(fs, originalFsMethods)
|
||||
lazyRestoreFs = () => {}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function restoreFs() {
|
||||
|
||||
+11
-4
@@ -5,10 +5,17 @@ export default async function (compiler: NexeCompiler, next: () => Promise<void>
|
||||
await next()
|
||||
compiler.shims.push(
|
||||
wrap(
|
||||
'' +
|
||||
'{{ file("lib/fs/patch.js") }}' +
|
||||
'\nshimFs(process.__nexe)' +
|
||||
`\n${compiler.options.fs ? '' : 'restoreFs()'}`
|
||||
[
|
||||
'process.__nexe = {};',
|
||||
'const fsPatcher = (function() {',
|
||||
'const module = {exports: {}};',
|
||||
'const exports = module.exports;',
|
||||
'{{file("lib/fs/patch.bundle.js")}}',
|
||||
'return module.exports;',
|
||||
'})()',
|
||||
'fsPatcher.shimFs(process.__nexe);',
|
||||
compiler.options.fs ? '' : 'restoreFs();',
|
||||
].join('\n')
|
||||
//TODO support only restoring specific methods
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import * as wcp from 'webpack-config-prefabs'
|
||||
export = wcp.nodeLibrary(module, {
|
||||
enableTypescript: true,
|
||||
entry: './src/fs/patch.ts',
|
||||
minimize: false,
|
||||
outputFilepath: './lib/fs/patch.bundle.js',
|
||||
});
|
||||
Reference in New Issue
Block a user