Compare commits
15 Commits
master
...
caleb/fslib-2
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a9434e1ff | |||
| 049fda37ac | |||
| 7febbbd89d | |||
| 5d5513d264 | |||
| 7a8625d92b | |||
| ec8597c124 | |||
| 053712c8d3 | |||
| 60747897dc | |||
| 59c03cd2f5 | |||
| cccf51f9bf | |||
| 25626c8d92 | |||
| ba3b91eaf1 | |||
| fc09a7d903 | |||
| bbaa63767d | |||
| c3baa1c2a3 |
+7
-3
@@ -47,7 +47,7 @@ jobs:
|
|||||||
- script: sudo apt-get update && sudo apt-get install -y libc6-dev-i386 gcc-multilib g++-multilib
|
- script: sudo apt-get update && sudo apt-get install -y libc6-dev-i386 gcc-multilib g++-multilib
|
||||||
condition: contains(variables['Agent.JobName'], 'linux')
|
condition: contains(variables['Agent.JobName'], 'linux')
|
||||||
displayName: Install Linux build dependencies
|
displayName: Install Linux build dependencies
|
||||||
- script: choco install visualcpp-build-tools --version 14.0.25420.1 -fy && npm config set msvs_version 2015
|
- script: npm config set msvs_version 2015
|
||||||
condition: contains(variables['Agent.JobName'], 'windows_2015')
|
condition: contains(variables['Agent.JobName'], 'windows_2015')
|
||||||
displayName: Install Windows build dependencies
|
displayName: Install Windows build dependencies
|
||||||
- script: npm install && npm run ci:build
|
- script: npm install && npm run ci:build
|
||||||
@@ -58,8 +58,7 @@ jobs:
|
|||||||
msbuild_args: /3G
|
msbuild_args: /3G
|
||||||
condition: not(contains(variables['Agent.JobName'], 'windows'))
|
condition: not(contains(variables['Agent.JobName'], 'windows'))
|
||||||
- powershell: |
|
- powershell: |
|
||||||
iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
|
choco install nasm
|
||||||
scoop install nasm
|
|
||||||
npm install
|
npm install
|
||||||
npm run ci:build
|
npm run ci:build
|
||||||
displayName: Build Nexe (Windows)
|
displayName: Build Nexe (Windows)
|
||||||
@@ -67,3 +66,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: $(PersonalGithubToken)
|
GITHUB_TOKEN: $(PersonalGithubToken)
|
||||||
NEXE_VERBOSE: $(NEXE_VERBOSE)
|
NEXE_VERBOSE: $(NEXE_VERBOSE)
|
||||||
|
- script: npm run test:integration
|
||||||
|
displayName: Test built artefact
|
||||||
|
env:
|
||||||
|
NEXE_VERBOSE: $(NEXE_VERBOSE)
|
||||||
|
BUILD: ''
|
||||||
|
|||||||
Generated
+5224
-1581
File diff suppressed because it is too large
Load Diff
+21
-6
@@ -2,7 +2,7 @@
|
|||||||
"name": "nexe",
|
"name": "nexe",
|
||||||
"description": "Create a single executable out of your Node.js application",
|
"description": "Create a single executable out of your Node.js application",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "4.0.0-rc.2",
|
"version": "5.0.0-beta.1",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
|
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
|
||||||
"Jared Allard <jaredallard@outlook.com>",
|
"Jared Allard <jaredallard@outlook.com>",
|
||||||
@@ -11,10 +11,14 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"ci:build": "ts-node tasks/build",
|
"ci:build": "ts-node tasks/build",
|
||||||
"test": "mocha",
|
"test": "mocha",
|
||||||
|
"test:integration": "node index.js --target 14.15.3 -i test/integration/index.js -o integration-tests --verbose -r test/integration -r node_modules/ && npm run test:integration:run",
|
||||||
|
"test:integration:run": "run-script-os",
|
||||||
|
"test:integration:run:win32": "integration-tests.exe",
|
||||||
|
"test:integration:run:default": "./integration-tests",
|
||||||
"lint": "tslint \"{src,plugins,tasks}/**/*.ts\" --fix",
|
"lint": "tslint \"{src,plugins,tasks}/**/*.ts\" --fix",
|
||||||
"prepare": "npm run lint && npm run build && npm test",
|
"prepare": "npm run lint && npm run build && npm test",
|
||||||
"prebuild": "rimraf lib",
|
"prebuild": "rimraf lib",
|
||||||
"build": "tsc --declaration && tsc tasks/*.ts --noEmit --skipLibCheck",
|
"build": "tsc --declaration && tsc -p tasks && webpack",
|
||||||
"postbuild": "ts-node tasks/post-build"
|
"postbuild": "ts-node tasks/post-build"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -40,8 +44,10 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@calebboyd/semaphore": "^1.3.1",
|
"@yarnpkg/fslib": "^2.6.0-rc.8",
|
||||||
|
"@yarnpkg/libzip": "^2.2.2",
|
||||||
"app-builder": "^7.0.4",
|
"app-builder": "^7.0.4",
|
||||||
|
"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",
|
||||||
@@ -53,27 +59,36 @@
|
|||||||
"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.8",
|
||||||
"rimraf": "^3.0.2"
|
"rimraf": "^3.0.2",
|
||||||
|
"run-script-os": "^1.1.6",
|
||||||
|
"webpack-config-prefabs": "0.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/archiver": "^5.3.2",
|
||||||
"@types/chai": "^4",
|
"@types/chai": "^4",
|
||||||
"@types/download": "^6",
|
"@types/download": "^6",
|
||||||
"@types/globby": "^9",
|
"@types/globby": "^9",
|
||||||
|
"@types/got": "^9.6.12",
|
||||||
|
"@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": "^9.0.0",
|
||||||
"@types/multistream": "^2.1.1",
|
"@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.8",
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
"execa": "^5.1.1",
|
"execa": "^5.1.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"mocha": "^10.0.0",
|
"mocha": "^10.0.0",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.6.2",
|
||||||
"ts-node": "^10.7.0",
|
"ts-node": "^10.7.0",
|
||||||
"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": "^4.6.4",
|
||||||
|
"webpack-cli": "^4.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-14
@@ -16,9 +16,11 @@ import {
|
|||||||
} from './util'
|
} from './util'
|
||||||
import { NexeOptions, version } from './options'
|
import { NexeOptions, version } from './options'
|
||||||
import { NexeTarget } from './target'
|
import { NexeTarget } from './target'
|
||||||
|
import { PassThrough, Readable, Stream, Transform } from 'stream'
|
||||||
import MultiStream = require('multistream')
|
import MultiStream = require('multistream')
|
||||||
import { Bundle, toStream } from './fs/bundle'
|
import { Bundle, toStream } from './fs/bundle'
|
||||||
import { File } from 'resolve-dependencies'
|
import { File } from 'resolve-dependencies'
|
||||||
|
import { FactoryStream, LazyStream } from 'multistream'
|
||||||
|
|
||||||
const isBsd = Boolean(~process.platform.indexOf('bsd'))
|
const isBsd = Boolean(~process.platform.indexOf('bsd'))
|
||||||
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
|
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
|
||||||
@@ -287,24 +289,34 @@ export class NexeCompiler {
|
|||||||
return [this.shims.join(''), this.startup].join(';')
|
return [this.shims.join(''), this.startup].join(';')
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _assembleDeliverable(binary: NodeJS.ReadableStream) {
|
private async _assembleDeliverable(binary: Readable) {
|
||||||
if (!this.options.mangle) {
|
if (!this.options.mangle) {
|
||||||
return binary
|
return binary
|
||||||
}
|
}
|
||||||
const resources = this.bundle.renderIndex()
|
const launchCode = this.code()
|
||||||
this.shims.unshift(wrap(`process.__nexe = ${JSON.stringify({ resources })};\n`))
|
const codeSize = Buffer.byteLength(launchCode)
|
||||||
|
const sentinel = Buffer.from('<nexe~~sentinel>')
|
||||||
|
|
||||||
const code = this.code(),
|
let vfsSize = 0
|
||||||
codeSize = Buffer.byteLength(code),
|
const streams = [binary, toStream(launchCode), this.bundle.toStream().pipe(new Transform({
|
||||||
lengths = Buffer.from(Array(16))
|
transform: (chunk, _, cb) => {
|
||||||
|
vfsSize || this.bundle.finalize()
|
||||||
|
chunk && (vfsSize += chunk.length)
|
||||||
|
cb(null, chunk)
|
||||||
|
},
|
||||||
|
}))]
|
||||||
|
|
||||||
lengths.writeDoubleLE(codeSize, 0)
|
let done = false
|
||||||
lengths.writeDoubleLE(this.bundle.size, 8)
|
return new MultiStream((cb) => {
|
||||||
return new (MultiStream as any)([
|
if (done) cb(null, null)
|
||||||
binary,
|
else if (streams.length) cb(null, streams.shift() as Readable)
|
||||||
toStream(code),
|
else {
|
||||||
this.bundle.toStream(),
|
done = true
|
||||||
toStream(Buffer.concat([Buffer.from('<nexe~~sentinel>'), lengths])),
|
const trailers = Buffer.alloc(16)
|
||||||
])
|
trailers.writeDoubleLE(codeSize, 0)
|
||||||
|
trailers.writeDoubleLE(vfsSize, 8)
|
||||||
|
cb(null, toStream(Buffer.concat([sentinel, trailers])))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,383 @@
|
|||||||
|
import { Libzip } from '@yarnpkg/libzip'
|
||||||
|
import {
|
||||||
|
FakeFS,
|
||||||
|
PortablePath,
|
||||||
|
ZipFS,
|
||||||
|
ZipOpenFS,
|
||||||
|
RmdirOptions,
|
||||||
|
OpendirOptions,
|
||||||
|
} from '@yarnpkg/fslib'
|
||||||
|
import { 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'
|
||||||
|
import { resolve, toNamespacedPath } from 'path'
|
||||||
|
import { constants } from 'fs'
|
||||||
|
|
||||||
|
export type SnapshotZipFSOptions = {
|
||||||
|
baseFs: FakeFS<PortablePath>
|
||||||
|
libzip: Libzip | (() => Libzip)
|
||||||
|
zipFs: ZipFS
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqBy = (arr: Array<string | Dirent>, pick: (...arg: any[]) => any) => {
|
||||||
|
return arr.filter((x, i, self) => i === self.findIndex((y) => pick(x) === pick(y)))
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
+20
-128
@@ -1,24 +1,10 @@
|
|||||||
import * as fs from 'fs'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
import { relative } from 'path'
|
import { relative } from 'path'
|
||||||
import { Readable } from 'stream'
|
import { Readable } from 'stream'
|
||||||
import { argv } from '../options'
|
|
||||||
import { File } from 'resolve-dependencies'
|
import { File } from 'resolve-dependencies'
|
||||||
import MultiStream = require('multistream')
|
import archiver from 'archiver'
|
||||||
|
|
||||||
const lstat = promisify(fs.lstat),
|
function makeRelativeToZip(cwd: string, path: string) {
|
||||||
realpath = promisify(fs.realpath),
|
return '/snapshot/' + relative(cwd, path)
|
||||||
createReadStream = fs.createReadStream,
|
|
||||||
stat = promisify(fs.stat)
|
|
||||||
|
|
||||||
export type MultiStreams = (Readable | (() => Readable))[]
|
|
||||||
|
|
||||||
function makeRelative(cwd: string, path: string) {
|
|
||||||
return './' + relative(cwd, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortEntry(a: [string, File], b: [string, File]) {
|
|
||||||
return a[0] > b[0] ? 1 : -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toStream(content: Buffer | string) {
|
export function toStream(content: Buffer | string) {
|
||||||
@@ -30,130 +16,36 @@ export function toStream(content: Buffer | string) {
|
|||||||
})
|
})
|
||||||
return readable
|
return readable
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createFile(absoluteFileName: string) {
|
|
||||||
const stats = await lstat(absoluteFileName),
|
|
||||||
file: File = {
|
|
||||||
size: stats.size,
|
|
||||||
moduleType: 'commonjs',
|
|
||||||
contents: '',
|
|
||||||
absPath: absoluteFileName,
|
|
||||||
deps: {},
|
|
||||||
}
|
|
||||||
if (stats.isSymbolicLink()) {
|
|
||||||
file.size = stats.size
|
|
||||||
const [realPath, realStat] = await Promise.all([
|
|
||||||
realpath(absoluteFileName),
|
|
||||||
stat(absoluteFileName),
|
|
||||||
])
|
|
||||||
file.realPath = realPath
|
|
||||||
file.realSize = realStat.size
|
|
||||||
}
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Bundle {
|
export class Bundle {
|
||||||
size = 0
|
|
||||||
cwd: string
|
cwd: string
|
||||||
rendered = false
|
files = new Set<string>()
|
||||||
private offset = 0
|
zip: any
|
||||||
private index: { [key: string]: [number, number] } = {}
|
|
||||||
private files: { [key: string]: File } = {}
|
|
||||||
streams: MultiStreams = []
|
|
||||||
constructor({ cwd }: { cwd: string } = { cwd: process.cwd() }) {
|
constructor({ cwd }: { cwd: string } = { cwd: process.cwd() }) {
|
||||||
this.cwd = cwd
|
this.cwd = cwd
|
||||||
|
this.zip = archiver('zip')
|
||||||
}
|
}
|
||||||
|
|
||||||
get list() {
|
get list() {
|
||||||
return Object.keys(this.files)
|
return Array.from(this.files)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addResource(
|
public addResource(absoluteFileName: string, content?: File | Buffer | string) {
|
||||||
absoluteFileName: string,
|
const destPath = makeRelativeToZip(this.cwd, absoluteFileName)
|
||||||
content?: File | Buffer | string
|
if (!this.files.has(destPath)) {
|
||||||
): Promise<number> {
|
if (content == null) {
|
||||||
if (this.files[absoluteFileName]) {
|
this.zip.file(absoluteFileName, { name: destPath })
|
||||||
return this.size
|
|
||||||
}
|
|
||||||
if (typeof content === 'string' || Buffer.isBuffer(content)) {
|
|
||||||
this.files[absoluteFileName] = {
|
|
||||||
size: Buffer.byteLength(content),
|
|
||||||
moduleType: 'commonjs',
|
|
||||||
contents: content as any, //todo type is wrong here... should allow buffer
|
|
||||||
deps: {},
|
|
||||||
absPath: absoluteFileName,
|
|
||||||
}
|
|
||||||
} else if (content) {
|
|
||||||
this.files[content.absPath] = content
|
|
||||||
} else {
|
|
||||||
this.files[absoluteFileName] = {
|
|
||||||
absPath: absoluteFileName,
|
|
||||||
moduleType: 'commonjs',
|
|
||||||
contents: '',
|
|
||||||
deps: {},
|
|
||||||
size: 0,
|
|
||||||
}
|
|
||||||
this.files[absoluteFileName] = await createFile(absoluteFileName)
|
|
||||||
}
|
|
||||||
return (this.size += this.files[absoluteFileName].size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* De-dupe files by absolute path, partition by symlink/real
|
|
||||||
* Iterate over real, add entries
|
|
||||||
* Iterate over symlinks, add symlinks
|
|
||||||
*/
|
|
||||||
renderIndex() {
|
|
||||||
if (this.rendered) {
|
|
||||||
throw new Error('Bundle index already rendered')
|
|
||||||
}
|
|
||||||
const files = Object.entries(this.files),
|
|
||||||
realFiles: [string, File][] = [],
|
|
||||||
symLinks: [string, File][] = []
|
|
||||||
for (const entry of files) {
|
|
||||||
if (entry[1].realPath) {
|
|
||||||
symLinks.push(entry)
|
|
||||||
} else {
|
} else {
|
||||||
realFiles.push(entry)
|
this.zip.append(content, { name: destPath })
|
||||||
}
|
}
|
||||||
}
|
this.files.add(destPath)
|
||||||
realFiles.sort(sortEntry)
|
|
||||||
symLinks.sort(sortEntry)
|
|
||||||
|
|
||||||
for (const [absPath, file] of realFiles) {
|
|
||||||
this.addEntry(absPath, file)
|
|
||||||
}
|
|
||||||
for (const [absPath, file] of symLinks) {
|
|
||||||
this.addEntry(file.realPath as string, file)
|
|
||||||
this.addEntry(absPath, file, file.realPath)
|
|
||||||
}
|
|
||||||
this.rendered = true
|
|
||||||
return this.index
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a stream if needed and an entry with the required offset and size
|
|
||||||
* Ensure the calling order of this method is idempotent (eg, while iterating a sorted set)
|
|
||||||
* @param entryPath
|
|
||||||
* @param file
|
|
||||||
* @param useEntry
|
|
||||||
*/
|
|
||||||
addEntry(entryPath: string, file: File, useEntry?: string) {
|
|
||||||
const existingName = useEntry && makeRelative(this.cwd, useEntry),
|
|
||||||
name = makeRelative(this.cwd, entryPath),
|
|
||||||
size = file.realSize ?? file.size,
|
|
||||||
existingEntry = this.index[existingName ?? name]
|
|
||||||
|
|
||||||
this.index[name] = existingEntry || [this.offset, size]
|
|
||||||
if (!existingEntry) {
|
|
||||||
this.streams.push(() =>
|
|
||||||
file.contents ? toStream(file.contents) : createReadStream(file.absPath)
|
|
||||||
)
|
|
||||||
this.offset += size
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toStream() {
|
public finalize() {
|
||||||
return new (MultiStream as any)(this.streams)
|
return this.zip.finalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
public toStream(): Readable {
|
||||||
|
return this.zip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-385
@@ -1,329 +1,76 @@
|
|||||||
import { Stats } from 'fs'
|
import { getLibzipSync } from '@yarnpkg/libzip'
|
||||||
import { getLatestGitRelease } from '../releases'
|
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
|
blobPath: string
|
||||||
resources: { [key: string]: number[] }
|
|
||||||
layout: {
|
layout: {
|
||||||
stat: Stats
|
|
||||||
resourceStart: number
|
resourceStart: number
|
||||||
contentSize?: number
|
resourceSize: number
|
||||||
contentStart?: number
|
contentSize: number
|
||||||
resourceSize?: number
|
contentStart: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let originalFsMethods: any = null
|
let originalFsMethods: any = null
|
||||||
let lazyRestoreFs = () => {}
|
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
|
function shimFs(binary: NexeHeader, fs: typeof import('fs') = require('fs')) {
|
||||||
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')) {
|
|
||||||
if (originalFsMethods !== null) {
|
if (originalFsMethods !== null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
originalFsMethods = Object.assign({}, fs)
|
originalFsMethods = Object.assign({}, fs)
|
||||||
const { blobPath, resources: manifest } = binary,
|
|
||||||
{ resourceStart, stat } = binary.layout,
|
const realFs: typeof fs = { ...fs }
|
||||||
directories: { [key: string]: { [key: string]: boolean } } = {},
|
const nodeFs = new NodeFS(realFs)
|
||||||
notAFile = '!@#$%^&*',
|
|
||||||
isWin = process.platform.startsWith('win'),
|
const blob = Buffer.allocUnsafe(binary.layout.resourceSize)
|
||||||
isString = (x: any): x is string => typeof x === 'string' || x instanceof String,
|
const blobFd = realFs.openSync(binary.blobPath, 'r')
|
||||||
noop = () => {},
|
const bytesRead = realFs.readSync(
|
||||||
path = require('path'),
|
blobFd,
|
||||||
winPath: (key: string) => string = isWin ? upcaseDriveLetter : (s) => s,
|
blob,
|
||||||
baseDir = winPath(path.dirname(process.execPath))
|
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 log = (_: string) => true
|
||||||
let loggedManifest = false
|
let loggedManifest = false
|
||||||
if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {
|
if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {
|
||||||
log = (text: string) => {
|
log = (text: string) => {
|
||||||
setupManifest()
|
|
||||||
if (!loggedManifest) {
|
if (!loggedManifest) {
|
||||||
process.stderr.write('[nexe] - MANIFEST' + JSON.stringify(manifest, null, 4) + '\n')
|
// TODO anything meaningful to log here?
|
||||||
process.stderr.write('[nexe] - DIRECTORIES' + JSON.stringify(directories, null, 4) + '\n')
|
|
||||||
loggedManifest = true
|
loggedManifest = true
|
||||||
}
|
}
|
||||||
return process.stderr.write('[nexe] - ' + text + '\n')
|
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[]) {
|
patches.internalModuleReadFile = function (this: any, original: any, ...args: any[]) {
|
||||||
setupManifest()
|
log(`internalModuleReadFile ${args[0]}`)
|
||||||
const filepath = getKey(args[0])
|
try {
|
||||||
if (manifest[filepath]) {
|
return fs.readFileSync(args[0], 'utf-8')
|
||||||
log('read (hit) ' + filepath)
|
} catch (e) {
|
||||||
return nfs.readFileSync(filepath, 'utf-8')
|
return null
|
||||||
}
|
}
|
||||||
log('read (miss) ' + filepath)
|
|
||||||
return original.call(this, ...args)
|
|
||||||
}
|
}
|
||||||
let returningArray: boolean
|
let returningArray: boolean
|
||||||
patches.internalModuleReadJSON = function (this: any, original: any, ...args: any[]) {
|
patches.internalModuleReadJSON = function (this: any, original: any, ...args: any[]) {
|
||||||
@@ -334,99 +81,21 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
|
|||||||
: res
|
: res
|
||||||
}
|
}
|
||||||
patches.internalModuleStat = function (this: any, original: any, ...args: any[]) {
|
patches.internalModuleStat = function (this: any, original: any, ...args: any[]) {
|
||||||
setupManifest()
|
log(`internalModuleStat ${args[0]}`)
|
||||||
const filepath = getKey(args[0])
|
try {
|
||||||
if (manifest[filepath]) {
|
const stat = fs.statSync(args[0])
|
||||||
log('stat (hit) ' + filepath + ' ' + 0)
|
if (stat.isDirectory()) return 1
|
||||||
return 0
|
return 0
|
||||||
}
|
} catch (e) {
|
||||||
if (directories[filepath]) {
|
return -constants.ENOENT
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO restore patches in restoreFs
|
||||||
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)
|
|
||||||
|
|
||||||
lazyRestoreFs = () => {
|
lazyRestoreFs = () => {
|
||||||
Object.keys(nfs).forEach((key) => {
|
Object.assign(fs, originalFsMethods)
|
||||||
fs[key] = originalFsMethods[key]
|
|
||||||
})
|
|
||||||
lazyRestoreFs = () => {}
|
lazyRestoreFs = () => {}
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreFs() {
|
function restoreFs() {
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
import * as parseArgv from 'minimist'
|
import parseArgv from 'minimist'
|
||||||
import { NexeCompiler, NexeError } from './compiler'
|
import { NexeCompiler, NexeError } from './compiler'
|
||||||
import { isWindows, STDIN_FLAG } from './util'
|
import { isWindows, STDIN_FLAG } from './util'
|
||||||
import { basename, extname, join, isAbsolute, resolve } from 'path'
|
import { basename, extname, join, isAbsolute, resolve } from 'path'
|
||||||
@@ -9,7 +9,7 @@ import { resolveSync } from 'resolve-dependencies'
|
|||||||
const caw = require('caw')
|
const caw = require('caw')
|
||||||
const c = process.platform === 'win32' ? chalk.constructor({ enabled: false }) : chalk
|
const c = process.platform === 'win32' ? chalk.constructor({ enabled: false }) : chalk
|
||||||
|
|
||||||
export const version = '{{replace:0}}'
|
export const version = '{{ version }}'
|
||||||
|
|
||||||
export interface NexePatch {
|
export interface NexePatch {
|
||||||
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
|
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
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, 16000),
|
tailSize = Math.min(stat.size, 4096),
|
||||||
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) {
|
||||||
throw 'Invalid Nexe binary'
|
process.stderr.write('Invalid Nexe binary')
|
||||||
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const footer = tailWindow.slice(footerPosition, footerPosition + 32),
|
const footer = tailWindow.slice(footerPosition, footerPosition + 32),
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default async function main(compiler: NexeCompiler, next: () => Promise<v
|
|||||||
fileLines.splice(
|
fileLines.splice(
|
||||||
location.start.line,
|
location.start.line,
|
||||||
0,
|
0,
|
||||||
'{{replace:lib/fs/bootstrap.js}}' +
|
'{{ file("lib/fs/bootstrap.js") }}' +
|
||||||
'\n' +
|
'\n' +
|
||||||
(semverGt(version, '11.99') ? 'expandArgv1 = false;\n' : '')
|
(semverGt(version, '11.99') ? 'expandArgv1 = false;\n' : '')
|
||||||
)
|
)
|
||||||
@@ -69,13 +69,13 @@ export default async function main(compiler: NexeCompiler, next: () => Promise<v
|
|||||||
await compiler.replaceInFileAsync(
|
await compiler.replaceInFileAsync(
|
||||||
bootFile,
|
bootFile,
|
||||||
'initializeFrozenIntrinsics();',
|
'initializeFrozenIntrinsics();',
|
||||||
'initializeFrozenIntrinsics();\n' + wrap('{{replace:lib/patches/boot-nexe.js}}')
|
'initializeFrozenIntrinsics();\n' + wrap('{{ file("lib/patches/boot-nexe.js") }}')
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
await compiler.replaceInFileAsync(
|
await compiler.replaceInFileAsync(
|
||||||
bootFile,
|
bootFile,
|
||||||
'initializePolicy();',
|
'initializePolicy();',
|
||||||
'initializePolicy();\n' + wrap('{{replace:lib/patches/boot-nexe.js}}')
|
'initializePolicy();\n' + wrap('{{ file("lib/patches/boot-nexe.js") }}')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
await compiler.replaceInFileAsync(
|
await compiler.replaceInFileAsync(
|
||||||
@@ -102,7 +102,7 @@ export default async function main(compiler: NexeCompiler, next: () => Promise<v
|
|||||||
} else {
|
} else {
|
||||||
await compiler.setFileContentsAsync(
|
await compiler.setFileContentsAsync(
|
||||||
'lib/_third_party_main.js',
|
'lib/_third_party_main.js',
|
||||||
'{{replace:lib/patches/boot-nexe.js}}'
|
'{{ file("lib/patches/boot-nexe.js") }}'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return next()
|
return next()
|
||||||
|
|||||||
+7
-4
@@ -61,9 +61,11 @@ export default async function bundle(compiler: NexeCompiler, next: any) {
|
|||||||
compiler.entrypoint = './' + relative(cwd, input)
|
compiler.entrypoint = './' + relative(cwd, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { files, warnings } = resolveFiles(
|
const step = compiler.log.step('Resolving dependencies...')
|
||||||
|
|
||||||
|
const { files, warnings } = await resolveFiles(
|
||||||
input,
|
input,
|
||||||
...Object.keys(compiler.bundle.list).filter((x) => x.endsWith('.js')),
|
...Object.keys(compiler.bundle.list).filter((x) => x.endsWith('.js') || x.endsWith('.mjs')),
|
||||||
{ cwd, expand: 'variable', loadContent: false }
|
{ cwd, expand: 'variable', loadContent: false }
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,8 +78,9 @@ export default async function bundle(compiler: NexeCompiler, next: any) {
|
|||||||
//TODO: warnings.forEach((x) => console.log(x))
|
//TODO: warnings.forEach((x) => console.log(x))
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(files).map(([key, file]) => {
|
Object.entries(files).map(([key]) => {
|
||||||
return compiler.addResource(key, file)
|
step.log(`Including dependency: ${key}`)
|
||||||
|
return compiler.addResource(key)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { each } from '../util'
|
import { each } from '../util'
|
||||||
import * as globs from 'globby'
|
import globs from 'globby'
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import { NexeCompiler } from '../compiler'
|
import { NexeCompiler } from '../compiler'
|
||||||
|
|
||||||
|
|||||||
+11
-4
@@ -5,10 +5,17 @@ export default async function (compiler: NexeCompiler, next: () => Promise<void>
|
|||||||
await next()
|
await next()
|
||||||
compiler.shims.push(
|
compiler.shims.push(
|
||||||
wrap(
|
wrap(
|
||||||
'' +
|
[
|
||||||
'{{replace:lib/fs/patch.js}}' +
|
'process.__nexe = {};',
|
||||||
'\nshimFs(process.__nexe)' +
|
'const fsPatcher = (function() {',
|
||||||
`\n${compiler.options.fs ? '' : 'restoreFs()'}`
|
'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
|
//TODO support only restoring specific methods
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
+12
-6
@@ -58,11 +58,17 @@ export async function runDockerBuild(target: NexeTarget) {
|
|||||||
appendFileSync(outFilename, x.stderr)
|
appendFileSync(outFilename, x.stderr)
|
||||||
appendFileSync(outFilename, x.stdout)
|
appendFileSync(outFilename, x.stdout)
|
||||||
})
|
})
|
||||||
await got(`https://transfer.sh/${Math.random().toString(36).substring(2)}.txt`, {
|
try {
|
||||||
body: await readFileAsync(outFilename),
|
const { body } = await got(
|
||||||
method: 'PUT',
|
`https://transfer.sh/${Math.random().toString(36).substring(2)}.txt`,
|
||||||
})
|
{
|
||||||
.then((x) => console.log('Posted docker log: ', x.body))
|
body: await readFileAsync(outFilename),
|
||||||
.catch((e) => console.log('Error posting log', e))
|
method: 'PUT',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
console.log('Posted docker log: ', body)
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Error posting log', e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-15
@@ -1,11 +1,10 @@
|
|||||||
import { writeFileSync, readFileSync } from 'fs'
|
import { writeFileSync, readFileSync } from 'fs'
|
||||||
|
import { template } from 'lodash'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* post build step to insert code files into code files (naively).
|
* post build step to insert code files into code files.
|
||||||
* '{{replace:path/to/file}}' => "file contents"
|
|
||||||
* And the package.json version.
|
* And the package.json version.
|
||||||
*/
|
*/
|
||||||
//inject('plugins/nexe-daemon/lib/index.js')
|
|
||||||
cp('src/fs/package.json', 'lib/fs/package.json')
|
cp('src/fs/package.json', 'lib/fs/package.json')
|
||||||
cp('src/fs/bootstrap.js', 'lib/fs/bootstrap.js')
|
cp('src/fs/bootstrap.js', 'lib/fs/bootstrap.js')
|
||||||
cp('src/fs/README.md', 'lib/fs/README.md')
|
cp('src/fs/README.md', 'lib/fs/README.md')
|
||||||
@@ -14,18 +13,12 @@ inject('lib/patches/third-party-main.js')
|
|||||||
inject('lib/steps/shim.js')
|
inject('lib/steps/shim.js')
|
||||||
inject('lib/options.js', JSON.stringify(require('../package.json').version))
|
inject('lib/options.js', JSON.stringify(require('../package.json').version))
|
||||||
|
|
||||||
function inject(filename: string, ...replacements: string[]) {
|
function inject(filename: string, version?: string) {
|
||||||
let contents = readFileSync(filename, 'utf8')
|
const contents = template(readFileSync(filename, 'utf8'), {
|
||||||
contents = contents.replace(/('{{(.*)}}')/g, (substring: string, ...matches: string[]) => {
|
interpolate: /'\{\{([\s\S]*?)\}\}'/,
|
||||||
if (!matches || !matches[1]) {
|
})({
|
||||||
return substring
|
file: (path: string) => JSON.stringify(readFileSync(path, 'utf8')),
|
||||||
}
|
version,
|
||||||
const [replace, file] = matches[1].split(':')
|
|
||||||
if (replace !== 'replace') {
|
|
||||||
return substring
|
|
||||||
}
|
|
||||||
console.log('Replacing: ', substring)
|
|
||||||
return replacements[+file] ? replacements[+file] : JSON.stringify(readFileSync(file, 'utf8'))
|
|
||||||
})
|
})
|
||||||
writeFileSync(filename, contents)
|
writeFileSync(filename, contents)
|
||||||
console.log(`Wrote: ${filename}`)
|
console.log(`Wrote: ${filename}`)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include":[
|
||||||
|
"*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
const { mkdtemp } = require('fs/promises')
|
||||||
|
const os = require('os')
|
||||||
|
const path = require('path')
|
||||||
|
const rimraf = require('rimraf')
|
||||||
|
|
||||||
|
async function runTests() {
|
||||||
|
const tempdir = await mkdtemp(path.join(os.tmpdir(), 'nexe-'))
|
||||||
|
process.on('beforeExit', () => rimraf.sync(tempdir))
|
||||||
|
process.chdir(tempdir)
|
||||||
|
const Mocha = require('mocha')
|
||||||
|
const mocha = new Mocha()
|
||||||
|
mocha.addFile('test/integration/tests.integration-spec.js')
|
||||||
|
mocha.run((failures) => {
|
||||||
|
process.exitCode = failures ? 1 : 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests()
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
const { expect } = require('chai')
|
||||||
|
const fs = require('fs')
|
||||||
|
const { dirname, basename }= require('path')
|
||||||
|
|
||||||
|
const files = { zipFs: 'test/integration/tests.integration-spec.js', real: 'real', real2: 'real2' }
|
||||||
|
describe('SnapshotZipFS', () => {
|
||||||
|
console.error('Note: these tests are order dependant, so subsequent failures are likely a flow on effect.')
|
||||||
|
it('checks existance from the zipFs', () => expect(fs.existsSync(files.zipFs)).to.equal(true))
|
||||||
|
it('checks non-existance from the zipFs', () => expect(fs.existsSync('not found')).to.equal(false))
|
||||||
|
it('reads from the zipFs', () => expect(fs.readFileSync(files.zipFs, 'utf8')).to.include('SnapshotZipFS'))
|
||||||
|
it('copies from the zipFs', () => expect(() => fs.copyFileSync(files.zipFs, files.real)).not.to.throw())
|
||||||
|
it('reads from the real fs', () => expect(fs.readFileSync(files.real, 'utf8')).to.include('SnapshotZipFS'))
|
||||||
|
it('checks non-existance from the real fs', () => expect(fs.existsSync(files.real2)).to.equal(false))
|
||||||
|
it('copies from the real fs', () => expect(() => fs.copyFileSync(files.real, files.real2)).not.to.throw())
|
||||||
|
it('checks existance from the real fs', () => expect(fs.existsSync(files.real2)).to.equal(true))
|
||||||
|
it('unlinks from the real fs', () => expect(() => fs.unlinkSync(files.real2)).not.to.throw())
|
||||||
|
it('symlinks the real fs', () => expect(() => fs.symlinkSync('tmp', files.real2)).not.to.throw())
|
||||||
|
it('readlinks the real fs', () => expect(fs.readlinkSync(files.real2)).to.equal('tmp'))
|
||||||
|
it('truncates the real fs', () => expect(() => fs.truncateSync(files.real)).not.to.throw())
|
||||||
|
it('writeFiles the real fs', () => expect(() => fs.writeFileSync(files.real2, 'testing')).not.to.throw())
|
||||||
|
it('accesses the real fs', () => expect(() => fs.accessSync(files.real)).not.to.throw())
|
||||||
|
it('accesses the zipFs', () => expect(() => fs.accessSync(files.zipFs)).not.to.throw())
|
||||||
|
it('stats the real fs', () => expect(fs.statSync(files.real2).size).to.be.greaterThan(0))
|
||||||
|
it('stats the zipFs', () => expect(fs.statSync(files.zipFs).size).to.be.greaterThan(0))
|
||||||
|
it('readdirs from the zipFs', () => expect(fs.readdirSync(dirname(files.zipFs))).to.include(basename(files.zipFs)))
|
||||||
|
it('readdirs from the real fs', () => expect(fs.readdirSync('.')).to.include(files.real))
|
||||||
|
it('readdirs from both', () => {
|
||||||
|
expect(fs.readdirSync('.')).to.include(files.real),
|
||||||
|
expect(fs.readdirSync('.')).to.include('test')
|
||||||
|
})
|
||||||
|
let dir
|
||||||
|
it('opendirs the zipFs fs', async () => {
|
||||||
|
expect(() => dir = fs.opendirSync(dirname(files.zipFs))).not.to.throw()
|
||||||
|
const names = []
|
||||||
|
for await (const dirent of dir) {
|
||||||
|
names.push(dirent.name)
|
||||||
|
}
|
||||||
|
expect(names).to.include(basename(files.zipFs))
|
||||||
|
})
|
||||||
|
it('opendirs the real fs and zipFs', async () => {
|
||||||
|
expect(() => dir = fs.opendirSync('.')).not.to.throw()
|
||||||
|
const names = []
|
||||||
|
for await (const dirent of dir) {
|
||||||
|
names.push(dirent.name)
|
||||||
|
}
|
||||||
|
expect(names).to.include(files.real)
|
||||||
|
expect(names).to.include('test')
|
||||||
|
})
|
||||||
|
it('createWriteStreams to the real fs', () => expect(() => fs.createWriteStream('createWriteStream')).not.to.throw())
|
||||||
|
it('createReadStreams to the real fs', () => expect(() => fs.createReadStream(files.real)).not.to.throw())
|
||||||
|
it('createReadStreams to the zipFs', () => expect(() => fs.createReadStream(files.zipFs)).not.to.throw())
|
||||||
|
it('renames to the realFs', () => {
|
||||||
|
expect(() => fs.renameSync(files.real2, 'rename')).not.to.throw()
|
||||||
|
expect(fs.existsSync('rename')).to.equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('write actions to the zipFs', () => {
|
||||||
|
it(`won't truncate to the zipFs`, () => expect(() => fs.truncateSync(files.zipFs)).to.throw())
|
||||||
|
it(`won't unlink from the zipFs`, () => expect(() => fs.unlinkSync(files.zipFs)).to.throw())
|
||||||
|
it(`won't appendFile to the zipFs`, () => expect(() => fs.appendFileSync(files.zipFs, 'xxx')).to.throw())
|
||||||
|
it(`won't symlink to the zipFs`, () => expect(() => fs.symlinkSync('tmp', files.zipFs)).to.throw())
|
||||||
|
it(`won't chmod to the zipFs`, () => expect(() => fs.chmodSync(files.zipFs, 0o666)).to.throw())
|
||||||
|
it(`won't chown to the zipFs`, () => expect(() => fs.chownSync(files.zipFs, 0, 0)).to.throw())
|
||||||
|
it(`won't writeFile the zipFs`, () => expect(() => fs.writeFileSync(files.zipFs, 'testing')).to.throw())
|
||||||
|
it(`won't createWriteStreams to the zipFs`, () => expect(() => fs.createWriteStream(files.zipFs)).to.throw())
|
||||||
|
it(`won't rename to the zipFs`, () => expect(() => fs.renameSync(files.real, files.zipFs)).to.throw())
|
||||||
|
})
|
||||||
|
|
||||||
|
;[ 'zipFs', 'real' ].forEach((k) => {
|
||||||
|
it(`watches the ${k} fs`, () => expect(() => fs.watch(files[k], () => null).close()).not.to.throw())
|
||||||
|
it(`watchFiles the ${k} fs`, () => expect(() => fs.watchFile(files[k], () => null)).not.to.throw())
|
||||||
|
it(`unwatchFiles the ${k} fs`, () => expect(() => fs.unwatchFile(files[k])).not.to.throw())
|
||||||
|
})
|
||||||
|
let buffer = Buffer.alloc(8092), fd
|
||||||
|
describe('zipfs file descriptors', () => {
|
||||||
|
it('opens files in the zipFs', () => {
|
||||||
|
expect(() => (fd = fs.openSync(files.zipFs, 'a+'))).not.to.throw()
|
||||||
|
expect(fd).to.not.be.null
|
||||||
|
})
|
||||||
|
it('reads file descriptors from the zipFs', () => {
|
||||||
|
expect(() => fs.readSync(fd, buffer)).not.to.throw()
|
||||||
|
expect(buffer.toString()).to.include('SnapshotZipFS')
|
||||||
|
})
|
||||||
|
it('fstats the zipFs', () => expect(fs.fstatSync(fd).size).to.be.greaterThan(0))
|
||||||
|
it(`won't write to the zipFs`, () => expect(() => fs.writeSync(fd, 'xxx')).to.throw())
|
||||||
|
it(`won't fchmod the zipFs`, () => expect(() => fs.fchmodSync(fd)).to.throw())
|
||||||
|
it(`won't fchown the zipFs`, () => expect(() => fs.fchownSync(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())
|
||||||
|
})
|
||||||
|
|
||||||
|
fd = null
|
||||||
|
buffer = Buffer.alloc(8092)
|
||||||
|
describe('real file descriptors', () => {
|
||||||
|
it('opens files in the real fs', () => {
|
||||||
|
expect(() => (fd = fs.openSync(files.real2, 'a+'))).not.to.throw()
|
||||||
|
expect(fd).to.not.be.null
|
||||||
|
})
|
||||||
|
it('reads file descriptors from the real', () => {
|
||||||
|
expect(() => fs.readSync(fd, buffer)).not.to.throw()
|
||||||
|
expect(buffer.toString()).to.include('SnapshotZipFS')
|
||||||
|
})
|
||||||
|
it('will write to the real fs', () => expect(() => fs.writeSync(fd, 'xxx')).not.to.throw())
|
||||||
|
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())
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('path clashes', () => {
|
||||||
|
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())
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
"target":"es2015",
|
"target":"es2015",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"lib": ["es2018"],
|
"lib": ["es2018"],
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
|
|||||||
@@ -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