Compare commits

..

4 Commits

Author SHA1 Message Date
oxmc c48b40b14e Update src/steps/download.ts
Co-authored-by: Jared Allard <jaredallard@users.noreply.github.com>
2023-11-16 22:52:15 -08:00
oxmc 1267f5d41d add spacer 2023-11-14 16:03:27 -08:00
bruce-one 2b4abb1b13 Add support for node v20. (#1063) 2023-11-12 08:00:56 -06:00
bruce-one ca657b5b06 feat: use yarn fslib/zip for fs shimming (#1048)
* refactor: compile with esmoudle interop

* Rigidify code replacement by using Lodash templates.

* Generate zip file bundle.

* 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.

* Add test suite using the built artefact.

Smoke tests the different fs methods.

Not perfect in any world, but better than nothing.

* Fix tsc on Windows - globbing doesn't work there.

There's probably a cleaner way to do this...

* Bump version.

* Dep bumps.

* fixup 9d57b32: make fs patch code clearer.

* fixup 3771c720: simplify test to just use require directly.

---------

Co-authored-by: calebboyd <caleb.boyd@gmail.com>
2023-08-20 14:43:06 -05:00
11 changed files with 634 additions and 552 deletions
+486 -442
View File
File diff suppressed because it is too large Load Diff
+17 -19
View File
@@ -44,21 +44,22 @@
] ]
}, },
"dependencies": { "dependencies": {
"@yarnpkg/fslib": "^2.6.0-rc.8", "@calebboyd/semaphore": "^1.3.1",
"@yarnpkg/libzip": "^2.2.2", "@yarnpkg/fslib": "^3.0.0-rc.43",
"@yarnpkg/libzip": "^3.0.0-rc.43",
"app-builder": "^7.0.4", "app-builder": "^7.0.4",
"archiver": "^5.3.1", "archiver": "^5.3.1",
"caw": "^2.0.1", "caw": "^2.0.1",
"chalk": "^2.4.2", "chalk": "^2.4.2",
"download": "^8.0.0", "download": "^8.0.0",
"globby": "^11.0.2", "globby": "^11.0.2",
"got": "^11.8.2", "got": "^12.6.0",
"meriyah": "^4.3.3", "meriyah": "^4.3.5",
"minimist": "^1.2.6", "minimist": "^1.2.8",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"multistream": "^4.1.0", "multistream": "^4.1.0",
"ora": "^3.4.0", "ora": "^3.4.0",
"resolve-dependencies": "^6.0.8", "resolve-dependencies": "^6.0.9",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"run-script-os": "^1.1.6", "run-script-os": "^1.1.6",
"webpack-config-prefabs": "0.0.5" "webpack-config-prefabs": "0.0.5"
@@ -66,29 +67,26 @@
"devDependencies": { "devDependencies": {
"@types/archiver": "^5.3.2", "@types/archiver": "^5.3.2",
"@types/chai": "^4", "@types/chai": "^4",
"@types/download": "^6", "@types/download": "^8",
"@types/globby": "^9", "@types/globby": "^9",
"@types/got": "^9.6.12", "@types/lodash": "^4.14.192",
"@types/highland": "^2.12.14",
"@types/lodash": "^4.14.173",
"@types/minimist": "^1.2.2", "@types/minimist": "^1.2.2",
"@types/mkdirp": "^1.0.2", "@types/mkdirp": "^1.0.2",
"@types/mocha": "^9.0.0", "@types/mocha": "^10.0.1",
"@types/multistream": "^4.1.0", "@types/multistream": "^4.1.0",
"@types/node": "^16.10.2",
"@types/ora": "^3.2.0", "@types/ora": "^3.2.0",
"@types/rimraf": "3.0.2", "@types/rimraf": "3.0.2",
"@types/semver": "^7.3.8", "@types/semver": "^7.3.13",
"chai": "^4.3.6", "chai": "^4.3.7",
"execa": "^5.1.1", "execa": "^5.1.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mocha": "^10.0.0", "mocha": "^10.2.0",
"prettier": "^2.6.2", "prettier": "^2.8.7",
"ts-node": "^10.7.0", "ts-node": "^10.9.1",
"tslint": "^6.1.1", "tslint": "^6.1.1",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.3.0", "tslint-plugin-prettier": "^2.3.0",
"typescript": "^4.6.4", "typescript": "^5.0.3",
"webpack-cli": "^4.8.0" "webpack-cli": "^5.0.1"
} }
} }
+13 -7
View File
@@ -298,13 +298,19 @@ export class NexeCompiler {
const sentinel = Buffer.from('<nexe~~sentinel>') const sentinel = Buffer.from('<nexe~~sentinel>')
let vfsSize = 0 let vfsSize = 0
const streams = [binary, toStream(launchCode), this.bundle.toStream().pipe(new Transform({ const streams = [
transform: (chunk, _, cb) => { binary,
vfsSize || this.bundle.finalize() toStream(launchCode),
chunk && (vfsSize += chunk.length) this.bundle.toStream().pipe(
cb(null, chunk) new Transform({
}, transform: (chunk, _, cb) => {
}))] vfsSize || this.bundle.finalize()
chunk && (vfsSize += chunk.length)
cb(null, chunk)
},
})
),
]
let done = false let done = false
return new MultiStream((cb) => { return new MultiStream((cb) => {
+40 -28
View File
@@ -2,14 +2,19 @@ import { Libzip } from '@yarnpkg/libzip'
import { import {
FakeFS, FakeFS,
PortablePath, PortablePath,
ZipFS,
ZipOpenFS,
RmdirOptions, RmdirOptions,
OpendirOptions, OpendirOptions,
BasePortableFakeFS,
Dirent,
Dir,
MkdirOptions,
FSPath,
ppath,
npath,
Filename,
CustomDir,
} from '@yarnpkg/fslib' } from '@yarnpkg/fslib'
import { BasePortableFakeFS, Dirent, MkdirOptions, Dir } from '@yarnpkg/fslib/lib/FakeFS' import { ZipFS, ZipOpenFS } from '@yarnpkg/libzip'
import { CustomDir } from '@yarnpkg/fslib/lib/algorithms/opendir'
import { FSPath, ppath, npath, Filename } from '@yarnpkg/fslib/lib/path'
import { resolve, toNamespacedPath } from 'path' import { resolve, toNamespacedPath } from 'path'
import { constants } from 'fs' import { constants } from 'fs'
@@ -17,10 +22,17 @@ export type SnapshotZipFSOptions = {
baseFs: FakeFS<PortablePath> baseFs: FakeFS<PortablePath>
libzip: Libzip | (() => Libzip) libzip: Libzip | (() => Libzip)
zipFs: ZipFS zipFs: ZipFS
root: string
} }
const uniqBy = (arr: Array<string | Dirent>, pick: (...arg: any[]) => any) => { const uniqBy = (arr: Array<string | Dirent>, pick: (...arg: any[]) => any) => {
return arr.filter((x, i, self) => i === self.findIndex((y) => pick(x) === pick(y))) const seen = new Set()
return arr.filter((x) => {
const key = pick(x)
if (seen.has(key)) return false
seen.add(key)
return true
})
} }
function uniqReaddir(arr: Array<string | Dirent>) { function uniqReaddir(arr: Array<string | Dirent>) {
return uniqBy(arr, (s: string | Dirent) => (typeof s === 'string' ? s : s.name)) return uniqBy(arr, (s: string | Dirent) => (typeof s === 'string' ? s : s.name))
@@ -29,10 +41,14 @@ function uniqReaddir(arr: Array<string | Dirent>) {
export class SnapshotZipFS extends BasePortableFakeFS { export class SnapshotZipFS extends BasePortableFakeFS {
zipFs: ZipFS zipFs: ZipFS
baseFs: FakeFS<PortablePath> baseFs: FakeFS<PortablePath>
root: string
magic: number
constructor(opts: SnapshotZipFSOptions) { constructor(opts: SnapshotZipFSOptions) {
super() super()
this.zipFs = opts.zipFs this.zipFs = opts.zipFs
this.baseFs = opts.baseFs this.baseFs = opts.baseFs
this.root = opts.root
this.magic = 0x2a << 24
} }
private readonly fdMap: Map<number, [ZipFS, number]> = new Map() private readonly fdMap: Map<number, [ZipFS, number]> = new Map()
private nextFd = 3 private nextFd = 3
@@ -74,46 +90,42 @@ export class SnapshotZipFS extends BasePortableFakeFS {
} }
async realpathPromise(p: PortablePath) { async realpathPromise(p: PortablePath) {
return await this.makeCallPromise( return await this.realpathSync(p)
p,
async () => {
return await this.baseFs.realpathPromise(p)
},
async (zipFs, { subPath }) => {
return await zipFs.realpathPromise(subPath)
}
)
} }
realpathSync(p: PortablePath) { realpathSync(p: PortablePath) {
return this.makeCallSync( return this.makeCallSync(
p, p,
() => { () => this.baseFs.realpathSync(p),
return this.baseFs.realpathSync(p)
},
(zipFs, { subPath }) => { (zipFs, { subPath }) => {
return zipFs.realpathSync(subPath) if (zipFs.lstatSync(subPath).isSymbolicLink()) {
return zipFs.realpathSync(subPath)
} else {
// return the original path in case it wasn't under /snapshot, e.g. if it was for a node module - otherwise the node module parent path is the wrong one (and other things resolve relative to that)
return p
}
} }
) )
} }
findZip(p: PortablePath) { findZip(p: PortablePath) {
p = this.resolve(p) p = this.resolve(p)
const snapshotPP = npath.toPortablePath('/snapshot')
const pathsToTry = Array.from( const pathsToTry = Array.from(
new Set([ new Set([
p, p,
resolve('/snapshot', p), resolve('/snapshot', p),
ppath.resolve( ppath.resolve(
npath.toPortablePath('/snapshot'), snapshotPP,
npath.toPortablePath( npath.toPortablePath(
npath.relative(npath.fromPortablePath(process.cwd()), npath.fromPortablePath(p)) npath.relative(npath.fromPortablePath(this.root), npath.fromPortablePath(p))
) )
), ),
ppath.resolve( ppath.resolve(
npath.toPortablePath('/snapshot'), snapshotPP,
npath.toPortablePath( npath.toPortablePath(
npath.relative( npath.relative(
toNamespacedPath(npath.fromPortablePath(process.cwd())), toNamespacedPath(npath.fromPortablePath(this.root)),
toNamespacedPath(npath.fromPortablePath(p)) toNamespacedPath(npath.fromPortablePath(p))
) )
) )
@@ -236,9 +248,7 @@ export class SnapshotZipFS extends BasePortableFakeFS {
async (zipFs, { subPath }) => { async (zipFs, { subPath }) => {
const fallbackPaths: Array<string | Dirent> = await fallback().catch(() => []) const fallbackPaths: Array<string | Dirent> = await fallback().catch(() => [])
return Promise.resolve( return Promise.resolve(
uniqReaddir( uniqReaddir(fallbackPaths.concat(await zipFs.readdirPromise(subPath, opts as any)))
(fallbackPaths as any[]).concat(await zipFs.readdirPromise(subPath, opts as any))
)
) )
}, },
{ {
@@ -263,7 +273,7 @@ export class SnapshotZipFS extends BasePortableFakeFS {
try { try {
fallbackPaths = fallback() fallbackPaths = fallback()
} catch (e) {} } catch (e) {}
return (fallbackPaths as any[]).concat(uniqReaddir(zipFs.readdirSync(subPath, opts as any))) return fallbackPaths.concat(uniqReaddir(zipFs.readdirSync(subPath, opts as any)))
}, },
{ {
requireSubpath: false, requireSubpath: false,
@@ -288,7 +298,7 @@ export class SnapshotZipFS extends BasePortableFakeFS {
} }
async opendirPromise(p: PortablePath, opts?: OpendirOptions) { async opendirPromise(p: PortablePath, opts?: OpendirOptions) {
return Promise.resolve(this.zipFs.opendirSync(p, opts)) return this.opendirSync(p, opts)
} }
opendirSync(p: PortablePath, opts?: OpendirOptions) { opendirSync(p: PortablePath, opts?: OpendirOptions) {
@@ -371,6 +381,8 @@ export class SnapshotZipFS extends BasePortableFakeFS {
unwatchFile = ZipOpenFS.prototype.unwatchFile unwatchFile = ZipOpenFS.prototype.unwatchFile
utimesPromise = ZipOpenFS.prototype.utimesPromise utimesPromise = ZipOpenFS.prototype.utimesPromise
utimesSync = ZipOpenFS.prototype.utimesSync utimesSync = ZipOpenFS.prototype.utimesSync
lutimesPromise = ZipOpenFS.prototype.utimesPromise
lutimesSync = ZipOpenFS.prototype.utimesSync
watch = ZipOpenFS.prototype.watch watch = ZipOpenFS.prototype.watch
watchFile = ZipOpenFS.prototype.watchFile watchFile = ZipOpenFS.prototype.watchFile
writeFilePromise = ZipOpenFS.prototype.writeFilePromise writeFilePromise = ZipOpenFS.prototype.writeFilePromise
+27 -20
View File
@@ -1,8 +1,9 @@
import { getLibzipSync } from '@yarnpkg/libzip' import { ZipFS, getLibzipSync } from '@yarnpkg/libzip'
import { patchFs, npath, PosixFS, NodeFS, ZipFS } from '@yarnpkg/fslib' import { patchFs, npath, PosixFS, NodeFS } from '@yarnpkg/fslib'
import { SnapshotZipFS } from './SnapshotZipFS' import { SnapshotZipFS } from './SnapshotZipFS'
import * as assert from 'assert' import * as assert from 'assert'
import * as constants from 'constants' import * as constants from 'constants'
import { dirname } from 'path'
export interface NexeHeader { export interface NexeHeader {
blobPath: string blobPath: string
@@ -17,6 +18,7 @@ export interface NexeHeader {
let originalFsMethods: any = null let originalFsMethods: any = null
let lazyRestoreFs = () => {} let lazyRestoreFs = () => {}
const patches = (process as any).nexe.patches || {} const patches = (process as any).nexe.patches || {}
const originalPatches = { ...patches }
delete (process as any).nexe delete (process as any).nexe
function shimFs(binary: NexeHeader, fs: typeof import('fs') = require('fs')) { function shimFs(binary: NexeHeader, fs: typeof import('fs') = require('fs')) {
@@ -40,60 +42,65 @@ function shimFs(binary: NexeHeader, fs: typeof import('fs') = require('fs')) {
) )
assert.equal(bytesRead, binary.layout.resourceSize) assert.equal(bytesRead, binary.layout.resourceSize)
const zipFs = new ZipFS(blob, { const zipFs = new ZipFS(blob, { readOnly: true })
libzip: getLibzipSync(),
readOnly: true,
})
const snapshotZipFS = new SnapshotZipFS({ const snapshotZipFS = new SnapshotZipFS({
libzip: getLibzipSync(), libzip: getLibzipSync(),
zipFs, zipFs,
baseFs: nodeFs, baseFs: nodeFs,
root: dirname(process.argv[0]),
}) })
const posixSnapshotZipFs = new PosixFS(snapshotZipFS) const posixSnapshotZipFs = new PosixFS(snapshotZipFS)
patchFs(fs, posixSnapshotZipFs) patchFs(fs, posixSnapshotZipFs)
const { readFileSync } = fs
let log = (_: string) => true let log = (_: string) => true
let loggedManifest = false
if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) { if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {
process.stderr.write(
// @ts-ignore
`[nexe] - FILES ${JSON.stringify(Array.from(zipFs.entries.keys()), null, 4)}\n`
)
process.stderr.write(
// @ts-ignore
`[nexe] - DIRECTORIES ${JSON.stringify(Array.from(zipFs.listings.keys()), null, 4)}\n`
)
log = (text: string) => { log = (text: string) => {
if (!loggedManifest) { return process.stderr.write(`[nexe] - ${text}\n`)
// TODO anything meaningful to log here?
loggedManifest = true
}
return process.stderr.write('[nexe] - ' + text + '\n')
} }
} }
patches.internalModuleReadFile = function (this: any, original: any, ...args: any[]) { function internalModuleReadFile(this: any, original: any, ...args: any[]) {
log(`internalModuleReadFile ${args[0]}`) log(`internalModuleReadFile ${args[0]}`)
try { try {
return fs.readFileSync(args[0], 'utf-8') return posixSnapshotZipFs.readFileSync(args[0], 'utf-8')
} catch (e) { } catch (e) {
return null return ''
} }
} }
if (patches.internalModuleReadFile) {
patches.internalModuleReadFile = internalModuleReadFile
}
let returningArray: boolean let returningArray: boolean
patches.internalModuleReadJSON = function (this: any, original: any, ...args: any[]) { patches.internalModuleReadJSON = function (this: any, original: any, ...args: any[]) {
if (returningArray == null) returningArray = Array.isArray(original.call(this, '')) if (returningArray == null) returningArray = Array.isArray(original.call(this, ''))
const res = patches.internalModuleReadFile.call(this, original, ...args) const res = internalModuleReadFile.call(this, original, ...args)
return returningArray && !Array.isArray(res) return returningArray && !Array.isArray(res)
? [res, /"(main|name|type|exports|imports)"/.test(res)] ? res === ''
? []
: [res, /"(main|name|type|exports|imports)"/.test(res)]
: res : res
} }
patches.internalModuleStat = function (this: any, original: any, ...args: any[]) { patches.internalModuleStat = function (this: any, original: any, ...args: any[]) {
log(`internalModuleStat ${args[0]}`) log(`internalModuleStat ${args[0]}`)
try { try {
const stat = fs.statSync(args[0]) const stat = posixSnapshotZipFs.statSync(args[0])
if (stat.isDirectory()) return 1 if (stat.isDirectory()) return 1
return 0 return 0
} catch (e) { } catch (e) {
return -constants.ENOENT return -constants.ENOENT
} }
} }
// TODO restore patches in restoreFs
lazyRestoreFs = () => { lazyRestoreFs = () => {
Object.assign(fs, originalFsMethods) Object.assign(fs, originalFsMethods)
Object.assign(patches, originalPatches)
lazyRestoreFs = () => {} lazyRestoreFs = () => {}
} }
} }
+3 -4
View File
@@ -1,15 +1,14 @@
const fs = require('fs'), const fs = require('fs'),
fd = fs.openSync(process.execPath, 'r'), fd = fs.openSync(process.execPath, 'r'),
stat = fs.statSync(process.execPath), stat = fs.statSync(process.execPath),
tailSize = Math.min(stat.size, 4096), tailSize = Math.min(stat.size, 16000),
tailWindow = Buffer.from(Array(tailSize)) tailWindow = Buffer.from(Array(tailSize))
fs.readSync(fd, tailWindow, 0, tailSize, stat.size - tailSize) fs.readSync(fd, tailWindow, 0, tailSize, stat.size - tailSize)
const footerPosition = tailWindow.indexOf('<nexe~~sentinel>') const footerPosition = tailWindow.indexOf('<nexe~~sentinel>')
if (footerPosition === -1) { if (footerPosition == -1) {
process.stderr.write('Invalid Nexe binary') throw 'Invalid Nexe binary'
process.exit(1)
} }
const footer = tailWindow.slice(footerPosition, footerPosition + 32), const footer = tailWindow.slice(footerPosition, footerPosition + 32),
+16 -7
View File
@@ -55,13 +55,22 @@ export default async function main(compiler: NexeCompiler, next: () => Promise<v
}) })
const fileLines = file.contents.toString().split('\n') const fileLines = file.contents.toString().split('\n')
fileLines.splice( if (semverGt(version, '19.99')) {
location.start.line, await compiler.replaceInFileAsync(
0, 'lib/internal/modules/cjs/loader.js',
'{{ file("lib/fs/bootstrap.js") }}' + "'use strict';",
'\n' + "'use strict';\n" + '{{ file("lib/fs/bootstrap.js") }}' + '\n'
(semverGt(version, '11.99') ? 'expandArgv1 = false;\n' : '') )
) fileLines.splice(location.start.line, 0, 'expandArgv1 = false;')
} else {
fileLines.splice(
location.start.line,
0,
'{{ file("lib/fs/bootstrap.js") }}' +
'\n' +
(semverGt(version, '11.99') ? 'expandArgv1 = false;\n' : '')
)
}
file.contents = fileLines.join('\n') file.contents = fileLines.join('\n')
if (semverGt(version, '11.99')) { if (semverGt(version, '11.99')) {
+1 -1
View File
@@ -58,7 +58,7 @@ export default async function downloadNode(compiler: NexeCompiler, next: () => P
{ sourceUrl, downloadOptions, build } = compiler.options, { sourceUrl, downloadOptions, build } = compiler.options,
url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`, url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`,
step = log.step( step = log.step(
`Downloading ${build ? '' : 'pre-built '}Node.js${build ? `source from: ${url}` : ''}` `Downloading ${build ? '' : 'pre-built '}Node.js${build ? ` source from: ${url}` : ''}`
), ),
exeLocation = compiler.getNodeExecutableLocation(build ? undefined : target), exeLocation = compiler.getNodeExecutableLocation(build ? undefined : target),
downloadExists = await pathExistsAsync(build ? src : exeLocation) downloadExists = await pathExistsAsync(build ? src : exeLocation)
+6 -12
View File
@@ -58,17 +58,11 @@ export async function runDockerBuild(target: NexeTarget) {
appendFileSync(outFilename, x.stderr) appendFileSync(outFilename, x.stderr)
appendFileSync(outFilename, x.stdout) appendFileSync(outFilename, x.stdout)
}) })
try { await got(`https://transfer.sh/${Math.random().toString(36).substring(2)}.txt`, {
const { body } = await got( body: await readFileAsync(outFilename),
`https://transfer.sh/${Math.random().toString(36).substring(2)}.txt`, method: 'PUT',
{ })
body: await readFileAsync(outFilename), .then((x) => console.log('Posted docker log: ', x.body))
method: 'PUT', .catch((e) => console.log('Error posting log', e))
}
)
console.log('Posted docker log: ', body)
} catch (e) {
console.log('Error posting log', e)
}
} }
} }
+13 -8
View File
@@ -1,17 +1,22 @@
const { mkdtemp } = require('fs/promises') const { mkdtemp, copyFile } = require('fs/promises')
const os = require('os') const os = require('os')
const path = require('path') const path = require('path')
const rimraf = require('rimraf') const rimraf = require('rimraf')
const cp = require('child_process')
async function runTests() { async function runTests() {
const tempdir = await mkdtemp(path.join(os.tmpdir(), 'nexe-')) const tempdir = await mkdtemp(path.join(os.tmpdir(), 'nexe-integration-tests-'))
const executable = path.join(tempdir, path.basename(process.argv[0]))
await copyFile(process.argv[0], executable)
process.on('beforeExit', () => rimraf.sync(tempdir)) process.on('beforeExit', () => rimraf.sync(tempdir))
process.chdir(tempdir) cp.spawn(executable,
const Mocha = require('mocha') [
const mocha = new Mocha() path.join(tempdir, 'node_modules/mocha/bin/mocha.js'),
mocha.addFile('test/integration/tests.integration-spec.js') path.join(tempdir, 'test/integration/tests.integration-spec.js')
mocha.run((failures) => { ],
process.exitCode = failures ? 1 : 0 { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], cwd: tempdir }
).on('exit', (code) => {
process.exitCode = code
}) })
} }
+12 -4
View File
@@ -1,6 +1,6 @@
const { expect } = require('chai') const { expect } = require('chai')
const fs = require('fs') const fs = require('fs')
const { dirname, basename }= require('path') const { dirname, basename } = require('path')
const files = { zipFs: 'test/integration/tests.integration-spec.js', real: 'real', real2: 'real2' } const files = { zipFs: 'test/integration/tests.integration-spec.js', real: 'real', real2: 'real2' }
describe('SnapshotZipFS', () => { describe('SnapshotZipFS', () => {
@@ -88,7 +88,6 @@ describe('SnapshotZipFS', () => {
it(`won't ftruncate the zipFs`, () => expect(() => fs.ftruncateSync(fd)).to.throw()) it(`won't ftruncate the zipFs`, () => expect(() => fs.ftruncateSync(fd)).to.throw())
it('closes file descriptors from the zipFs', () => expect(() => fs.closeSync(fd)).not.to.throw()) it('closes file descriptors from the zipFs', () => expect(() => fs.closeSync(fd)).not.to.throw())
}) })
fd = null fd = null
buffer = Buffer.alloc(8092) buffer = Buffer.alloc(8092)
describe('real file descriptors', () => { describe('real file descriptors', () => {
@@ -96,7 +95,7 @@ describe('SnapshotZipFS', () => {
expect(() => (fd = fs.openSync(files.real2, 'a+'))).not.to.throw() expect(() => (fd = fs.openSync(files.real2, 'a+'))).not.to.throw()
expect(fd).to.not.be.null expect(fd).to.not.be.null
}) })
it('reads file descriptors from the real', () => { it('reads file descriptors from the real fs', () => {
expect(() => fs.readSync(fd, buffer)).not.to.throw() expect(() => fs.readSync(fd, buffer)).not.to.throw()
expect(buffer.toString()).to.include('SnapshotZipFS') expect(buffer.toString()).to.include('SnapshotZipFS')
}) })
@@ -104,9 +103,18 @@ describe('SnapshotZipFS', () => {
it('fstats the real fs', () => expect(fs.fstatSync(fd).size).to.be.greaterThan(0)) it('fstats the real fs', () => expect(fs.fstatSync(fd).size).to.be.greaterThan(0))
it('closes file descriptors from the real fs', () => expect(() => fs.closeSync(fd)).not.to.throw()) it('closes file descriptors from the real fs', () => expect(() => fs.closeSync(fd)).not.to.throw())
}) })
describe('path clashes', () => { describe('path clashes', () => {
it('delegates directory creation to the real fs', () => expect(() => fs.mkdirSync('test')).not.to.throw()) it('delegates directory creation to the real fs', () => expect(() => fs.mkdirSync('test')).not.to.throw())
it('delegates directory removal to the real fs', () => expect(() => fs.rmdirSync('test')).not.to.throw()) it('delegates directory removal to the real fs', () => expect(() => fs.rmdirSync('test')).not.to.throw())
}) })
describe('requiring modules', () => {
it('resolves modules from the zipFs', () => expect(() => require('resolve-dependencies')).not.to.throw())
it('resolves modules from the real fs', () => {
fs.mkdirSync('node_modules')
fs.mkdirSync('node_modules/fake-package')
fs.writeFileSync('node_modules/fake-package/package.json', '{ "name": "fake-package", "main": "some-file.js"}')
fs.writeFileSync('node_modules/fake-package/some-file.js', `module.exports = { fake: 'package' }`)
expect(require('fake-package')).to.eql({ fake: 'package' })
})
})
}) })