Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d5eb4d2d7 | |||
| bc2c72d4e8 | |||
| e2e71023c1 | |||
| b2a26fbf16 | |||
| 4072dc6f0a | |||
| 5522ecde39 | |||
| ddc6103e43 | |||
| 3ca0d81922 | |||
| 5219a2ac74 | |||
| 2901fb8d78 | |||
| 4bd27c06ce | |||
| d13388ab4c | |||
| 138474e30c | |||
| 175bbf13fe | |||
| 2b4abb1b13 | |||
| ca657b5b06 | |||
| fdd9d97db3 | |||
| a746e00379 | |||
| 1eeac1b538 | |||
| ac1d444b17 | |||
| af7e1cb1be | |||
| ec1fcf66c6 | |||
| 51b478a18d | |||
| 95050d0472 | |||
| eb2ba5007a | |||
| e5ae6a124a |
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": ["@typescript-eslint", "prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "warn",
|
||||
"@typescript-eslint/no-empty-function": "warn",
|
||||
"no-useless-escape": "warn",
|
||||
"no-empty": "warn",
|
||||
"no-constant-condition": "warn",
|
||||
"prefer-const": "warn"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"ignorePatterns": ["lib/", "node_modules/", "*.js", "webpack.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
name: Build and test asset
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * 1' # every Monday at 10:00 UTC
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
versions:
|
||||
description: 'Node.js version(s) as JSON array'
|
||||
required: true
|
||||
default: '[ "lts/*", "22", "24" ]'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
push:
|
||||
tags: [ '*' ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ windows-latest, macos-latest, ubuntu-latest ]
|
||||
# Use the input versions if provided, otherwise fall back to default
|
||||
version: ${{ fromJSON(inputs.versions || '[ "lts/*", "22", "24" ]') }}
|
||||
env:
|
||||
NEXE_ASSET: ${{ github.workspace }}/nexe-asset
|
||||
NEXE_TMP_CACHE_PATH: ${{ github.workspace }}/nexe-tmp
|
||||
NEXE_TMP: ${{ github.workspace }}/nexe-tmp/standard
|
||||
CCACHE_COMPRESS: '1'
|
||||
ErrorView: NormalView
|
||||
steps:
|
||||
# Checkout repository
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Set up Node.js (using matrix version)
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.version }}
|
||||
|
||||
# Record the exact Node.js version for later use (cross‑platform)
|
||||
- name: Record exact Node.js version
|
||||
shell: bash
|
||||
run: echo "NODEJS_VERSION=$(node -p process.versions.node)" >> $GITHUB_ENV
|
||||
|
||||
# Install Node dependencies
|
||||
- run: npm ci
|
||||
|
||||
# Python may be needed for native addons
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.9'
|
||||
|
||||
# Windows: install NASM (required for some native modules)
|
||||
- if: matrix.os == 'windows-latest'
|
||||
run: choco install nasm
|
||||
|
||||
# Ubuntu: update package lists (kept for safety, can be removed if not needed)
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get update
|
||||
|
||||
# Linux/macOS: enable compiler caching with ccache
|
||||
- if: matrix.os != 'windows-latest'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
create-symlink: true
|
||||
key: ${{ github.job }}-${{ matrix.os }}-${{ matrix.version }}
|
||||
|
||||
# Windows: adjust asset name and suffix
|
||||
- if: matrix.os == 'windows-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "NEXE_ASSET=${NEXE_ASSET}.exe" >> $GITHUB_ENV
|
||||
echo "EXECUTABLE_SUFFIX=.exe" >> $GITHUB_ENV
|
||||
|
||||
# Main build & test (asset + integration)
|
||||
- name: Build and test asset
|
||||
run: node tasks/asset-build && node tasks/asset-test-build && npm run test:integration:run
|
||||
|
||||
# Rename the built asset to include target platform info
|
||||
- name: Update release artifact name
|
||||
shell: bash
|
||||
run: |
|
||||
nexe_target="$(node -p 'require("./lib/target").getTarget().toString()')"
|
||||
echo "NEXE_TARGET=${nexe_target}" >> $GITHUB_ENV
|
||||
artefact_name="nexe-asset-${nexe_target}"
|
||||
mv "${NEXE_ASSET}" "${artefact_name}"
|
||||
echo "NEXE_ASSET=${artefact_name}" >> $GITHUB_ENV
|
||||
|
||||
# Upload main asset
|
||||
- name: Upload asset artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.NEXE_ASSET }}
|
||||
path: ${{ env.NEXE_ASSET }}
|
||||
if-no-files-found: error
|
||||
|
||||
# Upload integration test binary
|
||||
- name: Upload integration test artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: integration-tests-${{ env.NEXE_TARGET }}${{ env.EXECUTABLE_SUFFIX }}
|
||||
path: integration-tests${{ env.EXECUTABLE_SUFFIX }}
|
||||
if-no-files-found: error
|
||||
|
||||
# If this is a tag push, attach the asset to the release
|
||||
- if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
prerelease: true
|
||||
files: ${{ env.NEXE_ASSET }}
|
||||
|
||||
# Extra step for Ubuntu: build static (musl) version
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: musl static build
|
||||
shell: bash
|
||||
run: |
|
||||
# Determine musl target and set up environment
|
||||
nexe_target="$(node -p 'require("./lib/target").getTarget({ platform: "static" }).toString()')"
|
||||
echo "NEXE_TARGET=${nexe_target}" >> $GITHUB_ENV
|
||||
artefact_name="nexe-asset-${nexe_target}"
|
||||
export NEXE_ASSET="${artefact_name}"
|
||||
echo "NEXE_ASSET=${NEXE_ASSET}" >> $GITHUB_ENV
|
||||
export MUSL_BUILD=yes NEXE_TMP="${NEXE_TMP_CACHE_PATH}/musl"
|
||||
|
||||
# Set up cross‑compiler with ccache
|
||||
eval $(npx -p node-musl musl-exports)
|
||||
export CC="ccache ${CC}" CXX="ccache ${CXX}" LD="ccache ${LD}"
|
||||
|
||||
# Build and test musl version
|
||||
node tasks/asset-build
|
||||
node tasks/asset-test-build
|
||||
npm run test:integration:run
|
||||
|
||||
# Upload musl asset
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Upload musl asset
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.NEXE_ASSET }}
|
||||
path: ${{ env.NEXE_ASSET }}
|
||||
if-no-files-found: error
|
||||
|
||||
# Upload musl integration test binary
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Upload musl integration test
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: integration-tests-${{ env.NEXE_TARGET }}${{ env.EXECUTABLE_SUFFIX }}
|
||||
path: integration-tests
|
||||
if-no-files-found: error
|
||||
|
||||
# If this is a tag push, also attach the musl asset to the release
|
||||
- if: startsWith(github.ref, 'refs/tags/') && matrix.os == 'ubuntu-latest'
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
prerelease: true
|
||||
files: ${{ env.NEXE_ASSET }}
|
||||
@@ -51,7 +51,7 @@ Additional files or resources can be added to the binary by passing `-r "glob/pa
|
||||
|
||||
## Compiling the nexe Executable
|
||||
|
||||
By default `nexe` will attempt to download a pre-built executable. These are listed on the [releases page](https://github.com/nexe/nexe/releases/tag/v3.3.3). The exact version you want may be unavailable or you may want to customize what is built. See `nexe --help` for a list of options available when passing the [`--build`](#build-boolean) option. You will also need to ensure your environment is setup to [build node](https://github.com/nodejs/node/blob/master/BUILDING.md). Note: the `python` binary in your path should be an acceptable version of python 2. eg. Systems that have python 2 will need to create a [symlink](https://github.com/nexe/nexe/issues/354#issuecomment-319874486).
|
||||
By default `nexe` will attempt to download a pre-built executable. These are listed on the [releases page](https://github.com/nexe/nexe/releases/tag/v3.3.3). The exact version you want may be unavailable or you may want to customize what is built. See `nexe --help` for a list of options available when passing the [`--build`](#build-boolean) option. You will also need to ensure your environment is setup to [build node](https://github.com/nodejs/node/blob/master/BUILDING.md). Note: the `python` binary in your path should be an acceptable version of python 3; you can create a [symlink](https://github.com/nexe/nexe/issues/354#issuecomment-319874486) or use the `--python` parameter (e.g. `nexe --build --python=$(which python3)`).
|
||||
|
||||
### Linux and macOS
|
||||
[Prerequisites & details](https://github.com/nodejs/node/blob/master/BUILDING.md#unix-and-macos)
|
||||
@@ -74,7 +74,7 @@ Install-BoxstarterPackage https://raw.githubusercontent.com/nodejs/node/master/t
|
||||
**Set config:**
|
||||
```
|
||||
npm config set msvs_version 2019
|
||||
npm config set python python2.7
|
||||
npm config set python python3.8
|
||||
```
|
||||
Where `2019` is the version of Visual Studio you have (if you have it).
|
||||
|
||||
@@ -136,6 +136,20 @@ compile({
|
||||
export function createBundle (options: NexeOptions): Promise<string>
|
||||
```
|
||||
- default: true
|
||||
- #### `bundleRules: object`
|
||||
- Configuration for the runtime module resolution inside the snapshot.
|
||||
This controls how bare module specifiers (e.g., `require('lodash')`) are resolved from the embedded `node_modules`.
|
||||
- Properties:
|
||||
- `conditions?: string[]` – The set of [conditions](https://nodejs.org/api/packages.html#conditional-exports) to use when evaluating the exports field of a package.
|
||||
- Example:
|
||||
```
|
||||
javascript
|
||||
{
|
||||
conditions: ['require', 'node', 'production'],
|
||||
basePath: '/snapshot/packages'
|
||||
}
|
||||
```
|
||||
- default: `{ conditions: ['require', 'node', 'default'], basePath: '/snapshot/node_modules' }`
|
||||
- #### `name: string`
|
||||
- Module friendly name of the application
|
||||
- default: basename of the input file, or `nexe_${Date.now()}`
|
||||
@@ -154,7 +168,7 @@ compile({
|
||||
- #### `asset: string`
|
||||
- Provide a pre-built nexe binary asset, this is a file path is resolved relative to cwd.
|
||||
- #### `python: string`
|
||||
- On Linux this is the path pointing to your python2 executable
|
||||
- On Linux this is the path pointing to your python3 executable
|
||||
- On Windows this is the directory where `python` can be accessed
|
||||
- default: `null`
|
||||
- #### `flags: string[]`
|
||||
|
||||
+7
-3
@@ -47,7 +47,7 @@ jobs:
|
||||
- script: sudo apt-get update && sudo apt-get install -y libc6-dev-i386 gcc-multilib g++-multilib
|
||||
condition: contains(variables['Agent.JobName'], 'linux')
|
||||
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')
|
||||
displayName: Install Windows build dependencies
|
||||
- script: npm install && npm run ci:build
|
||||
@@ -58,8 +58,7 @@ jobs:
|
||||
msbuild_args: /3G
|
||||
condition: not(contains(variables['Agent.JobName'], 'windows'))
|
||||
- powershell: |
|
||||
iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
|
||||
scoop install nasm
|
||||
choco install nasm
|
||||
npm install
|
||||
npm run ci:build
|
||||
displayName: Build Nexe (Windows)
|
||||
@@ -67,3 +66,8 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: $(PersonalGithubToken)
|
||||
NEXE_VERBOSE: $(NEXE_VERBOSE)
|
||||
- script: npm run test:integration
|
||||
displayName: Test built artefact
|
||||
env:
|
||||
NEXE_VERBOSE: $(NEXE_VERBOSE)
|
||||
BUILD: ''
|
||||
|
||||
Generated
+3945
-4588
File diff suppressed because it is too large
Load Diff
+38
-22
@@ -2,19 +2,24 @@
|
||||
"name": "nexe",
|
||||
"description": "Create a single executable out of your Node.js application",
|
||||
"license": "MIT",
|
||||
"version": "4.0.0-beta.19",
|
||||
"version": "5.0.0-beta",
|
||||
"contributors": [
|
||||
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
|
||||
"Jared Allard <jaredallard@outlook.com>",
|
||||
"Caleb Boyd <caleb.boyd@hotmail.com>"
|
||||
"Caleb Boyd <caleb.boyd@hotmail.com>",
|
||||
"Seth Olivarez <me@oxmc.me> (http://oxmc.me)"
|
||||
],
|
||||
"scripts": {
|
||||
"ci:build": "ts-node tasks/build",
|
||||
"test": "mocha",
|
||||
"lint": "tslint \"{src,plugins,tasks}/**/*.ts\" --fix",
|
||||
"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": "eslint \"{src,plugins,tasks}/**/*.ts\" --fix",
|
||||
"prepare": "npm run lint && npm run build && npm test",
|
||||
"prebuild": "rimraf lib",
|
||||
"build": "tsc --declaration",
|
||||
"build": "tsc --declaration && tsc -p tasks && webpack",
|
||||
"postbuild": "ts-node tasks/post-build"
|
||||
},
|
||||
"repository": {
|
||||
@@ -41,39 +46,50 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@calebboyd/semaphore": "^1.3.1",
|
||||
"@yarnpkg/fslib": "^3.0.0-rc.43",
|
||||
"@yarnpkg/libzip": "^3.0.0-rc.43",
|
||||
"app-builder": "^7.0.4",
|
||||
"archiver": "^5.3.1",
|
||||
"axios": "^1.13.2",
|
||||
"caw": "^2.0.1",
|
||||
"chalk": "^2.4.2",
|
||||
"cherow": "1.6.9",
|
||||
"download": "^8.0.0",
|
||||
"globby": "^11.0.2",
|
||||
"got": "^11.8.2",
|
||||
"minimist": "^1.2.5",
|
||||
"meriyah": "^4.3.5",
|
||||
"minimatch": "^10.1.2",
|
||||
"minimist": "^1.2.8",
|
||||
"mkdirp": "^1.0.4",
|
||||
"multistream": "^4.1.0",
|
||||
"ora": "^3.4.0",
|
||||
"resolve-dependencies": "^6.0.7",
|
||||
"rimraf": "^3.0.2"
|
||||
"resolve-dependencies": "^6.0.9",
|
||||
"rimraf": "^3.0.2",
|
||||
"run-script-os": "^1.1.6",
|
||||
"tar": "^7.5.2",
|
||||
"webpack-config-prefabs": "0.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "^5.3.2",
|
||||
"@types/chai": "^4",
|
||||
"@types/download": "^6",
|
||||
"@types/globby": "^9",
|
||||
"@types/lodash": "^4.14.192",
|
||||
"@types/minimist": "^1.2.2",
|
||||
"@types/mkdirp": "^1.0.2",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/multistream": "^2.1.1",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/multistream": "^4.1.0",
|
||||
"@types/ora": "^3.2.0",
|
||||
"@types/rimraf": "3.0.2",
|
||||
"@types/semver": "^7.3.8",
|
||||
"chai": "^4.3.4",
|
||||
"@types/semver": "^7.3.13",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"chai": "^4.3.7",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"execa": "^5.1.1",
|
||||
"mocha": "^9.1.2",
|
||||
"prettier": "^2.4.1",
|
||||
"ts-node": "^10.2.1",
|
||||
"tslint": "^6.1.1",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"tslint-plugin-prettier": "^2.3.0",
|
||||
"typescript": "^4.4.3"
|
||||
"lodash": "^4.17.21",
|
||||
"mocha": "^10.2.0",
|
||||
"prettier": "^2.8.7",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.3",
|
||||
"webpack-cli": "^5.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
+177
-139
@@ -1,8 +1,8 @@
|
||||
import { delimiter, resolve, normalize, join } from 'path'
|
||||
import { Buffer } from 'buffer'
|
||||
import { createReadStream, ReadStream } from 'fs'
|
||||
import { spawn } from 'child_process'
|
||||
import { Logger, LogStep } from './logger'
|
||||
import { delimiter, resolve, normalize, join } from "path";
|
||||
import { Buffer } from "buffer";
|
||||
import { createReadStream, ReadStream } from "fs";
|
||||
import { spawn } from "child_process";
|
||||
import { Logger, LogStep } from "./logger";
|
||||
import {
|
||||
readFileAsync,
|
||||
writeFileAsync,
|
||||
@@ -11,33 +11,33 @@ import {
|
||||
dequote,
|
||||
isWindows,
|
||||
bound,
|
||||
semverGt,
|
||||
wrap,
|
||||
} from './util'
|
||||
import { NexeOptions, version } from './options'
|
||||
import { NexeTarget } from './target'
|
||||
import MultiStream = require('multistream')
|
||||
import { Bundle, toStream } from './fs/bundle'
|
||||
import { File } from 'resolve-dependencies'
|
||||
semverGt
|
||||
} from "./util";
|
||||
import { NexeOptions, version } from "./options";
|
||||
import { NexeTarget } from "./target";
|
||||
import { Readable, Transform } from "stream";
|
||||
import MultiStream = require("multistream");
|
||||
import { Bundle, toStream } from "./fs/bundle";
|
||||
import { File } from "resolve-dependencies";
|
||||
|
||||
const isBsd = Boolean(~process.platform.indexOf('bsd'))
|
||||
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
|
||||
const configure = isWindows ? 'configure' : './configure'
|
||||
const isBsd = Boolean(~process.platform.indexOf("bsd"));
|
||||
const make = isWindows ? "vcbuild.bat" : isBsd ? "gmake" : "make";
|
||||
const configure = isWindows ? "configure" : "./configure";
|
||||
|
||||
type StringReplacer = (match: string) => string
|
||||
type StringReplacer = (match: string) => string;
|
||||
|
||||
export interface NexeFile {
|
||||
filename: string
|
||||
absPath: string
|
||||
contents: string | Buffer
|
||||
filename: string;
|
||||
absPath: string;
|
||||
contents: string | Buffer;
|
||||
}
|
||||
|
||||
export { NexeOptions }
|
||||
export { NexeOptions };
|
||||
|
||||
export class NexeError extends Error {
|
||||
constructor(m: string) {
|
||||
super(m)
|
||||
Object.setPrototypeOf(this, NexeError.prototype)
|
||||
super(m);
|
||||
Object.setPrototypeOf(this, NexeError.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,266 +45,304 @@ export class NexeCompiler {
|
||||
/**
|
||||
* Epoch of when compilation started
|
||||
*/
|
||||
private start = Date.now()
|
||||
private compileStep: LogStep | undefined
|
||||
public log = new Logger(this.options.loglevel)
|
||||
private start = Date.now();
|
||||
private compileStep: LogStep | undefined;
|
||||
public log = new Logger(this.options.loglevel);
|
||||
/**
|
||||
* Copy of process.env
|
||||
*/
|
||||
public env = { ...process.env }
|
||||
public env = { ...process.env };
|
||||
/**
|
||||
* Virtual FileSystem
|
||||
*/
|
||||
public bundle: Bundle
|
||||
public bundle: Bundle;
|
||||
/**
|
||||
* Root directory for the source of the current build
|
||||
*/
|
||||
public src: string
|
||||
public src: string;
|
||||
/**
|
||||
* In memory files that are being manipulated by the compiler
|
||||
*/
|
||||
public files: NexeFile[] = []
|
||||
public files: NexeFile[] = [];
|
||||
/**
|
||||
* Standalone pieces of code run before the application entrypoint
|
||||
*/
|
||||
public shims: string[] = []
|
||||
public shims: string[] = [];
|
||||
/**
|
||||
* The last shim (defaults to "require('module').runMain()")
|
||||
*/
|
||||
public startup: string = ''
|
||||
public startup = "";
|
||||
/**
|
||||
* The main entrypoint filename for your application - eg. node mainFile.js
|
||||
*/
|
||||
public entrypoint: string | undefined
|
||||
public entrypoint: string | undefined;
|
||||
/**
|
||||
* Not used
|
||||
*/
|
||||
public targets: NexeTarget[]
|
||||
public targets: NexeTarget[];
|
||||
/**
|
||||
* Current target of the compiler
|
||||
*/
|
||||
public target: NexeTarget
|
||||
public target: NexeTarget;
|
||||
/**
|
||||
* Output filename (-o myapp.exe)
|
||||
*/
|
||||
public output = this.options.output
|
||||
public output = this.options.output;
|
||||
/**
|
||||
* Flag to indicate whether or notstdin was used for input
|
||||
*/
|
||||
public stdinUsed = false
|
||||
public stdinUsed = false;
|
||||
/**
|
||||
* Path to the configure script
|
||||
*/
|
||||
public configureScript: string
|
||||
public configureScript: string;
|
||||
/**
|
||||
* The file path of node binary
|
||||
*/
|
||||
public nodeSrcBinPath: string
|
||||
public nodeSrcBinPath: string;
|
||||
/**
|
||||
* Remote asset path if available
|
||||
*/
|
||||
public remoteAsset: string
|
||||
public remoteAsset: string;
|
||||
|
||||
constructor(public options: NexeOptions) {
|
||||
const { python } = (this.options = options)
|
||||
const { python } = this.options;
|
||||
//SOMEDAY iterate over multiple targets with `--outDir`
|
||||
this.targets = options.targets as NexeTarget[]
|
||||
this.target = this.targets[0]
|
||||
if (!/https?\:\/\//.test(options.remote)) {
|
||||
throw new NexeError(`Invalid remote URI scheme (must be http or https): ${options.remote}`)
|
||||
this.targets = options.targets as NexeTarget[];
|
||||
this.target = this.targets[0];
|
||||
if (!/https?:\/\//.test(options.remote)) {
|
||||
throw new NexeError(
|
||||
`Invalid remote URI scheme (must be http or https): ${options.remote}`
|
||||
);
|
||||
}
|
||||
this.remoteAsset = options.remote + this.target.toString()
|
||||
this.src = join(this.options.temp, this.target.version)
|
||||
this.configureScript = configure + (semverGt(this.target.version, '10.10.0') ? '.py' : '')
|
||||
this.remoteAsset = options.remote + "nexe-asset-" + this.target.toString();
|
||||
this.src = join(this.options.temp, this.target.version);
|
||||
this.configureScript =
|
||||
configure + (semverGt(this.target.version, "10.10.0") ? ".py" : "");
|
||||
this.nodeSrcBinPath = isWindows
|
||||
? join(this.src, 'Release', 'node.exe')
|
||||
: join(this.src, 'out', 'Release', 'node')
|
||||
this.log.step('nexe ' + version, 'info')
|
||||
this.bundle = new Bundle(options)
|
||||
? join(this.src, "Release", "node.exe")
|
||||
: join(this.src, "out", "Release", "node");
|
||||
this.log.step("nexe " + version, "info");
|
||||
this.bundle = new Bundle(options);
|
||||
if (isWindows) {
|
||||
const originalPath = process.env.PATH
|
||||
delete process.env.PATH
|
||||
this.env = { ...process.env }
|
||||
const originalPath = process.env.PATH;
|
||||
delete process.env.PATH;
|
||||
this.env = { ...process.env };
|
||||
this.env.PATH = python
|
||||
? (this.env.PATH = dequote(normalize(python)) + delimiter + originalPath)
|
||||
: originalPath
|
||||
process.env.PATH = originalPath
|
||||
? (this.env.PATH =
|
||||
dequote(normalize(python)) + delimiter + originalPath)
|
||||
: originalPath;
|
||||
process.env.PATH = originalPath;
|
||||
} else {
|
||||
this.env = { ...process.env }
|
||||
python && (this.env.PYTHON = python)
|
||||
this.env = { ...process.env };
|
||||
if (python) this.env.PYTHON = python;
|
||||
}
|
||||
}
|
||||
|
||||
@bound
|
||||
addResource(absoluteFileName: string, content?: Buffer | string | File) {
|
||||
return this.bundle.addResource(absoluteFileName, content)
|
||||
return this.bundle.addResource(absoluteFileName, content);
|
||||
}
|
||||
|
||||
@bound
|
||||
async readFileAsync(file: string) {
|
||||
this.assertBuild()
|
||||
let cachedFile = this.files.find((x) => normalize(x.filename) === normalize(file))
|
||||
this.assertBuild();
|
||||
let cachedFile = this.files.find(
|
||||
(x) => normalize(x.filename) === normalize(file)
|
||||
);
|
||||
if (!cachedFile) {
|
||||
const absPath = join(this.src, file)
|
||||
const absPath = join(this.src, file);
|
||||
cachedFile = {
|
||||
absPath,
|
||||
filename: file,
|
||||
contents: await readFileAsync(absPath, 'utf-8').catch((x) => {
|
||||
if (x.code === 'ENOENT') return ''
|
||||
throw x
|
||||
contents: await readFileAsync(absPath, "utf-8").catch((x) => {
|
||||
if (x.code === "ENOENT") return "";
|
||||
throw x;
|
||||
}),
|
||||
}
|
||||
this.files.push(cachedFile)
|
||||
};
|
||||
this.files.push(cachedFile);
|
||||
}
|
||||
return cachedFile
|
||||
return cachedFile;
|
||||
}
|
||||
|
||||
@bound
|
||||
writeFileAsync(file: string, contents: string | Buffer) {
|
||||
this.assertBuild()
|
||||
return writeFileAsync(join(this.src, file), contents)
|
||||
this.assertBuild();
|
||||
return writeFileAsync(join(this.src, file), contents);
|
||||
}
|
||||
|
||||
@bound
|
||||
async replaceInFileAsync(file: string, replace: string | RegExp, value: string | StringReplacer) {
|
||||
const entry = await this.readFileAsync(file)
|
||||
entry.contents = entry.contents.toString().replace(replace, value as any)
|
||||
async replaceInFileAsync(
|
||||
file: string,
|
||||
replace: string | RegExp,
|
||||
value: string | StringReplacer
|
||||
) {
|
||||
const entry = await this.readFileAsync(file);
|
||||
entry.contents = entry.contents.toString().replace(replace, value as any);
|
||||
}
|
||||
|
||||
@bound
|
||||
async setFileContentsAsync(file: string, contents: string | Buffer) {
|
||||
const entry = await this.readFileAsync(file)
|
||||
entry.contents = contents
|
||||
const entry = await this.readFileAsync(file);
|
||||
entry.contents = contents;
|
||||
}
|
||||
|
||||
quit(error?: any) {
|
||||
const time = Date.now() - this.start
|
||||
this.log.write(`Finished in ${time / 1000}s`, error ? 'red' : 'green')
|
||||
return this.log.flush()
|
||||
const time = Date.now() - this.start;
|
||||
this.log.write(`Finished in ${time / 1000}s`, error ? "red" : "green");
|
||||
return this.log.flush();
|
||||
}
|
||||
|
||||
assertBuild() {
|
||||
if (!this.options.build) {
|
||||
throw new NexeError('This feature is only available with `--build`')
|
||||
throw new NexeError("This feature is only available with `--build`");
|
||||
}
|
||||
}
|
||||
|
||||
public getNodeExecutableLocation(target?: NexeTarget) {
|
||||
if (this.options.asset) {
|
||||
return resolve(this.options.cwd, this.options.asset)
|
||||
return resolve(this.options.cwd, this.options.asset);
|
||||
}
|
||||
if (target) {
|
||||
return join(this.options.temp, target.toString())
|
||||
return join(this.options.temp, target.toString());
|
||||
}
|
||||
return this.nodeSrcBinPath
|
||||
return this.nodeSrcBinPath;
|
||||
}
|
||||
|
||||
private _runBuildCommandAsync(command: string, args: string[]) {
|
||||
if (this.log.verbose) {
|
||||
this.compileStep!.pause()
|
||||
this.compileStep!.pause();
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
spawn(command, args, {
|
||||
cwd: this.src,
|
||||
env: this.env,
|
||||
stdio: this.log.verbose ? 'inherit' : 'ignore',
|
||||
shell: true,
|
||||
stdio: this.log.verbose ? "inherit" : "ignore",
|
||||
})
|
||||
.once('error', (e: Error) => {
|
||||
.once("error", (e: Error) => {
|
||||
if (this.log.verbose) {
|
||||
this.compileStep!.resume()
|
||||
this.compileStep!.resume();
|
||||
}
|
||||
reject(e)
|
||||
reject(e);
|
||||
})
|
||||
.once('close', (code: number) => {
|
||||
.once("close", (code: number) => {
|
||||
if (this.log.verbose) {
|
||||
this.compileStep!.resume()
|
||||
this.compileStep!.resume();
|
||||
}
|
||||
if (code != 0) {
|
||||
const error = `${command} ${args.join(' ')} exited with code: ${code}`
|
||||
reject(new NexeError(error))
|
||||
const error = `${command} ${args.join(
|
||||
" "
|
||||
)} exited with code: ${code}`;
|
||||
reject(new NexeError(error));
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _configureAsync() {
|
||||
if (isWindows && semverGt(this.target.version, '10.15.99')) {
|
||||
return Promise.resolve()
|
||||
if (isWindows && semverGt(this.target.version, "10.15.99")) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._runBuildCommandAsync(this.env.PYTHON || 'python', [
|
||||
return this._runBuildCommandAsync(this.env.PYTHON || "python", [
|
||||
this.configureScript,
|
||||
...this.options.configure,
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
public async build(): Promise<ReadStream> {
|
||||
this.compileStep!.log(
|
||||
`Configuring node build${
|
||||
this.options.configure.length ? ': ' + this.options.configure : '...'
|
||||
`Configuring node build${this.options.configure.length ? ": " + this.options.configure : "..."
|
||||
}`
|
||||
)
|
||||
await this._configureAsync()
|
||||
const buildOptions = this.options.make
|
||||
);
|
||||
await this._configureAsync();
|
||||
const buildOptions = this.options.make;
|
||||
this.compileStep!.log(
|
||||
`Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}`
|
||||
)
|
||||
await this._runBuildCommandAsync(make, buildOptions)
|
||||
return createReadStream(this.getNodeExecutableLocation())
|
||||
`Compiling Node${buildOptions.length ? " with arguments: " + buildOptions : "..."
|
||||
}`
|
||||
);
|
||||
await this._runBuildCommandAsync(make, buildOptions);
|
||||
return createReadStream(this.getNodeExecutableLocation());
|
||||
}
|
||||
|
||||
private async _shouldCompileBinaryAsync(binary: NodeJS.ReadableStream | null, location: string) {
|
||||
private async _shouldCompileBinaryAsync(
|
||||
binary: NodeJS.ReadableStream | null,
|
||||
location: string
|
||||
) {
|
||||
//SOMEDAY combine make/configure/vcBuild/and modified times of included files
|
||||
const { snapshot, build } = this.options
|
||||
const { snapshot, build } = this.options;
|
||||
|
||||
if (!binary) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
if (build && snapshot != null && (await pathExistsAsync(snapshot))) {
|
||||
const snapshotLastModified = (await statAsync(snapshot)).mtimeMs
|
||||
const binaryLastModified = (await statAsync(location)).mtimeMs
|
||||
return snapshotLastModified > binaryLastModified
|
||||
const snapshotLastModified = (await statAsync(snapshot)).mtimeMs;
|
||||
const binaryLastModified = (await statAsync(location)).mtimeMs;
|
||||
return snapshotLastModified > binaryLastModified;
|
||||
}
|
||||
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
async compileAsync(target: NexeTarget) {
|
||||
const step = (this.compileStep = this.log.step('Compiling result'))
|
||||
const build = this.options.build
|
||||
const location = this.getNodeExecutableLocation(build ? undefined : target)
|
||||
let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null
|
||||
const step = (this.compileStep = this.log.step("Compiling result"));
|
||||
const build = this.options.build;
|
||||
const location = this.getNodeExecutableLocation(build ? undefined : target);
|
||||
let binary = (await pathExistsAsync(location))
|
||||
? createReadStream(location)
|
||||
: null;
|
||||
|
||||
if (await this._shouldCompileBinaryAsync(binary, location)) {
|
||||
binary = await this.build()
|
||||
step.log('Node binary compiled')
|
||||
binary = await this.build();
|
||||
step.log("Node binary compiled");
|
||||
}
|
||||
return this._assembleDeliverable(binary!)
|
||||
return this._assembleDeliverable(binary!);
|
||||
}
|
||||
|
||||
code() {
|
||||
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) {
|
||||
return binary
|
||||
return binary;
|
||||
}
|
||||
const resources = this.bundle.renderIndex()
|
||||
this.shims.unshift(wrap(`process.__nexe = ${JSON.stringify({ resources })};\n`))
|
||||
const launchCode = this.code();
|
||||
const codeSize = Buffer.byteLength(launchCode);
|
||||
const sentinel = Buffer.from("<nexe~~sentinel>");
|
||||
|
||||
const code = this.code(),
|
||||
codeSize = Buffer.byteLength(code),
|
||||
lengths = Buffer.from(Array(16))
|
||||
|
||||
lengths.writeDoubleLE(codeSize, 0)
|
||||
lengths.writeDoubleLE(this.bundle.size, 8)
|
||||
return new (MultiStream as any)([
|
||||
let vfsSize = 0;
|
||||
// finalize() must be called before any reads — archiver won't emit chunks
|
||||
// until finalized, so calling it inside the Transform's first-chunk callback
|
||||
// creates a deadlock (Transform waits for chunks, archiver waits for finalize).
|
||||
const bundleStream = this.bundle.toStream();
|
||||
this.bundle.finalize();
|
||||
const streams = [
|
||||
binary,
|
||||
toStream(code),
|
||||
this.bundle.toStream(),
|
||||
toStream(Buffer.concat([Buffer.from('<nexe~~sentinel>'), lengths])),
|
||||
])
|
||||
toStream(launchCode),
|
||||
bundleStream.pipe(
|
||||
new Transform({
|
||||
transform: (chunk, _, cb) => {
|
||||
if (chunk) vfsSize += chunk.length;
|
||||
cb(null, chunk);
|
||||
},
|
||||
})
|
||||
),
|
||||
];
|
||||
|
||||
let done = false;
|
||||
return new MultiStream((cb) => {
|
||||
if (done) cb(null, null);
|
||||
else if (streams.length) cb(null, streams.shift() as Readable);
|
||||
else {
|
||||
done = true;
|
||||
const trailers = Buffer.alloc(16);
|
||||
trailers.writeDoubleLE(codeSize, 0);
|
||||
trailers.writeDoubleLE(vfsSize, 8);
|
||||
cb(null, toStream(Buffer.concat([sentinel, trailers])));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
import { Libzip } from "@yarnpkg/libzip";
|
||||
import {
|
||||
FakeFS,
|
||||
PortablePath,
|
||||
RmdirOptions,
|
||||
OpendirOptions,
|
||||
BasePortableFakeFS,
|
||||
Dirent,
|
||||
Dir,
|
||||
MkdirOptions,
|
||||
FSPath,
|
||||
ppath,
|
||||
npath,
|
||||
Filename,
|
||||
CustomDir,
|
||||
} from "@yarnpkg/fslib";
|
||||
import { ZipFS, ZipOpenFS } from "@yarnpkg/libzip";
|
||||
import { resolve, toNamespacedPath } from "path";
|
||||
import { constants } from "fs";
|
||||
|
||||
export type SnapshotZipFSOptions = {
|
||||
baseFs: FakeFS<PortablePath>;
|
||||
libzip: Libzip | (() => Libzip);
|
||||
zipFs: ZipFS;
|
||||
root: string;
|
||||
};
|
||||
|
||||
const uniqBy = (
|
||||
arr: Array<string | Dirent<PortablePath>>,
|
||||
pick: (...arg: any[]) => any
|
||||
) => {
|
||||
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<PortablePath>>) {
|
||||
return uniqBy(arr, (s: string | Dirent<PortablePath>) =>
|
||||
typeof s === "string" ? s : s.name
|
||||
);
|
||||
}
|
||||
|
||||
export class SnapshotZipFS extends BasePortableFakeFS {
|
||||
zipFs: ZipFS;
|
||||
baseFs: FakeFS<PortablePath>;
|
||||
root: string;
|
||||
magic: number;
|
||||
constructor(opts: SnapshotZipFSOptions) {
|
||||
super();
|
||||
this.zipFs = opts.zipFs;
|
||||
this.baseFs = opts.baseFs;
|
||||
this.root = opts.root;
|
||||
this.magic = 0x2a << 24;
|
||||
}
|
||||
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.realpathSync(p);
|
||||
}
|
||||
|
||||
realpathSync(p: PortablePath) {
|
||||
return this.makeCallSync(
|
||||
p,
|
||||
() => this.baseFs.realpathSync(p),
|
||||
(zipFs, { 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) {
|
||||
p = this.resolve(p);
|
||||
const snapshotPP = npath.toPortablePath("/snapshot");
|
||||
const pathsToTry = Array.from(
|
||||
new Set([
|
||||
p,
|
||||
resolve("/snapshot", p),
|
||||
ppath.resolve(
|
||||
snapshotPP,
|
||||
npath.toPortablePath(
|
||||
npath.relative(
|
||||
npath.fromPortablePath(this.root),
|
||||
npath.fromPortablePath(p)
|
||||
)
|
||||
)
|
||||
),
|
||||
ppath.resolve(
|
||||
snapshotPP,
|
||||
npath.toPortablePath(
|
||||
npath.relative(
|
||||
toNamespacedPath(npath.fromPortablePath(this.root)),
|
||||
toNamespacedPath(npath.fromPortablePath(p))
|
||||
)
|
||||
)
|
||||
),
|
||||
ppath.resolve(
|
||||
snapshotPP,
|
||||
npath.toPortablePath(
|
||||
npath.relative(
|
||||
npath.fromPortablePath(process.cwd()),
|
||||
npath.fromPortablePath(p)
|
||||
)
|
||||
)
|
||||
),
|
||||
ppath.resolve(
|
||||
snapshotPP,
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
// ESM imports often omit extensions (e.g. `import './lib/array'`).
|
||||
// If the path has no extension, try .js / .mjs / .cjs fallbacks.
|
||||
const basename = ppath.basename(p);
|
||||
if (!ppath.extname(basename)) {
|
||||
for (const path of pathsToTry) {
|
||||
for (const ext of [".js", ".mjs", ".cjs"] as const) {
|
||||
const candidate = npath.toPortablePath(path + ext);
|
||||
if (this.zipFs.existsSync(candidate)) {
|
||||
return { subPath: candidate };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async copyFilePromise(sourceP: PortablePath, destP: PortablePath, flags = 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 = 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<PortablePath>>>;
|
||||
async readdirPromise(
|
||||
p: PortablePath,
|
||||
opts: { withFileTypes: boolean }
|
||||
): Promise<Array<Filename> | Array<Dirent<PortablePath>>>;
|
||||
async readdirPromise(
|
||||
p: PortablePath,
|
||||
opts?: { withFileTypes?: boolean } | null
|
||||
): Promise<Array<string | Dirent<PortablePath>>> {
|
||||
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<PortablePath>> =
|
||||
await fallback().catch(() => []);
|
||||
return Promise.resolve(
|
||||
uniqReaddir(
|
||||
fallbackPaths.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<PortablePath>>;
|
||||
readdirSync(
|
||||
p: PortablePath,
|
||||
opts: { withFileTypes: boolean }
|
||||
): Array<Filename> | Array<Dirent<PortablePath>>;
|
||||
readdirSync(
|
||||
p: PortablePath,
|
||||
opts?: { withFileTypes?: boolean } | null
|
||||
): Array<string | Dirent<PortablePath>> {
|
||||
const fallback = () => {
|
||||
return this.baseFs.readdirSync(p, opts as any);
|
||||
};
|
||||
return this.makeCallSync(
|
||||
p,
|
||||
fallback,
|
||||
(zipFs, { subPath }) => {
|
||||
let fallbackPaths: Array<string | Dirent<PortablePath>> = [];
|
||||
try {
|
||||
fallbackPaths = fallback();
|
||||
} catch (e) { }
|
||||
return fallbackPaths.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 rmPromise(p: PortablePath, opts?: any) {
|
||||
return await this.baseFs.rmPromise(p, opts);
|
||||
}
|
||||
|
||||
rmSync(p: PortablePath, opts?: any) {
|
||||
return this.baseFs.rmSync(p, opts);
|
||||
}
|
||||
|
||||
async opendirPromise(p: PortablePath, opts?: OpendirOptions) {
|
||||
return this.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;
|
||||
lutimesPromise = ZipOpenFS.prototype.utimesPromise;
|
||||
lutimesSync = 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;
|
||||
}
|
||||
Vendored
+25
-17
@@ -1,20 +1,28 @@
|
||||
if (true) {
|
||||
const __nexe_patches = (process.nexe = { patches: {} }).patches
|
||||
const slice = [].slice
|
||||
{
|
||||
const __nexe_patches = (process.nexe = { patches: {} }).patches;
|
||||
const slice = [].slice;
|
||||
const __nexe_noop_patch = function (original) {
|
||||
const args = slice.call(arguments, 1)
|
||||
return original.apply(this, args)
|
||||
}
|
||||
const args = slice.call(arguments, 1);
|
||||
return original.apply(this, args);
|
||||
};
|
||||
const __nexe_patch = function (obj, method, patch) {
|
||||
const original = obj[method]
|
||||
if (!original) return
|
||||
__nexe_patches[method] = patch
|
||||
obj[method] = function() {
|
||||
const args = [original].concat(slice.call(arguments))
|
||||
return __nexe_patches[method].apply(this, args)
|
||||
}
|
||||
}
|
||||
__nexe_patch((process).binding('fs'), 'internalModuleReadFile', __nexe_noop_patch)
|
||||
__nexe_patch((process).binding('fs'), 'internalModuleReadJSON', __nexe_noop_patch)
|
||||
__nexe_patch((process).binding('fs'), 'internalModuleStat', __nexe_noop_patch)
|
||||
const original = obj[method];
|
||||
if (!original) return;
|
||||
__nexe_patches[method] = patch;
|
||||
obj[method] = function () {
|
||||
const args = [original].concat(slice.call(arguments));
|
||||
return __nexe_patches[method].apply(this, args);
|
||||
};
|
||||
};
|
||||
__nexe_patch(
|
||||
process.binding("fs"),
|
||||
"internalModuleReadFile",
|
||||
__nexe_noop_patch,
|
||||
);
|
||||
__nexe_patch(
|
||||
process.binding("fs"),
|
||||
"internalModuleReadJSON",
|
||||
__nexe_noop_patch,
|
||||
);
|
||||
__nexe_patch(process.binding("fs"), "internalModuleStat", __nexe_noop_patch);
|
||||
}
|
||||
|
||||
+30
-135
@@ -1,159 +1,54 @@
|
||||
import * as fs from 'fs'
|
||||
import { promisify } from 'util'
|
||||
import { relative } from 'path'
|
||||
import { Readable } from 'stream'
|
||||
import { argv } from '../options'
|
||||
import { File } from 'resolve-dependencies'
|
||||
import MultiStream = require('multistream')
|
||||
import { relative } from "path";
|
||||
import { Readable } from "stream";
|
||||
import { File } from "resolve-dependencies";
|
||||
import archiver from "archiver";
|
||||
|
||||
const lstat = promisify(fs.lstat),
|
||||
realpath = promisify(fs.realpath),
|
||||
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
|
||||
function makeRelativeToZip(cwd: string, path: string) {
|
||||
return "/snapshot/" + relative(cwd, path);
|
||||
}
|
||||
|
||||
export function toStream(content: Buffer | string) {
|
||||
const readable = new Readable({
|
||||
read() {
|
||||
this.push(content)
|
||||
this.push(null)
|
||||
this.push(content);
|
||||
this.push(null);
|
||||
},
|
||||
})
|
||||
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 {
|
||||
size = 0
|
||||
cwd: string
|
||||
rendered = false
|
||||
private offset = 0
|
||||
private index: { [key: string]: [number, number] } = {}
|
||||
private files: { [key: string]: File } = {}
|
||||
streams: MultiStreams = []
|
||||
cwd: string;
|
||||
files = new Set<string>();
|
||||
zip: any;
|
||||
constructor({ cwd }: { cwd: string } = { cwd: process.cwd() }) {
|
||||
this.cwd = cwd
|
||||
this.cwd = cwd;
|
||||
this.zip = archiver("zip");
|
||||
}
|
||||
|
||||
get list() {
|
||||
return Object.keys(this.files)
|
||||
return Array.from(this.files);
|
||||
}
|
||||
|
||||
public async addResource(
|
||||
public addResource(
|
||||
absoluteFileName: string,
|
||||
content?: File | Buffer | string
|
||||
): Promise<number> {
|
||||
if (this.files[absoluteFileName]) {
|
||||
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)
|
||||
) {
|
||||
const destPath = makeRelativeToZip(this.cwd, absoluteFileName);
|
||||
if (!this.files.has(destPath)) {
|
||||
if (content == null) {
|
||||
this.zip.file(absoluteFileName, { name: destPath });
|
||||
} else {
|
||||
realFiles.push(entry)
|
||||
this.zip.append(content, { name: 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
|
||||
this.files.add(destPath);
|
||||
}
|
||||
}
|
||||
|
||||
toStream() {
|
||||
return new (MultiStream as any)(this.streams)
|
||||
public finalize() {
|
||||
return this.zip.finalize();
|
||||
}
|
||||
|
||||
public toStream(): Readable {
|
||||
return this.zip;
|
||||
}
|
||||
}
|
||||
|
||||
+355
-404
@@ -1,436 +1,387 @@
|
||||
import { Stats } from 'fs'
|
||||
import { getLatestGitRelease } from '../releases'
|
||||
import { ZipFS, getLibzipSync } from "@yarnpkg/libzip";
|
||||
import { patchFs, PosixFS, NodeFS } from "@yarnpkg/fslib";
|
||||
import { SnapshotZipFS } from "./SnapshotZipFS";
|
||||
import * as assert from "assert";
|
||||
import * as constants from "constants";
|
||||
import { dirname, relative, sep } from "path";
|
||||
|
||||
export interface NexeBinary {
|
||||
blobPath: string
|
||||
resources: { [key: string]: number[] }
|
||||
export interface NexeHeader {
|
||||
blobPath: string;
|
||||
layout: {
|
||||
stat: Stats
|
||||
resourceStart: number
|
||||
contentSize?: number
|
||||
contentStart?: number
|
||||
resourceSize?: number
|
||||
}
|
||||
resourceStart: number;
|
||||
resourceSize: number;
|
||||
contentSize: number;
|
||||
contentStart: number;
|
||||
};
|
||||
}
|
||||
|
||||
let originalFsMethods: any = null
|
||||
let lazyRestoreFs = () => {}
|
||||
let originalFsMethods: any = null;
|
||||
let lazyRestoreFs = () => {};
|
||||
const patches = (process as any).nexe.patches || {};
|
||||
const originalPatches = { ...patches };
|
||||
delete (process as any).nexe;
|
||||
|
||||
// optional Win32 file namespace prefix followed by drive letter and colon
|
||||
const windowsFullPathRegex = /^(\\{2}\?\\)?([a-zA-Z]):/
|
||||
|
||||
const upcaseDriveLetter = (s: string): string =>
|
||||
s.replace(windowsFullPathRegex, (_match, ns, drive) => `${ns || ''}${drive.toUpperCase()}:`)
|
||||
|
||||
function shimFs(binary: NexeBinary, fs: any = require('fs')) {
|
||||
function shimFs(binary: NexeHeader, fs: typeof import("fs") = require("fs")) {
|
||||
if (originalFsMethods !== null) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
originalFsMethods = Object.assign({}, fs)
|
||||
const { blobPath, resources: manifest } = binary,
|
||||
{ resourceStart, stat } = binary.layout,
|
||||
directories: { [key: string]: { [key: string]: boolean } } = {},
|
||||
notAFile = '!@#$%^&*',
|
||||
isWin = process.platform.startsWith('win'),
|
||||
isString = (x: any): x is string => typeof x === 'string' || x instanceof String,
|
||||
noop = () => {},
|
||||
path = require('path'),
|
||||
winPath: (key: string) => string = isWin ? upcaseDriveLetter : (s) => s,
|
||||
baseDir = winPath(path.dirname(process.execPath))
|
||||
|
||||
let log = (_: string) => true
|
||||
let loggedManifest = false
|
||||
if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {
|
||||
log = (text: string) => {
|
||||
setupManifest()
|
||||
if (!loggedManifest) {
|
||||
process.stderr.write('[nexe] - MANIFEST' + JSON.stringify(manifest, null, 4) + '\n')
|
||||
process.stderr.write('[nexe] - DIRECTORIES' + JSON.stringify(directories, null, 4) + '\n')
|
||||
loggedManifest = true
|
||||
}
|
||||
return process.stderr.write('[nexe] - ' + text + '\n')
|
||||
originalFsMethods = Object.assign({}, fs);
|
||||
|
||||
const realFs: typeof fs = { ...fs };
|
||||
const nodeFs = new NodeFS(realFs);
|
||||
|
||||
const blob = Buffer.allocUnsafe(binary.layout.resourceSize);
|
||||
const blobFd = realFs.openSync(binary.blobPath, "r");
|
||||
const bytesRead = realFs.readSync(
|
||||
blobFd,
|
||||
blob,
|
||||
0,
|
||||
binary.layout.resourceSize,
|
||||
binary.layout.resourceStart
|
||||
);
|
||||
assert.equal(bytesRead, binary.layout.resourceSize);
|
||||
|
||||
const zipFs = new ZipFS(blob, { readOnly: true });
|
||||
const snapshotZipFS = new SnapshotZipFS({
|
||||
libzip: getLibzipSync(),
|
||||
zipFs,
|
||||
baseFs: nodeFs,
|
||||
root: dirname(process.argv[0]), // executable directory = original project root
|
||||
});
|
||||
const posixSnapshotZipFs = new PosixFS(snapshotZipFS);
|
||||
patchFs(fs, posixSnapshotZipFs); // patches global fs
|
||||
|
||||
// Capture the project root for path mapping
|
||||
const projectRoot = dirname(process.argv[0]);
|
||||
const drive = projectRoot.slice(0, 2); // e.g., "C:"
|
||||
|
||||
// Enable logging with DEBUG=nexe:require
|
||||
let log = (_: string) => true;
|
||||
if ((process.env.DEBUG || "").toLowerCase().includes("nexe:require")) {
|
||||
process.stderr.write(
|
||||
`[nexe] - FILES ${JSON.stringify(
|
||||
// @ts-ignore - accessing private property for debugging
|
||||
Array.from(zipFs.entries.keys()),
|
||||
null,
|
||||
4
|
||||
)}\n`
|
||||
);
|
||||
process.stderr.write(
|
||||
`[nexe] - DIRECTORIES ${JSON.stringify(
|
||||
// @ts-ignore - accessing private property for debugging
|
||||
Array.from(zipFs.listings.keys()),
|
||||
null,
|
||||
4
|
||||
)}\n`
|
||||
);
|
||||
log = (text: string) => process.stderr.write(`[nexe] - ${text}\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Windows path to the snapshot POSIX path.
|
||||
* Handles:
|
||||
* - Paths under the original project root (e.g., C:\Users\oxmc\Desktop\src\entry.js → /snapshot/src/entry.js)
|
||||
* - Paths that have been converted to C:\snapshot\... (from _findPath returning /snapshot/...)
|
||||
* - Already POSIX /snapshot/... paths (return as-is)
|
||||
*/
|
||||
function toSnapshotPath(filePath: string): string {
|
||||
// Already a POSIX snapshot path
|
||||
if (filePath.startsWith("/snapshot/")) {
|
||||
return filePath;
|
||||
}
|
||||
// Handle Windows-style snapshot path: e.g., C:\snapshot\src\entry.js
|
||||
if (filePath.startsWith(drive + "\\snapshot\\")) {
|
||||
return "/snapshot/" + filePath.slice(drive.length + 9).replace(/\\/g, "/");
|
||||
}
|
||||
// Path under original project root
|
||||
if (filePath.startsWith(projectRoot)) {
|
||||
const rel = relative(projectRoot, filePath);
|
||||
return "/snapshot/" + rel.replace(/\\/g, "/");
|
||||
}
|
||||
// Not mappable – return unchanged (will likely fail)
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// --- Internal Module Patches (unconditionally assigned) ---
|
||||
|
||||
function internalModuleReadFile(this: any, original: any, ...args: any[]) {
|
||||
// Strip Windows extended-length path prefix if present
|
||||
let filePath = typeof args[0] === "string" ? args[0] : "";
|
||||
if (filePath.startsWith("\\\\?\\")) {
|
||||
filePath = filePath.slice(4);
|
||||
}
|
||||
const mappedPath = toSnapshotPath(filePath);
|
||||
log(`internalModuleReadFile ${filePath} -> ${mappedPath}`);
|
||||
try {
|
||||
// Use the patched fs (which already sees the zip)
|
||||
return fs.readFileSync(mappedPath, "utf-8");
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
patches.internalModuleReadFile = internalModuleReadFile;
|
||||
|
||||
return winPath(key)
|
||||
}
|
||||
|
||||
const statTime = function () {
|
||||
return {
|
||||
atime: new Date(stat.atime),
|
||||
mtime: new Date(stat.mtime),
|
||||
ctime: new Date(stat.ctime),
|
||||
birthtime: new Date(stat.birthtime),
|
||||
}
|
||||
}
|
||||
|
||||
let BigInt: Function
|
||||
try {
|
||||
BigInt = eval('BigInt')
|
||||
} catch (ignored) {}
|
||||
|
||||
const minBlocks = Math.max(Math.ceil(stat.blksize / 512), 1)
|
||||
const createStat = function (extensions: any, options: any) {
|
||||
const stat = Object.assign(new fs.Stats(), binary.layout.stat, statTime(), extensions)
|
||||
if ('size' in extensions) {
|
||||
//Assume non adjustable allocation size for file system
|
||||
stat.blocks = Math.ceil(stat.size / stat.blksize) * minBlocks
|
||||
}
|
||||
if (options && options.bigint && typeof BigInt !== 'undefined') {
|
||||
for (const k in stat) {
|
||||
if (Object.prototype.hasOwnProperty.call(stat, k) && typeof stat[k] === 'number') {
|
||||
stat[k] = BigInt(stat[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
return stat
|
||||
}
|
||||
|
||||
const ownStat = function (filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (directories[key]) {
|
||||
let mode = binary.layout.stat.mode
|
||||
mode |= fs.constants.S_IFDIR
|
||||
mode &= ~fs.constants.S_IFREG
|
||||
return createStat({ mode, size: 0 }, options)
|
||||
}
|
||||
if (manifest[key]) {
|
||||
return createStat({ size: manifest[key][1] }, options)
|
||||
}
|
||||
}
|
||||
|
||||
const getStat = function (fn: string) {
|
||||
return function stat(filepath: string | Buffer, options: any, callback: any) {
|
||||
let stat: any
|
||||
if (typeof options === 'function') {
|
||||
callback = options
|
||||
stat = ownStat(filepath, null)
|
||||
} else {
|
||||
stat = ownStat(filepath, options)
|
||||
}
|
||||
if (stat) {
|
||||
process.nextTick(() => {
|
||||
callback(null, stat)
|
||||
})
|
||||
} else {
|
||||
return originalFsMethods[fn].apply(fs, arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeLong(filepath: string) {
|
||||
return (path as any)._makeLong && (path as any)._makeLong(filepath)
|
||||
}
|
||||
|
||||
function fileOpts(options: any) {
|
||||
return !options ? {} : isString(options) ? { encoding: options } : options
|
||||
}
|
||||
|
||||
let setupManifest = () => {
|
||||
Object.keys(manifest).forEach((filepath) => {
|
||||
const entry = manifest[filepath]
|
||||
const absolutePath = getKey(filepath)
|
||||
const longPath = makeLong(absolutePath)
|
||||
const normalizedPath = winPath(path.normalize(filepath))
|
||||
|
||||
if (!manifest[absolutePath]) {
|
||||
manifest[absolutePath] = entry
|
||||
}
|
||||
if (longPath && !manifest[longPath]) {
|
||||
manifest[longPath] = entry
|
||||
}
|
||||
if (!manifest[normalizedPath]) {
|
||||
manifest[normalizedPath] = manifest[filepath]
|
||||
}
|
||||
|
||||
let currentDir = path.dirname(absolutePath)
|
||||
let prevDir = absolutePath
|
||||
|
||||
while (currentDir !== prevDir) {
|
||||
directories[currentDir] = directories[currentDir] || {}
|
||||
directories[currentDir][path.basename(prevDir)] = true
|
||||
const longDir = makeLong(currentDir)
|
||||
if (longDir && !directories[longDir]) {
|
||||
directories[longDir] = directories[currentDir]
|
||||
}
|
||||
prevDir = currentDir
|
||||
currentDir = path.dirname(currentDir)
|
||||
}
|
||||
})
|
||||
;(manifest[notAFile] as any) = false
|
||||
;(directories[notAFile] as any) = false
|
||||
setupManifest = noop
|
||||
}
|
||||
|
||||
//naive patches intended to work for most use cases
|
||||
const nfs: any = {
|
||||
existsSync: function existsSync(filepath: string) {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (manifest[key] || directories[key]) {
|
||||
return true
|
||||
}
|
||||
return originalFsMethods.existsSync.apply(fs, arguments)
|
||||
},
|
||||
realpath: function realpath(filepath: any, options: any, cb: any): void {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (isString(filepath) && (manifest[filepath] || manifest[key])) {
|
||||
return process.nextTick(() => cb(null, filepath))
|
||||
}
|
||||
return originalFsMethods.realpath.call(fs, filepath, options, cb)
|
||||
},
|
||||
realpathSync: function realpathSync(filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const key = getKey(filepath)
|
||||
if (manifest[key]) {
|
||||
return filepath
|
||||
}
|
||||
return originalFsMethods.realpathSync.call(fs, filepath, options)
|
||||
},
|
||||
readdir: function readdir(filepath: string | Buffer, options: any, callback: any) {
|
||||
setupManifest()
|
||||
const dir = directories[getKey(filepath)]
|
||||
if (dir) {
|
||||
if ('function' === typeof options) {
|
||||
callback = options
|
||||
options = { encoding: 'utf8' }
|
||||
}
|
||||
process.nextTick(() => callback(null, Object.keys(dir)))
|
||||
} else {
|
||||
return originalFsMethods.readdir.apply(fs, arguments)
|
||||
}
|
||||
},
|
||||
readdirSync: function readdirSync(filepath: string | Buffer, options: any) {
|
||||
setupManifest()
|
||||
const dir = directories[getKey(filepath)]
|
||||
if (dir) {
|
||||
return Object.keys(dir)
|
||||
}
|
||||
return originalFsMethods.readdirSync.apply(fs, arguments)
|
||||
},
|
||||
|
||||
readFile: function readFile(filepath: any, options: any, callback: any) {
|
||||
setupManifest()
|
||||
const entry = manifest[getKey(filepath)]
|
||||
if (!entry) {
|
||||
return originalFsMethods.readFile.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const encoding = fileOpts(options).encoding
|
||||
callback = typeof options === 'function' ? options : callback
|
||||
|
||||
originalFsMethods.open(blobPath, 'r', function (err: Error, fd: number) {
|
||||
if (err) return callback(err, null)
|
||||
originalFsMethods.read(
|
||||
fd,
|
||||
Buffer.alloc(length),
|
||||
0,
|
||||
length,
|
||||
resourceOffset,
|
||||
function (error: Error, bytesRead: number, result: Buffer) {
|
||||
if (error) {
|
||||
return originalFsMethods.close(fd, function () {
|
||||
callback(error, null)
|
||||
})
|
||||
}
|
||||
originalFsMethods.close(fd, function (err: Error) {
|
||||
if (err) {
|
||||
return callback(err, result)
|
||||
}
|
||||
callback(err, encoding ? result.toString(encoding) : result)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
},
|
||||
createReadStream: function createReadStream(filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const entry = manifest[getKey(filepath)]
|
||||
if (!entry) {
|
||||
return originalFsMethods.createReadStream.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const opts = fileOpts(options)
|
||||
|
||||
return originalFsMethods.createReadStream(
|
||||
blobPath,
|
||||
Object.assign({}, opts, {
|
||||
start: resourceOffset,
|
||||
end: resourceOffset + length - 1,
|
||||
})
|
||||
)
|
||||
},
|
||||
readFileSync: function readFileSync(filepath: any, options: any) {
|
||||
setupManifest()
|
||||
const entry = manifest[getKey(filepath)]
|
||||
if (!entry) {
|
||||
return originalFsMethods.readFileSync.apply(fs, arguments)
|
||||
}
|
||||
const [offset, length] = entry
|
||||
const resourceOffset = resourceStart + offset
|
||||
const encoding = fileOpts(options).encoding
|
||||
const fd = originalFsMethods.openSync(process.execPath, 'r')
|
||||
const result = Buffer.alloc(length)
|
||||
originalFsMethods.readSync(fd, result, 0, length, resourceOffset)
|
||||
originalFsMethods.closeSync(fd)
|
||||
return encoding ? result.toString(encoding) : result
|
||||
},
|
||||
statSync: function statSync(filepath: string | Buffer, options: any) {
|
||||
const stat = ownStat(filepath, options)
|
||||
if (stat) {
|
||||
return stat
|
||||
}
|
||||
return originalFsMethods.statSync.apply(fs, arguments)
|
||||
},
|
||||
stat: getStat('stat'),
|
||||
lstat: getStat('lstat'),
|
||||
lstatSync: function statSync(filepath: string | Buffer, options: any) {
|
||||
const stat = ownStat(filepath, options)
|
||||
if (stat) {
|
||||
return stat
|
||||
}
|
||||
return originalFsMethods.lstatSync.apply(fs, arguments)
|
||||
},
|
||||
}
|
||||
if (typeof fs.exists === 'function') {
|
||||
nfs.exists = function (filepath: string, cb: Function) {
|
||||
cb = cb || noop
|
||||
const exists = nfs.existsSync(filepath)
|
||||
process.nextTick(() => cb(exists))
|
||||
}
|
||||
}
|
||||
|
||||
const patches = (process as any).nexe.patches || {}
|
||||
delete (process as any).nexe
|
||||
patches.internalModuleReadFile = function (this: any, original: any, ...args: any[]) {
|
||||
setupManifest()
|
||||
const filepath = getKey(args[0])
|
||||
if (manifest[filepath]) {
|
||||
log('read (hit) ' + filepath)
|
||||
return nfs.readFileSync(filepath, 'utf-8')
|
||||
}
|
||||
log('read (miss) ' + filepath)
|
||||
return original.call(this, ...args)
|
||||
}
|
||||
let returningArray: boolean
|
||||
// internalModuleReadJSON should return a string (or undefined) in Node 22
|
||||
patches.internalModuleReadJSON = function (this: any, original: any, ...args: any[]) {
|
||||
if (returningArray == null) returningArray = Array.isArray(original.call(this, ''))
|
||||
const res = patches.internalModuleReadFile.call(this, original, ...args)
|
||||
return returningArray && !Array.isArray(res)
|
||||
? [res, /"(main|name|type|exports|imports)"/.test(res)]
|
||||
: res
|
||||
}
|
||||
patches.internalModuleStat = function (this: any, original: any, ...args: any[]) {
|
||||
setupManifest()
|
||||
const filepath = getKey(args[0])
|
||||
if (manifest[filepath]) {
|
||||
log('stat (hit) ' + filepath + ' ' + 0)
|
||||
return 0
|
||||
const content = internalModuleReadFile.call(this, original, ...args);
|
||||
return content === "" ? undefined : content;
|
||||
};
|
||||
|
||||
// Handle Node 22+ signature: first arg is context (non-string), second is path
|
||||
function internalModuleStat(this: any, original: any, ...args: any[]) {
|
||||
const target = args[0];
|
||||
let statPath: string;
|
||||
|
||||
// Node 22+: args[0] is context (often a buffer), args[1] is the path string
|
||||
if (typeof target === "number") {
|
||||
// file descriptor case – unchanged
|
||||
try {
|
||||
originalFsMethods.fstatSync(target);
|
||||
return 0;
|
||||
} catch (e) {
|
||||
return -constants.ENOENT;
|
||||
}
|
||||
}
|
||||
if (directories[filepath]) {
|
||||
log('stat dir (hit) ' + filepath + ' ' + 1)
|
||||
return 1
|
||||
}
|
||||
const res = original.call(this, ...args)
|
||||
if (res === 0) {
|
||||
log('stat (miss) ' + filepath + ' ' + res)
|
||||
} else if (res === 1) {
|
||||
log('stat dir (miss) ' + filepath + ' ' + res)
|
||||
|
||||
if (typeof target === "string") {
|
||||
// Node <22 or when a string is passed directly
|
||||
statPath = target;
|
||||
} else {
|
||||
log('stat (fail) ' + filepath + ' ' + res)
|
||||
// Node 22+: path is the second argument
|
||||
statPath = args[1];
|
||||
}
|
||||
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)
|
||||
if (typeof statPath !== "string") {
|
||||
// Unexpected: fallback to original
|
||||
return original.call(this, ...args);
|
||||
}
|
||||
|
||||
// Strip Windows extended-length prefix
|
||||
if (statPath.startsWith("\\\\?\\")) {
|
||||
statPath = statPath.slice(4);
|
||||
}
|
||||
|
||||
const mappedPath = toSnapshotPath(statPath);
|
||||
log(`internalModuleStat ${statPath} -> ${mappedPath}`);
|
||||
|
||||
try {
|
||||
const stat = fs.statSync(mappedPath);
|
||||
return stat.isDirectory() ? 1 : 0;
|
||||
} catch (e) {
|
||||
// VFS has no entry — check the real filesystem at the original path.
|
||||
// This handles dynamically created files (e.g. written via the patched fs)
|
||||
// that don't exist in the zip.
|
||||
try {
|
||||
const realStat = originalFsMethods.statSync(statPath);
|
||||
return realStat.isDirectory() ? 1 : 0;
|
||||
} catch (e2) {
|
||||
return -constants.ENOENT;
|
||||
}
|
||||
process.nextTick(() => cb(exists))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
patches.internalModuleStat = internalModuleStat;
|
||||
|
||||
// --- Patch Module._findPath with a correct exports resolver ---
|
||||
|
||||
// Helper to check if a path exists and is a file
|
||||
function isFile(p: string): boolean {
|
||||
try {
|
||||
const stat = fs.statSync(p);
|
||||
return stat.isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Recursive exports resolver following Node's conditional exports
|
||||
function resolvePackageExports(exports: any, conditions: string[]): string | null {
|
||||
if (typeof exports === "string") {
|
||||
return exports;
|
||||
}
|
||||
if (!exports || typeof exports !== "object") {
|
||||
return null;
|
||||
}
|
||||
// Handle "." subpath first (for bare module, we always start with ".")
|
||||
const subExports = exports["."] ?? exports;
|
||||
return resolveExportsObject(subExports, conditions);
|
||||
}
|
||||
|
||||
function resolveExportsObject(obj: any, conditions: string[]): string | null {
|
||||
if (typeof obj === "string") {
|
||||
return obj;
|
||||
}
|
||||
if (!obj || typeof obj !== "object") {
|
||||
return null;
|
||||
}
|
||||
// Iterate over keys in order; if a key matches a condition, take that branch
|
||||
for (const key of Object.keys(obj)) {
|
||||
if (conditions.includes(key)) {
|
||||
const value = obj[key];
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
originalFsMethods.writeFile(dest, buffer, (err: any) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
if (value && typeof value === "object") {
|
||||
const nested = resolveExportsObject(value, conditions);
|
||||
if (nested) return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no matching condition, try "default" if not already tried
|
||||
if (!conditions.includes("default") && obj.default) {
|
||||
const def = obj.default;
|
||||
if (typeof def === "string") return def;
|
||||
if (def && typeof def === "object") {
|
||||
return resolveExportsObject(def, conditions);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveFromPkg(
|
||||
pkg: any,
|
||||
basePath: string,
|
||||
request: string,
|
||||
nodePath: any,
|
||||
logFn: (s: string) => any,
|
||||
isFileFn: (p: string) => boolean,
|
||||
fsMod: typeof import("fs")
|
||||
): string | null {
|
||||
// 1. exports field
|
||||
if (pkg.exports) {
|
||||
const conditions = ["require", "node", "default"];
|
||||
const exportTarget = resolvePackageExports(pkg.exports, conditions);
|
||||
if (exportTarget) {
|
||||
const candidate = nodePath.posix.join(basePath, request, exportTarget);
|
||||
logFn(`_findPath exports target: ${candidate}`);
|
||||
if (isFileFn(candidate)) return candidate;
|
||||
for (const ext of [".js", ".json", ".node"]) {
|
||||
const withExt = candidate + ext;
|
||||
if (isFileFn(withExt)) return withExt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. main field
|
||||
let main = "index.js";
|
||||
if (typeof pkg.main === "string") main = pkg.main;
|
||||
if (main.startsWith("./")) main = main.slice(2);
|
||||
if (!main || main === ".") main = "index.js";
|
||||
if (main.endsWith("/")) main += "index.js";
|
||||
|
||||
const mainPath = nodePath.posix.join(basePath, request, main);
|
||||
logFn(`_findPath main: ${mainPath}`);
|
||||
try {
|
||||
const st = fsMod.statSync(mainPath);
|
||||
if (st.isFile()) return mainPath;
|
||||
if (st.isDirectory()) {
|
||||
const idxPath = nodePath.posix.join(mainPath, "index.js");
|
||||
if (isFileFn(idxPath)) return idxPath;
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
for (const ext of [".js", ".json", ".node"]) {
|
||||
const withExt = mainPath + ext;
|
||||
if (isFileFn(withExt)) return withExt;
|
||||
}
|
||||
|
||||
// 3. index.js fallback only when main not explicitly set
|
||||
if (typeof pkg.main !== "string") {
|
||||
const fallbackIndex = nodePath.posix.join(basePath, request, "index.js");
|
||||
if (isFileFn(fallbackIndex)) return fallbackIndex;
|
||||
}
|
||||
|
||||
// 4. dist subdirectory (axios-style)
|
||||
const distIndex = nodePath.posix.join(basePath, request, "dist", "index.js");
|
||||
if (isFileFn(distIndex)) return distIndex;
|
||||
const distMain = nodePath.posix.join(basePath, request, "dist", request + ".js");
|
||||
if (isFileFn(distMain)) return distMain;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
let _origFindPath: ((...args: any[]) => any) | null = null;
|
||||
try {
|
||||
const Module = require("module");
|
||||
const nodePath = require("path");
|
||||
const capturedOrigFindPath = (Module as any)._findPath;
|
||||
if (typeof capturedOrigFindPath === "function") {
|
||||
_origFindPath = capturedOrigFindPath;
|
||||
(Module as any)._findPath = function nexeFindPath(this: any, ...args: any[]) {
|
||||
const request: string = args[0];
|
||||
|
||||
// For bare module specifiers, try VFS first. Node 22 removed
|
||||
// internalModuleReadJSON from process.binding('fs'), so _findPath can
|
||||
// no longer read package.json from the VFS and falls back to index.js.
|
||||
// Our code reads package.json via the patched fs.readFileSync which works.
|
||||
if (request && request[0] !== "." && !nodePath.isAbsolute(request)) {
|
||||
const basePath = "/snapshot/node_modules";
|
||||
const pkgJsonPath = nodePath.posix.join(basePath, request, "package.json");
|
||||
let pkg: any;
|
||||
try {
|
||||
pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8") as string);
|
||||
} catch (_) {
|
||||
pkg = null;
|
||||
}
|
||||
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 (pkg) {
|
||||
const vfsResult = resolveFromPkg(pkg, basePath, request, nodePath, log, isFile, fs);
|
||||
if (vfsResult) {
|
||||
log(`_findPath VFS resolved ${request} -> ${vfsResult}`);
|
||||
return vfsResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
// Fall back to original for everything else (real fs, relative, absolute)
|
||||
let result: any;
|
||||
try {
|
||||
result = capturedOrigFindPath.apply(this, args);
|
||||
} catch (_) {
|
||||
result = false;
|
||||
}
|
||||
if (result) return result;
|
||||
|
||||
Object.assign(fs, nfs)
|
||||
// Secondary VFS fallback for paths that couldn't be resolved above
|
||||
// (e.g. bare specifier where package.json wasn't found in /snapshot)
|
||||
if (!request || request[0] === "." || nodePath.isAbsolute(request)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Use snapshot node_modules as the base for all bare modules
|
||||
const basePath2 = "/snapshot/node_modules";
|
||||
const pkgJsonPath2 = nodePath.posix.join(basePath2, request, "package.json");
|
||||
log(`_findPath secondary snapshot check: ${pkgJsonPath2}`);
|
||||
let pkg2: any;
|
||||
try {
|
||||
pkg2 = JSON.parse(fs.readFileSync(pkgJsonPath2, "utf8") as string);
|
||||
} catch (_) {
|
||||
return result;
|
||||
}
|
||||
return resolveFromPkg(pkg2, basePath2, request, nodePath, log, isFile, fs) ?? result;
|
||||
};
|
||||
}
|
||||
} catch (_) {
|
||||
// Patching _findPath failed, ignore
|
||||
}
|
||||
|
||||
lazyRestoreFs = () => {
|
||||
Object.keys(nfs).forEach((key) => {
|
||||
fs[key] = originalFsMethods[key]
|
||||
})
|
||||
lazyRestoreFs = () => {}
|
||||
}
|
||||
return true
|
||||
Object.assign(fs, originalFsMethods);
|
||||
Object.assign(patches, originalPatches);
|
||||
if (_origFindPath !== null) {
|
||||
try {
|
||||
const Module = require("module");
|
||||
(Module as any)._findPath = _origFindPath;
|
||||
} catch (_) {}
|
||||
_origFindPath = null;
|
||||
}
|
||||
lazyRestoreFs = () => {};
|
||||
};
|
||||
}
|
||||
|
||||
function restoreFs() {
|
||||
lazyRestoreFs()
|
||||
lazyRestoreFs();
|
||||
}
|
||||
|
||||
export { shimFs, restoreFs }
|
||||
export { shimFs, restoreFs };
|
||||
|
||||
+38
-38
@@ -1,65 +1,65 @@
|
||||
import colors from 'chalk'
|
||||
import ora from 'ora'
|
||||
import colors from "chalk";
|
||||
import ora from "ora";
|
||||
|
||||
const frameLength = 120
|
||||
const frameLength = 120;
|
||||
|
||||
export interface LogStep {
|
||||
modify(text: string, color?: string): void
|
||||
log(text: string, color?: string): void
|
||||
pause(): void
|
||||
resume(): void
|
||||
modify(text: string, color?: string): void;
|
||||
log(text: string, color?: string): void;
|
||||
pause(): void;
|
||||
resume(): void;
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
public verbose: boolean
|
||||
private silent: boolean
|
||||
private ora: any
|
||||
private modify: Function
|
||||
public write: (text: string, color?: string) => void
|
||||
public verbose: boolean;
|
||||
private silent: boolean;
|
||||
private ora: any;
|
||||
private modify: Function;
|
||||
public write: (text: string, color?: string) => void;
|
||||
|
||||
constructor(level: 'verbose' | 'silent' | 'info') {
|
||||
this.verbose = level === 'verbose'
|
||||
this.silent = level === 'silent'
|
||||
constructor(level: "verbose" | "silent" | "info") {
|
||||
this.verbose = level === "verbose";
|
||||
this.silent = level === "silent";
|
||||
if (!this.silent) {
|
||||
this.ora = ora({
|
||||
text: 'Starting...',
|
||||
color: 'blue',
|
||||
spinner: 'dots',
|
||||
})
|
||||
this.ora.stop()
|
||||
text: "Starting...",
|
||||
color: "blue",
|
||||
spinner: "dots",
|
||||
});
|
||||
this.ora.stop();
|
||||
}
|
||||
const noop = () => {}
|
||||
this.modify = this.silent ? noop : this._modify.bind(this)
|
||||
this.write = this.silent ? noop : this._write.bind(this)
|
||||
const noop = () => { };
|
||||
this.modify = this.silent ? noop : this._modify.bind(this);
|
||||
this.write = this.silent ? noop : this._write.bind(this);
|
||||
}
|
||||
|
||||
flush() {
|
||||
!this.silent && this.ora.succeed()
|
||||
return new Promise((resolve) => setTimeout(resolve, frameLength))
|
||||
!this.silent && this.ora.succeed();
|
||||
return new Promise((resolve) => setTimeout(resolve, frameLength));
|
||||
}
|
||||
|
||||
_write(update: string, color = 'green') {
|
||||
this.ora.succeed().text = (colors as any)[color](update)
|
||||
this.ora.start()
|
||||
_write(update: string, color = "green") {
|
||||
this.ora.succeed().text = (colors as any)[color](update);
|
||||
this.ora.start();
|
||||
}
|
||||
|
||||
_modify(update: string, color = this.ora.color) {
|
||||
this.ora.text = update
|
||||
this.ora.color = color
|
||||
this.ora.text = update;
|
||||
this.ora.color = color;
|
||||
}
|
||||
|
||||
step(text: string, method: string = 'succeed'): LogStep {
|
||||
step(text: string, method = "succeed"): LogStep {
|
||||
if (this.silent) {
|
||||
return { modify() {}, log() {}, pause() {}, resume() {} }
|
||||
return { modify() { }, log() { }, pause() { }, resume() { } };
|
||||
}
|
||||
if (!this.ora.id) {
|
||||
this.ora.start().text = text
|
||||
if (method !== 'succeed') {
|
||||
this.ora[method]()
|
||||
this.ora.start().text = text;
|
||||
if (method !== "succeed") {
|
||||
this.ora[method]();
|
||||
}
|
||||
} else {
|
||||
this.ora[method]().text = text
|
||||
this.ora.start()
|
||||
this.ora[method]().text = text;
|
||||
this.ora.start();
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -67,6 +67,6 @@ export class Logger {
|
||||
log: this.verbose ? this.write : this.modify,
|
||||
pause: () => this.ora.stopAndPersist(),
|
||||
resume: () => this.ora.start(),
|
||||
} as LogStep
|
||||
} as LogStep;
|
||||
}
|
||||
}
|
||||
|
||||
+25
-24
@@ -1,15 +1,14 @@
|
||||
import { EOL } from 'os'
|
||||
import { compose } from 'app-builder'
|
||||
import { NexeCompiler, NexeError } from './compiler'
|
||||
import { normalizeOptions, NexeOptions, NexePatch } from './options'
|
||||
import resource from './steps/resource'
|
||||
import clean from './steps/clean'
|
||||
import cli from './steps/cli'
|
||||
import bundle from './steps/bundle'
|
||||
import download from './steps/download'
|
||||
import shim from './steps/shim'
|
||||
import artifacts from './steps/artifacts'
|
||||
import patches from './patches'
|
||||
import { compose } from "app-builder";
|
||||
import { NexeCompiler } from "./compiler";
|
||||
import { normalizeOptions, NexeOptions, NexePatch } from "./options";
|
||||
import resource from "./steps/resource";
|
||||
import clean from "./steps/clean";
|
||||
import cli from "./steps/cli";
|
||||
import bundle from "./steps/bundle";
|
||||
import download from "./steps/download";
|
||||
import shim from "./steps/shim";
|
||||
import artifacts from "./steps/artifacts";
|
||||
import patches from "./patches";
|
||||
|
||||
async function compile(
|
||||
compilerOptions?: Partial<NexeOptions>,
|
||||
@@ -17,11 +16,11 @@ async function compile(
|
||||
) {
|
||||
let error: Error | null = null,
|
||||
options: NexeOptions | null = null,
|
||||
compiler: NexeCompiler | null = null
|
||||
compiler: NexeCompiler | null = null;
|
||||
|
||||
try {
|
||||
options = normalizeOptions(compilerOptions)
|
||||
compiler = new NexeCompiler(options)
|
||||
options = normalizeOptions(compilerOptions);
|
||||
compiler = new NexeCompiler(options);
|
||||
await compose(
|
||||
clean,
|
||||
resource,
|
||||
@@ -29,21 +28,23 @@ async function compile(
|
||||
bundle,
|
||||
shim,
|
||||
download,
|
||||
options.build ? [artifacts, ...patches, ...(options.patches as NexePatch[])] : [],
|
||||
options.build
|
||||
? [artifacts, ...patches, ...(options.patches as NexePatch[])]
|
||||
: [],
|
||||
options.plugins as NexePatch[]
|
||||
)(compiler)
|
||||
)(compiler);
|
||||
} catch (e: any) {
|
||||
error = e
|
||||
error = e;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
compiler && compiler.quit(error)
|
||||
if (callback) return callback(error)
|
||||
return Promise.reject(error)
|
||||
compiler && compiler.quit(error);
|
||||
if (callback) return callback(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (callback) callback(null)
|
||||
if (callback) callback(null);
|
||||
}
|
||||
|
||||
export { compile, NexeCompiler }
|
||||
export { argv, version, NexeOptions, help } from './options'
|
||||
export { compile, NexeCompiler };
|
||||
export { argv, version, NexeOptions, help } from "./options";
|
||||
|
||||
+177
-156
@@ -1,61 +1,72 @@
|
||||
import * as parseArgv from 'minimist'
|
||||
import { NexeCompiler, NexeError } from './compiler'
|
||||
import { isWindows, STDIN_FLAG } from './util'
|
||||
import { basename, extname, join, isAbsolute, resolve } from 'path'
|
||||
import { getTarget, NexeTarget } from './target'
|
||||
import { EOL, homedir } from 'os'
|
||||
import chalk from 'chalk'
|
||||
import { resolveSync } from 'resolve-dependencies'
|
||||
const caw = require('caw')
|
||||
const c = process.platform === 'win32' ? chalk.constructor({ enabled: false }) : chalk
|
||||
import parseArgv from "minimist";
|
||||
import { NexeCompiler, NexeError } from "./compiler";
|
||||
import { isWindows, STDIN_FLAG } from "./util";
|
||||
import { basename, extname, join, isAbsolute, resolve } from "path";
|
||||
import { getTarget, NexeTarget } from "./target";
|
||||
import { EOL, homedir } from "os";
|
||||
import chalk from "chalk";
|
||||
import { resolveSync } from "resolve-dependencies";
|
||||
const caw = require("caw");
|
||||
const c =
|
||||
process.platform === "win32" ? chalk.constructor({ enabled: false }) : chalk;
|
||||
|
||||
export const version = '{{replace:0}}'
|
||||
export const version = "{{ version }}";
|
||||
|
||||
export interface NexePatch {
|
||||
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
|
||||
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ModuleBundleRule {
|
||||
include?: string[];
|
||||
exclude?: string[];
|
||||
}
|
||||
|
||||
export interface BundleRules {
|
||||
[moduleName: string]: ModuleBundleRule;
|
||||
}
|
||||
|
||||
export interface NexeOptions {
|
||||
build: boolean
|
||||
input: string
|
||||
output: string
|
||||
targets: (string | NexeTarget)[]
|
||||
name: string
|
||||
remote: string
|
||||
asset: string
|
||||
cwd: string
|
||||
fs: boolean | string[]
|
||||
flags: string[]
|
||||
configure: string[]
|
||||
vcBuild: string[]
|
||||
make: string[]
|
||||
snapshot?: string
|
||||
resources: string[]
|
||||
temp: string
|
||||
rc: { [key: string]: string }
|
||||
enableNodeCli: boolean
|
||||
bundle: boolean | string
|
||||
patches: (string | NexePatch)[]
|
||||
plugins: (string | NexePatch)[]
|
||||
native: any
|
||||
mangle: boolean
|
||||
ghToken: string
|
||||
sourceUrl?: string
|
||||
enableStdIn?: boolean
|
||||
python?: string
|
||||
loglevel: 'info' | 'silent' | 'verbose'
|
||||
silent?: boolean
|
||||
fakeArgv?: boolean
|
||||
verbose?: boolean
|
||||
info?: boolean
|
||||
ico?: string
|
||||
debugBundle?: boolean
|
||||
warmup?: string
|
||||
clean?: boolean
|
||||
build: boolean;
|
||||
input: string;
|
||||
output: string;
|
||||
targets: (string | NexeTarget)[];
|
||||
name: string;
|
||||
remote: string;
|
||||
asset: string;
|
||||
cwd: string;
|
||||
fs: boolean | string[];
|
||||
flags: string[];
|
||||
configure: string[];
|
||||
vcBuild: string[];
|
||||
make: string[];
|
||||
snapshot?: string;
|
||||
resources: string[];
|
||||
temp: string;
|
||||
rc: { [key: string]: string };
|
||||
enableNodeCli: boolean;
|
||||
bundle: boolean | string;
|
||||
bundleRules?: BundleRules;
|
||||
patches: (string | NexePatch)[];
|
||||
plugins: (string | NexePatch)[];
|
||||
native: any;
|
||||
mangle: boolean;
|
||||
ghToken: string;
|
||||
sourceUrl?: string;
|
||||
enableStdIn?: boolean;
|
||||
python?: string;
|
||||
loglevel: "info" | "silent" | "verbose";
|
||||
silent?: boolean;
|
||||
fakeArgv?: boolean;
|
||||
verbose?: boolean;
|
||||
info?: boolean;
|
||||
ico?: string;
|
||||
debugBundle?: boolean;
|
||||
warmup?: string;
|
||||
clean?: boolean;
|
||||
/**
|
||||
* Api Only
|
||||
*/
|
||||
downloadOptions: any
|
||||
downloadOptions: any;
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
@@ -66,37 +77,37 @@ const defaults = {
|
||||
mangle: true,
|
||||
make: [],
|
||||
targets: [],
|
||||
vcBuild: isWindows ? ['nosign', 'release'] : [],
|
||||
vcBuild: isWindows ? ["nosign", "release"] : [],
|
||||
enableNodeCli: false,
|
||||
build: false,
|
||||
bundle: true,
|
||||
patches: [],
|
||||
plugins: [],
|
||||
remote: 'https://github.com/nexe/nexe/releases/download/v3.3.3/',
|
||||
}
|
||||
remote: `https://github.com/oxmc/nexe/releases/download/v${require('../package.json').version}/`,
|
||||
};
|
||||
const alias = {
|
||||
i: 'input',
|
||||
o: 'output',
|
||||
v: 'version',
|
||||
a: 'asset',
|
||||
t: 'target',
|
||||
b: 'build',
|
||||
n: 'name',
|
||||
r: 'resource',
|
||||
p: 'python',
|
||||
f: 'flag',
|
||||
c: 'configure',
|
||||
m: 'make',
|
||||
h: 'help',
|
||||
l: 'loglevel',
|
||||
'fake-argv': 'fakeArgv',
|
||||
'gh-token': 'ghToken',
|
||||
}
|
||||
const argv = parseArgv(process.argv, { alias, default: { ...defaults } })
|
||||
i: "input",
|
||||
o: "output",
|
||||
v: "version",
|
||||
a: "asset",
|
||||
t: "target",
|
||||
b: "build",
|
||||
n: "name",
|
||||
r: "resource",
|
||||
p: "python",
|
||||
f: "flag",
|
||||
c: "configure",
|
||||
m: "make",
|
||||
h: "help",
|
||||
l: "loglevel",
|
||||
"fake-argv": "fakeArgv",
|
||||
"gh-token": "ghToken",
|
||||
};
|
||||
const argv = parseArgv(process.argv, { alias, default: { ...defaults } });
|
||||
let help = `
|
||||
${c.bold('nexe <entry-file> [options]')}
|
||||
${c.bold("nexe <entry-file> [options]")}
|
||||
|
||||
${c.underline.bold('Options:')}
|
||||
${c.underline.bold("Options:")}
|
||||
|
||||
-i --input -- application entry point
|
||||
-o --output -- path to output file
|
||||
@@ -106,10 +117,10 @@ ${c.bold('nexe <entry-file> [options]')}
|
||||
--remote -- alternate location (URL) to download pre-built base (nexe) binaries from
|
||||
--plugin -- extend nexe runtime behavior
|
||||
|
||||
${c.underline.bold('Building from source:')}
|
||||
${c.underline.bold("Building from source:")}
|
||||
|
||||
-b --build -- build from source
|
||||
-p --python -- python2 (as python) executable path
|
||||
-p --python -- python3 (as python) executable path
|
||||
-f --flag -- *v8 flags to include during compilation
|
||||
-c --configure -- *arguments to the configure step
|
||||
-m --make -- *arguments to the make/build step
|
||||
@@ -121,7 +132,7 @@ ${c.bold('nexe <entry-file> [options]')}
|
||||
--sourceUrl -- pass an alternate source (node.tar.gz) url
|
||||
--enableNodeCli -- enable node cli enforcement (blocks app cli)
|
||||
|
||||
${c.underline.bold('Other options:')}
|
||||
${c.underline.bold("Other options:")}
|
||||
|
||||
--bundle -- custom bundling module with 'createBundle' export
|
||||
--temp -- temp file storage default '~/.nexe'
|
||||
@@ -131,11 +142,11 @@ ${c.bold('nexe <entry-file> [options]')}
|
||||
--silent -- disable logging
|
||||
--verbose -- set logging to verbose
|
||||
|
||||
-* variable key name * option can be used more than once`.trim()
|
||||
help = EOL + help + EOL
|
||||
-* variable key name * option can be used more than once`.trim();
|
||||
help = EOL + help + EOL;
|
||||
|
||||
function flatten(...args: any[]): string[] {
|
||||
return ([] as string[]).concat(...args).filter((x) => x)
|
||||
return ([] as string[]).concat(...args).filter((x) => x);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,48 +159,48 @@ function extractCliMap(match: RegExp, options: any) {
|
||||
return Object.keys(options)
|
||||
.filter((x) => match.test(x))
|
||||
.reduce((map: any, option: any) => {
|
||||
const key = option.split('-')[1]
|
||||
map[key] = options[option]
|
||||
delete options[option]
|
||||
return map
|
||||
}, {} as any)
|
||||
const key = option.split("-")[1];
|
||||
map[key] = options[option];
|
||||
delete options[option];
|
||||
return map;
|
||||
}, {} as any);
|
||||
}
|
||||
|
||||
function extractLogLevel(options: NexeOptions) {
|
||||
if (options.loglevel) return options.loglevel
|
||||
if (options.silent) return 'silent'
|
||||
if (options.verbose) return 'verbose'
|
||||
return 'info'
|
||||
if (options.loglevel) return options.loglevel;
|
||||
if (options.silent) return "silent";
|
||||
if (options.verbose) return "verbose";
|
||||
return "info";
|
||||
}
|
||||
|
||||
function isName(name: string) {
|
||||
return name && name !== 'index' && name !== STDIN_FLAG
|
||||
return name && name !== "index" && name !== STDIN_FLAG;
|
||||
}
|
||||
|
||||
function extractName(options: NexeOptions) {
|
||||
let name = options.name
|
||||
let name = options.name;
|
||||
//try and use the input filename as the output filename if its not index
|
||||
if (!isName(name) && typeof options.input === 'string') {
|
||||
name = basename(options.input).replace(extname(options.input), '')
|
||||
if (!isName(name) && typeof options.input === "string") {
|
||||
name = basename(options.input).replace(extname(options.input), "");
|
||||
}
|
||||
//try and use the directory as the filename
|
||||
if (!isName(name) && basename(options.cwd)) {
|
||||
name = basename(options.cwd)
|
||||
name = basename(options.cwd);
|
||||
}
|
||||
|
||||
return name.replace(/\.exe$/, '')
|
||||
return name.replace(/\.exe$/, "");
|
||||
}
|
||||
|
||||
function padRelative(input: string) {
|
||||
let prefix = ''
|
||||
if (!input.startsWith('.')) {
|
||||
prefix = './'
|
||||
let prefix = "";
|
||||
if (!input.startsWith(".")) {
|
||||
prefix = "./";
|
||||
}
|
||||
return prefix + input
|
||||
return prefix + input;
|
||||
}
|
||||
|
||||
function isEntryFile(filename?: string): filename is string {
|
||||
return Boolean(filename && !isAbsolute(filename))
|
||||
return Boolean(filename && !isAbsolute(filename));
|
||||
}
|
||||
|
||||
export function resolveEntry(
|
||||
@@ -198,107 +209,117 @@ export function resolveEntry(
|
||||
maybeEntry: string | undefined,
|
||||
bundle: boolean | string
|
||||
) {
|
||||
let result = null
|
||||
if (input === '-' || maybeEntry === '-') {
|
||||
return STDIN_FLAG
|
||||
let result = null;
|
||||
if (input === "-" || maybeEntry === "-") {
|
||||
return STDIN_FLAG;
|
||||
}
|
||||
if (input && isAbsolute(input)) {
|
||||
return input
|
||||
return input;
|
||||
}
|
||||
if (input) {
|
||||
const inputPath = padRelative(input)
|
||||
result = resolveSync(cwd, inputPath)
|
||||
const inputPath = padRelative(input);
|
||||
result = resolveSync(cwd, inputPath);
|
||||
}
|
||||
if (isEntryFile(maybeEntry) && (!result || !result.absPath)) {
|
||||
const inputPath = padRelative(maybeEntry)
|
||||
result = resolveSync(cwd, inputPath)
|
||||
const inputPath = padRelative(maybeEntry);
|
||||
result = resolveSync(cwd, inputPath);
|
||||
}
|
||||
if (!process.stdin.isTTY && (!result || !result.absPath) && bundle === defaults.bundle) {
|
||||
return STDIN_FLAG
|
||||
if (
|
||||
!process.stdin.isTTY &&
|
||||
(!result || !result.absPath) &&
|
||||
bundle === defaults.bundle
|
||||
) {
|
||||
return STDIN_FLAG;
|
||||
}
|
||||
if (!result || !result.absPath) {
|
||||
result = resolveSync(cwd, '.')
|
||||
result = resolveSync(cwd, ".");
|
||||
}
|
||||
if (!result.absPath) {
|
||||
throw new NexeError(`Entry file "${input || ''}" not found!`)
|
||||
throw new NexeError(`Entry file "${input || ""}" not found!`);
|
||||
}
|
||||
return result.absPath
|
||||
return result.absPath;
|
||||
}
|
||||
|
||||
function isCli(options?: Partial<NexeOptions>) {
|
||||
return argv === options
|
||||
return argv === options;
|
||||
}
|
||||
|
||||
function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
|
||||
const options = Object.assign({}, defaults, input) as NexeOptions
|
||||
const opts = options as any
|
||||
const cwd = (options.cwd = resolve(options.cwd))
|
||||
const options = Object.assign({}, defaults, input) as NexeOptions;
|
||||
const opts = options as any;
|
||||
const cwd = (options.cwd = resolve(options.cwd));
|
||||
options.temp = options.temp
|
||||
? resolve(cwd, options.temp)
|
||||
: process.env.NEXE_TEMP || join(homedir(), '.nexe')
|
||||
const maybeEntry = isCli(input) ? argv._[argv._.length - 1] : undefined
|
||||
options.input = resolveEntry(options.input, cwd, maybeEntry, options.bundle)
|
||||
options.enableStdIn = isCli(input) && options.input === STDIN_FLAG
|
||||
options.name = extractName(options)
|
||||
options.loglevel = extractLogLevel(options)
|
||||
options.flags = flatten(opts.flag, options.flags)
|
||||
options.targets = flatten(opts.target, options.targets).map(getTarget)
|
||||
: process.env.NEXE_TEMP || join(homedir(), ".nexe");
|
||||
const maybeEntry = isCli(input) ? argv._[argv._.length - 1] : undefined;
|
||||
options.input = resolveEntry(options.input, cwd, maybeEntry, options.bundle);
|
||||
options.enableStdIn = isCli(input) && options.input === STDIN_FLAG;
|
||||
options.name = extractName(options);
|
||||
options.loglevel = extractLogLevel(options);
|
||||
options.flags = flatten(opts.flag, options.flags);
|
||||
options.targets = flatten(opts.target, options.targets).map(getTarget);
|
||||
if (!options.targets.length) {
|
||||
options.targets.push(getTarget())
|
||||
options.targets.push(getTarget());
|
||||
}
|
||||
options.ghToken = options.ghToken || process.env.GITHUB_TOKEN || ''
|
||||
options.make = flatten(isWindows ? options.vcBuild : options.make)
|
||||
options.configure = flatten(options.configure)
|
||||
options.resources = flatten(opts.resource, options.resources)
|
||||
options.ghToken = options.ghToken || process.env.GITHUB_TOKEN || "";
|
||||
options.make = flatten(isWindows ? options.vcBuild : options.make);
|
||||
options.configure = flatten(options.configure);
|
||||
options.resources = flatten(opts.resource, options.resources);
|
||||
|
||||
if (!options.remote.endsWith('/')) {
|
||||
options.remote += '/'
|
||||
if (!options.remote.endsWith("/")) {
|
||||
options.remote += "/";
|
||||
}
|
||||
|
||||
options.downloadOptions = options.downloadOptions || {}
|
||||
options.downloadOptions.headers = options.downloadOptions.headers || {}
|
||||
options.downloadOptions.headers['User-Agent'] = 'nexe (https://www.npmjs.com/package/nexe)'
|
||||
options.downloadOptions = options.downloadOptions || {};
|
||||
options.downloadOptions.headers = options.downloadOptions.headers || {};
|
||||
options.downloadOptions.headers["User-Agent"] =
|
||||
"nexe (https://www.npmjs.com/package/nexe)";
|
||||
options.downloadOptions.agent = process.env.HTTPS_PROXY
|
||||
? caw(process.env.HTTPS_PROXY, { protocol: 'https' })
|
||||
: options.downloadOptions.agent || require('https').globalAgent
|
||||
options.downloadOptions.rejectUnauthorized = process.env.HTTPS_PROXY ? false : true
|
||||
? caw(process.env.HTTPS_PROXY, { protocol: "https" })
|
||||
: options.downloadOptions.agent || require("https").globalAgent;
|
||||
options.downloadOptions.rejectUnauthorized = process.env
|
||||
.NODE_TLS_REJECT_UNAUTHORIZED
|
||||
? false
|
||||
: true;
|
||||
|
||||
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
|
||||
options.rc = options.rc || extractCliMap(/^rc-.*/, options);
|
||||
options.output =
|
||||
(options.targets[0] as NexeTarget).platform === 'windows'
|
||||
? `${(options.output || options.name).replace(/\.exe$/, '')}.exe`
|
||||
: `${options.output || options.name}`
|
||||
options.output = resolve(cwd, options.output)
|
||||
(options.targets[0] as NexeTarget).platform === "windows"
|
||||
? `${(options.output || options.name).replace(/\.exe$/, "")}.exe`
|
||||
: `${options.output || options.name}`;
|
||||
options.output = resolve(cwd, options.output);
|
||||
|
||||
const requireDefault = (x: string) => {
|
||||
if (typeof x === 'string') {
|
||||
return require(x).default
|
||||
if (typeof x === "string") {
|
||||
return require(x).default;
|
||||
}
|
||||
return x
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
options.mangle = 'mangle' in opts ? opts.mangle : true
|
||||
options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault)
|
||||
options.patches = flatten(opts.patch, options.patches).map(requireDefault)
|
||||
options.mangle = "mangle" in opts ? opts.mangle : true;
|
||||
options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault);
|
||||
options.patches = flatten(opts.patch, options.patches).map(requireDefault);
|
||||
|
||||
if ((!options.mangle && !options.bundle) || options.patches.length) {
|
||||
options.build = true
|
||||
options.build = true;
|
||||
}
|
||||
|
||||
if (options.build) {
|
||||
const { arch } = options.targets[0] as NexeTarget
|
||||
const { arch } = options.targets[0] as NexeTarget;
|
||||
if (isWindows) {
|
||||
options.make = Array.from(new Set(options.make.concat(arch)))
|
||||
options.make = Array.from(new Set(options.make.concat(arch)));
|
||||
} else {
|
||||
options.configure = Array.from(new Set(options.configure.concat([`--dest-cpu=${arch}`])))
|
||||
options.configure = Array.from(
|
||||
new Set(options.configure.concat([`--dest-cpu=${arch}`]))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(alias)
|
||||
.filter((k) => k !== 'rc')
|
||||
.forEach((x) => delete opts[x])
|
||||
.filter((k) => k !== "rc")
|
||||
.forEach((x) => delete opts[x]);
|
||||
|
||||
return options
|
||||
return options;
|
||||
}
|
||||
|
||||
export { argv, normalizeOptions, help }
|
||||
export { argv, normalizeOptions, help };
|
||||
|
||||
+68
-26
@@ -1,34 +1,57 @@
|
||||
const fs = require('fs'),
|
||||
fd = fs.openSync(process.execPath, 'r'),
|
||||
stat = fs.statSync(process.execPath),
|
||||
const fs = require("fs"),
|
||||
fd = fs.openSync(process.execPath, "r"),
|
||||
stat = fs.fstatSync(fd),
|
||||
tailSize = Math.min(stat.size, 16000),
|
||||
tailWindow = Buffer.from(Array(tailSize))
|
||||
tailWindow = Buffer.alloc(tailSize),
|
||||
match = "<nexe" + "~~sentinel>",
|
||||
matchLength = match.length,
|
||||
lastBuffer = Buffer.alloc(matchLength + 32);
|
||||
|
||||
fs.readSync(fd, tailWindow, 0, tailSize, stat.size - tailSize)
|
||||
let offset = stat.size,
|
||||
footerPosition = -1,
|
||||
footerPositionOffset = 0,
|
||||
footer: Buffer;
|
||||
|
||||
const footerPosition = tailWindow.indexOf('<nexe~~sentinel>')
|
||||
if (footerPosition == -1) {
|
||||
throw 'Invalid Nexe binary'
|
||||
while (true) {
|
||||
const bytesRead = fs.readSync(fd, tailWindow, 0, tailSize, offset - tailSize);
|
||||
if (bytesRead === 0) break;
|
||||
|
||||
const combinedBuffers = Buffer.concat([tailWindow, lastBuffer]);
|
||||
footerPosition = combinedBuffers.indexOf(match);
|
||||
|
||||
if (footerPosition > -1) {
|
||||
footer = combinedBuffers.slice(footerPosition, footerPosition + 32);
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset < 0) break;
|
||||
|
||||
tailWindow.copy(lastBuffer);
|
||||
offset = offset - bytesRead;
|
||||
}
|
||||
|
||||
const footer = tailWindow.slice(footerPosition, footerPosition + 32),
|
||||
contentSize = footer.readDoubleLE(16),
|
||||
resourceSize = footer.readDoubleLE(24),
|
||||
contentStart = stat.size - tailSize + footerPosition - resourceSize - contentSize,
|
||||
resourceStart = contentStart + contentSize
|
||||
if (footerPosition == -1) {
|
||||
throw "Invalid Nexe binary";
|
||||
}
|
||||
|
||||
const contentSize = footer!.readDoubleLE(16),
|
||||
resourceSize = footer!.readDoubleLE(24),
|
||||
contentStart =
|
||||
offset - tailSize + footerPosition - resourceSize - contentSize,
|
||||
resourceStart = contentStart + contentSize;
|
||||
|
||||
Object.defineProperty(
|
||||
process,
|
||||
'__nexe',
|
||||
"__nexe",
|
||||
(function () {
|
||||
let nexeHeader: any = null
|
||||
let nexeHeader: any = null;
|
||||
return {
|
||||
get: function () {
|
||||
return nexeHeader
|
||||
return nexeHeader;
|
||||
},
|
||||
set: function (value: any) {
|
||||
if (nexeHeader) {
|
||||
throw new Error('This property is readonly')
|
||||
throw new Error("This property is readonly");
|
||||
}
|
||||
nexeHeader = Object.assign({}, value, {
|
||||
blobPath: process.execPath,
|
||||
@@ -39,19 +62,38 @@ Object.defineProperty(
|
||||
resourceSize,
|
||||
resourceStart,
|
||||
},
|
||||
})
|
||||
Object.freeze(nexeHeader)
|
||||
});
|
||||
Object.freeze(nexeHeader);
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
}
|
||||
};
|
||||
})()
|
||||
)
|
||||
);
|
||||
|
||||
const contentBuffer = Buffer.from(Array(contentSize)),
|
||||
Module = require('module')
|
||||
const contentBuffer = Buffer.alloc(contentSize),
|
||||
Module = require("module");
|
||||
|
||||
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart)
|
||||
fs.closeSync(fd)
|
||||
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart);
|
||||
fs.closeSync(fd);
|
||||
|
||||
new Module(process.execPath, null)._compile(contentBuffer.toString(), process.execPath)
|
||||
// Node 24 changed finalizeResolution in the ESM loader to no longer catch ENOENT
|
||||
// from realpathSync. VFS paths don't exist on real FS, so binding.lstat (called
|
||||
// internally by realpathSync) throws ENOENT and crashes ESM imports.
|
||||
// The ESM resolver captures realpathSync via destructuring at load time, so
|
||||
// patchFs (which runs later) doesn't reach it. We install an ENOENT-tolerant
|
||||
// wrapper here, before any ESM code can load, so the resolver captures our version.
|
||||
{
|
||||
const _origRpSync: any = fs.realpathSync;
|
||||
const nexeRpSync = function(p: string, opts?: any): string {
|
||||
try { return _origRpSync(p, opts); }
|
||||
catch (e: any) { if (e?.code === 'ENOENT') return p; throw e; }
|
||||
};
|
||||
(nexeRpSync as any).native = _origRpSync.native;
|
||||
(fs as any).realpathSync = nexeRpSync;
|
||||
}
|
||||
|
||||
new Module(process.execPath, null)._compile(
|
||||
contentBuffer.slice(1).toString(),
|
||||
process.execPath
|
||||
);
|
||||
|
||||
+11
-10
@@ -1,17 +1,18 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { NexeCompiler } from "../compiler";
|
||||
|
||||
export default async function buildFixes(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
if (!compiler.target.version.startsWith('8.2')) {
|
||||
return next()
|
||||
export default async function buildFixes(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
if (!compiler.target.version.startsWith("8.2")) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const file = await compiler.readFileAsync('./tools/msvs/find_python.cmd')
|
||||
|
||||
await compiler.replaceInFileAsync(
|
||||
'./tools/msvs/find_python.cmd',
|
||||
'%p%python.exe -V 2>&1',
|
||||
"./tools/msvs/find_python.cmd",
|
||||
"%p%python.exe -V 2>&1",
|
||||
'"%p%python.exe" -V 2>&1'
|
||||
)
|
||||
);
|
||||
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -1,44 +1,61 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { semverGt } from '../util'
|
||||
import { NexeCompiler } from "../compiler";
|
||||
import { semverGt } from "../util";
|
||||
|
||||
export default async function disableNodeCli(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
export default async function disableNodeCli(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
if (compiler.options.enableNodeCli) {
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
if (semverGt(compiler.target.version, '11.6.0')) {
|
||||
if (semverGt(compiler.target.version, "18.99")) {
|
||||
await compiler.replaceInFileAsync(
|
||||
'src/node.cc',
|
||||
"src/node.cc",
|
||||
/(?<!static ExitCode )ProcessGlobalArgsInternal\(argv[^;]*;/gm,
|
||||
"ExitCode::kNoFailure;/*$&*/"
|
||||
);
|
||||
} else if (semverGt(compiler.target.version, "11.6.0")) {
|
||||
await compiler.replaceInFileAsync(
|
||||
"src/node.cc",
|
||||
/(?<!int )ProcessGlobalArgs\(argv[^;]*;/gm,
|
||||
'0;/*$&*/'
|
||||
)
|
||||
} else if (semverGt(compiler.target.version, '10.9')) {
|
||||
await compiler.replaceInFileAsync('src/node.cc', /(?<!void )ProcessArgv\(argv/g, '//$&')
|
||||
} else if (semverGt(compiler.target.version, '9.999')) {
|
||||
"0;/*$&*/"
|
||||
);
|
||||
} else if (semverGt(compiler.target.version, "10.9")) {
|
||||
await compiler.replaceInFileAsync(
|
||||
'src/node.cc',
|
||||
'int i = 1; i < v8_argc; i++',
|
||||
'int i = v8_argc; i < v8_argc; i++'
|
||||
)
|
||||
let matches = 0
|
||||
await compiler.replaceInFileAsync('src/node.cc', /v8_argc > 1/g, (match) => {
|
||||
if (matches++) {
|
||||
return 'false'
|
||||
"src/node.cc",
|
||||
/(?<!void )ProcessArgv\(argv/g,
|
||||
"//$&"
|
||||
);
|
||||
} else if (semverGt(compiler.target.version, "9.999")) {
|
||||
await compiler.replaceInFileAsync(
|
||||
"src/node.cc",
|
||||
"int i = 1; i < v8_argc; i++",
|
||||
"int i = v8_argc; i < v8_argc; i++"
|
||||
);
|
||||
let matches = 0;
|
||||
await compiler.replaceInFileAsync(
|
||||
"src/node.cc",
|
||||
/v8_argc > 1/g,
|
||||
(match) => {
|
||||
if (matches++) {
|
||||
return "false";
|
||||
}
|
||||
return match;
|
||||
}
|
||||
return match
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const nodeccMarker = 'argv[index][0] =='
|
||||
const nodeccMarker = "argv[index][0] ==";
|
||||
|
||||
await compiler.replaceInFileAsync(
|
||||
'src/node.cc',
|
||||
"src/node.cc",
|
||||
`${nodeccMarker} '-'`,
|
||||
// allow NODE_OPTIONS, introduced in 8.0
|
||||
semverGt(compiler.target.version, '7.99')
|
||||
semverGt(compiler.target.version, "7.99")
|
||||
? `(${nodeccMarker} (is_env ? '-' : ']'))`
|
||||
: `(${nodeccMarker} ']')`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
+11
-8
@@ -1,16 +1,19 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { NexeCompiler } from "../compiler";
|
||||
|
||||
export default async function flags(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const nodeflags = compiler.options.flags
|
||||
export default async function flags(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
const nodeflags = compiler.options.flags;
|
||||
if (!nodeflags.length) {
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
await compiler.replaceInFileAsync(
|
||||
'node.gyp',
|
||||
"node.gyp",
|
||||
"'node_v8_options%': ''",
|
||||
`'node_v8_options%': '${nodeflags.join(' ')}'`
|
||||
)
|
||||
`'node_v8_options%': '${nodeflags.join(" ")}'`
|
||||
);
|
||||
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
|
||||
export default async function fsStat(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
await compiler.replaceInFileAsync(
|
||||
'lib/fs.js',
|
||||
'function statSync(path, options = { bigint: false, throwIfNoEntry: true }) {',
|
||||
`function statSync(path, options = { bigint: false, throwIfNoEntry: true }) {
|
||||
// If Node passes a file descriptor, we must fstat the REAL fs
|
||||
if (typeof path === 'number') {
|
||||
try {
|
||||
return fstatSync(path, options);
|
||||
} catch (e) {
|
||||
if (options.throwIfNoEntry) {
|
||||
throw e;
|
||||
}
|
||||
return options.bigint ? BigInt(-1) : -1;
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
return next()
|
||||
}
|
||||
+8
-8
@@ -1,21 +1,21 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { NexeCompiler } from "../compiler";
|
||||
|
||||
export default async function nodeGyp(
|
||||
{ files, replaceInFileAsync }: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
await next()
|
||||
await next();
|
||||
|
||||
const nodeGypMarker = "'lib/fs.js',"
|
||||
const nodeGypMarker = "'lib/fs.js',";
|
||||
await replaceInFileAsync(
|
||||
'node.gyp',
|
||||
"node.gyp",
|
||||
nodeGypMarker,
|
||||
`
|
||||
${nodeGypMarker}
|
||||
${files
|
||||
.filter((x) => x.filename.startsWith('lib'))
|
||||
.map((x) => `'${x.filename}'`)
|
||||
.toString()},
|
||||
.filter((x) => x.filename.startsWith("lib"))
|
||||
.map((x) => `'${x.filename}'`)
|
||||
.toString()},
|
||||
`.trim()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
+14
-8
@@ -1,12 +1,18 @@
|
||||
import { normalize } from 'path'
|
||||
import { readFileAsync } from '../util'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { normalize } from "path";
|
||||
import { readFileAsync } from "../util";
|
||||
import { NexeCompiler } from "../compiler";
|
||||
|
||||
export default async function ico(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const iconFile = compiler.options.ico
|
||||
export default async function ico(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
const iconFile = compiler.options.ico;
|
||||
if (!iconFile) {
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
await compiler.setFileContentsAsync('src/res/node.ico', await readFileAsync(normalize(iconFile)))
|
||||
return next()
|
||||
await compiler.setFileContentsAsync(
|
||||
"src/res/node.ico",
|
||||
await readFileAsync(normalize(iconFile))
|
||||
);
|
||||
return next();
|
||||
}
|
||||
|
||||
+13
-10
@@ -1,12 +1,15 @@
|
||||
import gyp from './gyp'
|
||||
import bootNexe from './third-party-main'
|
||||
import buildFixes from './build-fixes'
|
||||
import cli from './disable-node-cli'
|
||||
import flags from './flags'
|
||||
import ico from './ico'
|
||||
import rc from './node-rc'
|
||||
import snapshot from './snapshot'
|
||||
import gyp from "./gyp";
|
||||
import bootNexe from "./third-party-main";
|
||||
import buildFixes from "./build-fixes";
|
||||
import cli from "./disable-node-cli";
|
||||
import flags from "./flags";
|
||||
import ico from "./ico";
|
||||
import rc from "./node-rc";
|
||||
import snapshot from "./snapshot";
|
||||
import fsStat from "./fs-stat-debug";
|
||||
|
||||
const patches = [gyp, bootNexe, buildFixes, cli, flags, ico, rc, snapshot]
|
||||
// Patches are applied in order, so if a patch depends on another patch, it should be listed after the patch it depends on.
|
||||
// For example, if a patch modifies the output of gyp, it should be listed after gyp in this array.
|
||||
const patches = [gyp, bootNexe, buildFixes, cli, flags, ico, rc, snapshot];
|
||||
|
||||
export default patches
|
||||
export default patches;
|
||||
|
||||
+17
-14
@@ -1,28 +1,31 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { NexeCompiler } from "../compiler";
|
||||
|
||||
export default async function nodeRc(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const options = compiler.options.rc
|
||||
export default async function nodeRc(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
const options = compiler.options.rc;
|
||||
if (!options) {
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
const file = await compiler.readFileAsync('src/res/node.rc')
|
||||
const file = await compiler.readFileAsync("src/res/node.rc");
|
||||
|
||||
Object.keys(options).forEach((key) => {
|
||||
let value = options[key]
|
||||
const isVar = /^[A-Z_]+$/.test(value)
|
||||
value = isVar ? value : `"${value}"`
|
||||
let value = options[key];
|
||||
const isVar = /^[A-Z_]+$/.test(value);
|
||||
value = isVar ? value : `"${value}"`;
|
||||
file.contents = file.contents
|
||||
.toString()
|
||||
.replace(new RegExp(`VALUE "${key}",.*`), `VALUE "${key}", ${value}`)
|
||||
})
|
||||
;['PRODUCTVERSION', 'FILEVERSION'].forEach((x) => {
|
||||
.replace(new RegExp(`VALUE "${key}",.*`), `VALUE "${key}", ${value}`);
|
||||
});
|
||||
["PRODUCTVERSION", "FILEVERSION"].forEach((x) => {
|
||||
if (options[x]) {
|
||||
file.contents = file.contents
|
||||
.toString()
|
||||
.replace(new RegExp(x + ' .*$', 'm'), `${x} ${options[x]}`)
|
||||
.replace(new RegExp(x + " .*$", "m"), `${x} ${options[x]}`);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
+15
-10
@@ -1,19 +1,24 @@
|
||||
import { resolve } from 'path'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { semverGt } from '../util'
|
||||
import { resolve } from "path";
|
||||
import { NexeCompiler } from "../compiler";
|
||||
import { semverGt } from "../util";
|
||||
|
||||
export default async function (compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const { snapshot, warmup, cwd } = compiler.options
|
||||
export default async function (
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
const { snapshot, warmup, cwd } = compiler.options;
|
||||
|
||||
if (!snapshot) {
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
const variablePrefix = semverGt(compiler.target.version, '11.0.0') ? 'v8_' : ''
|
||||
const variablePrefix = semverGt(compiler.target.version, "11.0.0")
|
||||
? "v8_"
|
||||
: "";
|
||||
|
||||
await compiler.replaceInFileAsync(
|
||||
compiler.configureScript,
|
||||
'def configure_v8(o):',
|
||||
"def configure_v8(o):",
|
||||
`def configure_v8(o):\n o['variables']['${variablePrefix}embed_script'] = r'${resolve(
|
||||
cwd,
|
||||
snapshot
|
||||
@@ -21,7 +26,7 @@ export default async function (compiler: NexeCompiler, next: () => Promise<void>
|
||||
cwd,
|
||||
warmup || snapshot
|
||||
)}'`
|
||||
)
|
||||
);
|
||||
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -1,109 +1,131 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { parse } from 'cherow'
|
||||
import { wrap, semverGt } from '../util'
|
||||
import { NexeCompiler } from "../compiler";
|
||||
import { parse } from "meriyah";
|
||||
import { wrap, semverGt } from "../util";
|
||||
|
||||
function walkSome(node: any, visit: Function) {
|
||||
if (!node || typeof node.type !== 'string' || node._visited) {
|
||||
return false
|
||||
if (!node || typeof node.type !== "string" || node._visited) {
|
||||
return false;
|
||||
}
|
||||
visit(node)
|
||||
node._visited = true
|
||||
for (let childNode in node) {
|
||||
const child = node[childNode]
|
||||
visit(node);
|
||||
node._visited = true;
|
||||
for (const childNode in node) {
|
||||
const child = node[childNode];
|
||||
if (Array.isArray(child)) {
|
||||
for (let i = 0; i < child.length; i++) {
|
||||
if (walkSome(child[i], visit)) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (walkSome(child, visit)) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
let bootFile = 'lib/internal/bootstrap_node.js'
|
||||
const { version } = compiler.target
|
||||
export default async function main(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
let bootFile = "lib/internal/bootstrap_node.js";
|
||||
const { version } = compiler.target;
|
||||
|
||||
if (version.startsWith('4.')) {
|
||||
bootFile = 'src/node.js'
|
||||
} else if (semverGt(version, '11.99')) {
|
||||
bootFile = 'lib/internal/bootstrap/pre_execution.js'
|
||||
} else if (semverGt(version, '9.10.1')) {
|
||||
bootFile = 'lib/internal/bootstrap/node.js'
|
||||
if (version.startsWith("4.")) {
|
||||
bootFile = "src/node.js";
|
||||
} else if (semverGt(version, "18.11.99")) {
|
||||
bootFile = "lib/internal/process/pre_execution.js";
|
||||
} else if (semverGt(version, "11.99")) {
|
||||
bootFile = "lib/internal/bootstrap/pre_execution.js";
|
||||
} else if (semverGt(version, "9.10.1")) {
|
||||
bootFile = "lib/internal/bootstrap/node.js";
|
||||
}
|
||||
|
||||
const file = await compiler.readFileAsync(bootFile),
|
||||
ast = parse(file.contents.toString(), {
|
||||
loc: true,
|
||||
tolerant: true,
|
||||
next: true,
|
||||
globalReturn: true,
|
||||
node: true,
|
||||
skipShebang: true,
|
||||
loc: true,
|
||||
}),
|
||||
location = { start: { line: 0 } }
|
||||
location = { start: { line: 0 } };
|
||||
|
||||
walkSome(ast, (node: any) => {
|
||||
if (!location.start.line && node.type === 'BlockStatement') {
|
||||
if (!location.start.line && node.type === "BlockStatement") {
|
||||
//Find the first block statement and mark the location
|
||||
Object.assign(location, node.loc)
|
||||
return true
|
||||
Object.assign(location, node.loc);
|
||||
return true;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const fileLines = file.contents.toString().split('\n')
|
||||
fileLines.splice(
|
||||
location.start.line,
|
||||
0,
|
||||
'{{replace:lib/fs/bootstrap.js}}' +
|
||||
'\n' +
|
||||
(semverGt(version, '11.99') ? 'expandArgv1 = false;\n' : '')
|
||||
)
|
||||
file.contents = fileLines.join('\n')
|
||||
const fileLines = file.contents.toString().split("\n");
|
||||
if (semverGt(version, "18.16.99")) {
|
||||
await compiler.replaceInFileAsync(
|
||||
"lib/internal/modules/cjs/loader.js",
|
||||
"'use strict';",
|
||||
"'use strict';\n" + '{{ file("lib/fs/bootstrap.js") }}' + "\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");
|
||||
|
||||
if (semverGt(version, '11.99')) {
|
||||
if (semverGt(version, '12.17.99')) {
|
||||
if (semverGt(version, "11.99")) {
|
||||
if (semverGt(version, "12.17.99")) {
|
||||
await compiler.replaceInFileAsync(
|
||||
bootFile,
|
||||
'initializeFrozenIntrinsics();',
|
||||
'initializeFrozenIntrinsics();\n' + wrap('{{replace:lib/patches/boot-nexe.js}}')
|
||||
)
|
||||
"initializeFrozenIntrinsics();",
|
||||
"initializeFrozenIntrinsics();\n" +
|
||||
wrap('{{ file("lib/patches/boot-nexe.js") }}')
|
||||
);
|
||||
} else {
|
||||
await compiler.replaceInFileAsync(
|
||||
bootFile,
|
||||
'initializePolicy();',
|
||||
'initializePolicy();\n' + wrap('{{replace:lib/patches/boot-nexe.js}}')
|
||||
)
|
||||
"initializePolicy();",
|
||||
"initializePolicy();\n" + wrap('{{ file("lib/patches/boot-nexe.js") }}')
|
||||
);
|
||||
}
|
||||
await compiler.replaceInFileAsync(
|
||||
bootFile,
|
||||
'assert(!CJSLoader.hasLoadedAnyUserCJSModule)',
|
||||
'/*assert(!CJSLoader.hasLoadedAnyUserCJSModule)*/'
|
||||
)
|
||||
const { contents: nodeccContents } = await compiler.readFileAsync('src/node.cc')
|
||||
if (nodeccContents.includes('if (env->worker_context() != nullptr) {')) {
|
||||
"assert(!CJSLoader.hasLoadedAnyUserCJSModule)",
|
||||
"/*assert(!CJSLoader.hasLoadedAnyUserCJSModule)*/"
|
||||
);
|
||||
// Node 22.22+ / 24 removed the ENOENT catch from finalizeResolution in the
|
||||
// ESM resolver. realpathSync calls binding.lstat which throws ENOENT for VFS
|
||||
// paths (they don't exist on real FS). Restore the catch so VFS imports work.
|
||||
await compiler.replaceInFileAsync(
|
||||
"lib/internal/modules/esm/resolve.js",
|
||||
/const real = realpathSync\(path, \{ encoding: ['"]utf8['"] \}\);/,
|
||||
"let real; try { real = realpathSync(path, { encoding: 'utf8' }); } catch (e) { if (e.code !== 'ENOENT') throw e; real = path; }"
|
||||
);
|
||||
const { contents: nodeccContents } = await compiler.readFileAsync(
|
||||
"src/node.cc"
|
||||
);
|
||||
if (nodeccContents.includes("if (env->worker_context() != nullptr) {")) {
|
||||
await compiler.replaceInFileAsync(
|
||||
'src/node.cc',
|
||||
'if (env->worker_context() != nullptr) {',
|
||||
'if (env->worker_context() == nullptr) {\n' +
|
||||
' return StartExecution(env, "internal/main/run_main_module"); } else {\n'
|
||||
)
|
||||
"src/node.cc",
|
||||
"if (env->worker_context() != nullptr) {",
|
||||
"if (env->worker_context() == nullptr) {\n" +
|
||||
' return StartExecution(env, "internal/main/run_main_module"); } else {\n'
|
||||
);
|
||||
} else {
|
||||
await compiler.replaceInFileAsync(
|
||||
'src/node.cc',
|
||||
'MaybeLocal<Value> StartMainThreadExecution(Environment* env) {',
|
||||
'MaybeLocal<Value> StartMainThreadExecution(Environment* env) {\n' +
|
||||
' return StartExecution(env, "internal/main/run_main_module");\n'
|
||||
)
|
||||
"src/node.cc",
|
||||
"MaybeLocal<Value> StartMainThreadExecution(Environment* env) {",
|
||||
"MaybeLocal<Value> StartMainThreadExecution(Environment* env) {\n" +
|
||||
' return StartExecution(env, "internal/main/run_main_module");\n'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await compiler.setFileContentsAsync(
|
||||
'lib/_third_party_main.js',
|
||||
'{{replace:lib/patches/boot-nexe.js}}'
|
||||
)
|
||||
"lib/_third_party_main.js",
|
||||
'{{ file("lib/patches/boot-nexe.js") }}'
|
||||
);
|
||||
}
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
+47
-33
@@ -1,62 +1,76 @@
|
||||
import got = require('got')
|
||||
import { platforms, architectures, NexeTarget, getTarget, targetsEqual } from './target'
|
||||
export { NexeTarget }
|
||||
import axios from "axios";
|
||||
import {
|
||||
platforms,
|
||||
architectures,
|
||||
NexeTarget,
|
||||
getTarget,
|
||||
targetsEqual,
|
||||
} from "./target";
|
||||
export { NexeTarget };
|
||||
|
||||
const versionsToSkip = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 17, 19, 21, 22]
|
||||
const versionsToSkip = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 17, 19, 21, 22,
|
||||
];
|
||||
export interface GitAsset {
|
||||
name: string
|
||||
url: string
|
||||
browser_download_url: string
|
||||
name: string;
|
||||
url: string;
|
||||
browser_download_url: string;
|
||||
}
|
||||
|
||||
export interface GitRelease {
|
||||
tag_name: string
|
||||
assets_url: string
|
||||
upload_url: string
|
||||
assets: GitAsset[]
|
||||
tag_name: string;
|
||||
assets_url: string;
|
||||
upload_url: string;
|
||||
assets: GitAsset[];
|
||||
}
|
||||
|
||||
interface NodeRelease {
|
||||
version: string
|
||||
version: string;
|
||||
}
|
||||
|
||||
async function getJson<T>(url: string, options?: any) {
|
||||
return JSON.parse((await got(url, options)).body) as T
|
||||
async function getJson<T>(url: string, options?: any): Promise<T> {
|
||||
const response = await axios.get(url, options);
|
||||
return response.data as T;
|
||||
}
|
||||
|
||||
function isBuildableVersion(version: string) {
|
||||
if (version === '12.11.0') {
|
||||
return false
|
||||
if (version === "12.11.0") {
|
||||
return false;
|
||||
}
|
||||
return !versionsToSkip.includes(Number(version.split('.')[0]))
|
||||
return !versionsToSkip.includes(Number(version.split(".")[0]));
|
||||
}
|
||||
|
||||
export function getLatestGitRelease(options?: any) {
|
||||
return getJson<GitRelease>('https://api.github.com/repos/nexe/nexe/releases/latest', options)
|
||||
export function getLatestGitRelease(options?: any): Promise<GitRelease> {
|
||||
return getJson<GitRelease>(
|
||||
"https://api.github.com/repos/nexe/nexe/releases/latest",
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
export async function getUnBuiltReleases(options?: any) {
|
||||
const nodeReleases = await getJson<NodeRelease[]>(
|
||||
'https://nodejs.org/download/release/index.json'
|
||||
)
|
||||
const existingVersions = (await getLatestGitRelease(options)).assets.map((x) => getTarget(x.name))
|
||||
"https://nodejs.org/download/release/index.json"
|
||||
);
|
||||
const existingVersions = (await getLatestGitRelease(options)).assets.map(
|
||||
(x) => getTarget(x.name)
|
||||
);
|
||||
|
||||
const versionMap: { [key: string]: true } = {}
|
||||
const versionMap: { [key: string]: true } = {};
|
||||
return nodeReleases
|
||||
.reduce((versions: NexeTarget[], { version }) => {
|
||||
version = version.replace('v', '').trim()
|
||||
version = version.replace("v", "").trim();
|
||||
if (!isBuildableVersion(version) || versionMap[version]) {
|
||||
return versions
|
||||
return versions;
|
||||
}
|
||||
versionMap[version] = true
|
||||
versionMap[version] = true;
|
||||
platforms.forEach((platform) => {
|
||||
architectures.forEach((arch) => {
|
||||
if (arch === 'x86' && platform === 'mac') return
|
||||
if (arch.includes('arm')) return
|
||||
versions.push(getTarget({ platform, arch, version }))
|
||||
})
|
||||
})
|
||||
return versions
|
||||
if (arch === "x86" && platform === "mac") return;
|
||||
if (arch.includes("arm")) return;
|
||||
versions.push(getTarget({ platform, arch, version }));
|
||||
});
|
||||
});
|
||||
return versions;
|
||||
}, [])
|
||||
.filter((x) => !existingVersions.some((t) => targetsEqual(t, x)))
|
||||
.filter((x) => !existingVersions.some((t) => targetsEqual(t, x)));
|
||||
}
|
||||
|
||||
+41
-33
@@ -1,33 +1,35 @@
|
||||
import { join, dirname } from 'path'
|
||||
import * as fs from 'fs'
|
||||
import { promisify } from 'util'
|
||||
import { readFileAsync, writeFileAsync, isDirectoryAsync } from '../util'
|
||||
import mkdirpAsync = require('mkdirp')
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { join, dirname } from "path";
|
||||
import * as fs from "fs";
|
||||
import { promisify } from "util";
|
||||
import { readFileAsync, writeFileAsync, isDirectoryAsync } from "../util";
|
||||
import mkdirpAsync = require("mkdirp");
|
||||
import { NexeCompiler } from "../compiler";
|
||||
|
||||
const unlinkAsync = promisify(fs.unlink),
|
||||
readdirAsync = promisify(fs.readdir)
|
||||
readdirAsync = promisify(fs.readdir);
|
||||
|
||||
function readDirAsync(dir: string): Promise<string[]> {
|
||||
return readdirAsync(dir).then((paths) => {
|
||||
return Promise.all(
|
||||
paths.map((file: string) => {
|
||||
const path = join(dir, file)
|
||||
return isDirectoryAsync(path).then((x) => (x ? readDirAsync(path) : (path as any)))
|
||||
const path = join(dir, file);
|
||||
return isDirectoryAsync(path).then((x) =>
|
||||
x ? readDirAsync(path) : (path as any)
|
||||
);
|
||||
})
|
||||
).then((result) => {
|
||||
return [].concat(...(result as any))
|
||||
})
|
||||
})
|
||||
return [].concat(...(result as any));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function maybeReadFileContentsAsync(file: string) {
|
||||
return readFileAsync(file, 'utf-8').catch((e) => {
|
||||
if (e.code === 'ENOENT') {
|
||||
return ''
|
||||
return readFileAsync(file, "utf-8").catch((e) => {
|
||||
if (e.code === "ENOENT") {
|
||||
return "";
|
||||
}
|
||||
throw e
|
||||
})
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,30 +41,36 @@ function maybeReadFileContentsAsync(file: string) {
|
||||
* - Original versions of sources to be patched are written to the temporary directory
|
||||
* - Finally, The patched files are written into source.
|
||||
*/
|
||||
export default async function artifacts(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
const { src } = compiler
|
||||
const temp = join(src, 'nexe')
|
||||
await mkdirpAsync(temp)
|
||||
const tmpFiles = await readDirAsync(temp)
|
||||
export default async function artifacts(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
const { src } = compiler;
|
||||
const temp = join(src, "nexe");
|
||||
await mkdirpAsync(temp);
|
||||
const tmpFiles = await readDirAsync(temp);
|
||||
|
||||
await Promise.all(
|
||||
tmpFiles.map(async (path) => {
|
||||
return compiler.writeFileAsync(path.replace(temp, ''), await readFileAsync(path, 'utf-8'))
|
||||
return compiler.writeFileAsync(
|
||||
path.replace(temp, ""),
|
||||
await readFileAsync(path, "utf-8")
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
await next()
|
||||
await next();
|
||||
|
||||
await Promise.all(tmpFiles.map((x) => unlinkAsync(x)))
|
||||
await Promise.all(tmpFiles.map((x) => unlinkAsync(x)));
|
||||
return Promise.all(
|
||||
compiler.files.map(async (file) => {
|
||||
const sourceFile = join(src, file.filename)
|
||||
const tempFile = join(temp, file.filename)
|
||||
const fileContents = await maybeReadFileContentsAsync(sourceFile)
|
||||
const sourceFile = join(src, file.filename);
|
||||
const tempFile = join(temp, file.filename);
|
||||
const fileContents = await maybeReadFileContentsAsync(sourceFile);
|
||||
|
||||
await mkdirpAsync(dirname(tempFile))
|
||||
await writeFileAsync(tempFile, fileContents)
|
||||
await compiler.writeFileAsync(file.filename, file.contents)
|
||||
await mkdirpAsync(dirname(tempFile));
|
||||
await writeFileAsync(tempFile, fileContents);
|
||||
await compiler.writeFileAsync(file.filename, file.contents);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
+256
-47
@@ -1,85 +1,294 @@
|
||||
import { NexeCompiler, NexeError } from '../compiler'
|
||||
import { resolve, relative } from 'path'
|
||||
import resolveFiles, { resolveSync } from 'resolve-dependencies'
|
||||
import { dequote, STDIN_FLAG, semverGt, each } from '../util'
|
||||
import { Readable } from 'stream'
|
||||
import { NexeCompiler, NexeError } from "../compiler";
|
||||
import { resolve, relative, sep, join } from "path";
|
||||
import resolveFiles, { resolveSync } from "resolve-dependencies";
|
||||
import { dequote, STDIN_FLAG, semverGt } from "../util";
|
||||
import { Readable } from "stream";
|
||||
import { minimatch } from "minimatch";
|
||||
import { readdirSync, statSync } from "fs";
|
||||
|
||||
type ModuleRule = {
|
||||
include?: string[];
|
||||
exclude?: string[];
|
||||
};
|
||||
|
||||
function getModuleName(file: string): string | null {
|
||||
const parts = file.split(/[\\/]/);
|
||||
const idx = parts.lastIndexOf("node_modules");
|
||||
if (idx === -1) return null;
|
||||
|
||||
const name = parts[idx + 1];
|
||||
if (!name) return null;
|
||||
|
||||
return name.startsWith("@")
|
||||
? parts.slice(idx + 1, idx + 3).join("/")
|
||||
: name;
|
||||
}
|
||||
|
||||
function getModuleBasePath(file: string, moduleName: string): string | null {
|
||||
const parts = file.split(/[\\/]/);
|
||||
const idx = parts.lastIndexOf("node_modules");
|
||||
if (idx === -1) return null;
|
||||
|
||||
const baseParts = parts.slice(0, idx + 1);
|
||||
if (moduleName.startsWith("@")) {
|
||||
baseParts.push(...moduleName.split("/"));
|
||||
} else {
|
||||
baseParts.push(moduleName);
|
||||
}
|
||||
|
||||
return baseParts.join(sep);
|
||||
}
|
||||
|
||||
function getModuleRelativePath(file: string, moduleName: string): string {
|
||||
const normalizedFile = file.replace(/\\/g, '/');
|
||||
const normalizedModuleName = moduleName.replace(/\\/g, '/');
|
||||
|
||||
const marker = `node_modules/${normalizedModuleName}/`;
|
||||
const idx = normalizedFile.indexOf(marker);
|
||||
|
||||
if (idx === -1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return normalizedFile.slice(idx + marker.length);
|
||||
}
|
||||
|
||||
function getAllFilesInModule(basePath: string): string[] {
|
||||
const files: string[] = [];
|
||||
|
||||
function walk(dir: string) {
|
||||
try {
|
||||
const entries = readdirSync(dir);
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(dir, entry);
|
||||
try {
|
||||
const stat = statSync(fullPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
walk(fullPath);
|
||||
} else if (stat.isFile()) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
} catch (err) {
|
||||
// Skip files we can't read
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// Skip directories we can't read
|
||||
}
|
||||
}
|
||||
|
||||
walk(basePath);
|
||||
return files;
|
||||
}
|
||||
|
||||
function shouldIncludeFile(
|
||||
file: string,
|
||||
moduleName: string,
|
||||
rule: ModuleRule
|
||||
): boolean {
|
||||
const rel = getModuleRelativePath(file, moduleName);
|
||||
|
||||
if (!rel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always include package.json
|
||||
if (rel === "package.json") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If there's an include list, file MUST match it
|
||||
if (rule.include && rule.include.length > 0) {
|
||||
const matchesInclude = rule.include.some((g) => minimatch(rel, g));
|
||||
if (!matchesInclude) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check exclude patterns
|
||||
if (rule.exclude && rule.exclude.length > 0) {
|
||||
const matchesExclude = rule.exclude.some((g) => minimatch(rel, g));
|
||||
if (matchesExclude) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getStdIn(stdin: Readable): Promise<string> {
|
||||
let out = ''
|
||||
let out = "";
|
||||
return new Promise((resolve) => {
|
||||
stdin
|
||||
.setEncoding('utf8')
|
||||
.on('readable', () => {
|
||||
let current
|
||||
.setEncoding("utf8")
|
||||
.on("readable", () => {
|
||||
let current;
|
||||
while ((current = stdin.read())) {
|
||||
out += current
|
||||
out += current;
|
||||
}
|
||||
})
|
||||
.on('end', () => resolve(out.trim()))
|
||||
.on("end", () => resolve(out.trim()));
|
||||
setTimeout(() => {
|
||||
if (!out.trim()) {
|
||||
resolve(out.trim())
|
||||
resolve(out.trim());
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
export default async function bundle(compiler: NexeCompiler, next: any) {
|
||||
const { bundle: doBundle, cwd, input: inputPath } = compiler.options
|
||||
let input = inputPath
|
||||
compiler.entrypoint = './' + relative(cwd, input)
|
||||
const { bundle: doBundle, cwd, input: inputPath, bundleRules } = compiler.options;
|
||||
|
||||
const rules: Record<string, ModuleRule> =
|
||||
bundleRules && Object.keys(bundleRules).length ? bundleRules : {};
|
||||
|
||||
if (semverGt(compiler.target.version, '11.99')) {
|
||||
compiler.startup = ''
|
||||
let input = inputPath;
|
||||
compiler.entrypoint = "./" + relative(cwd, input);
|
||||
|
||||
if (semverGt(compiler.target.version, "11.99")) {
|
||||
compiler.startup = "";
|
||||
} else {
|
||||
compiler.startup = ';require("module").runMain();'
|
||||
compiler.startup = ';require("module").runMain();';
|
||||
}
|
||||
|
||||
if (!doBundle) {
|
||||
await compiler.addResource(resolve(cwd, input))
|
||||
return next()
|
||||
await compiler.addResource(resolve(cwd, input));
|
||||
return next();
|
||||
}
|
||||
|
||||
let code = ''
|
||||
if (typeof doBundle === 'string') {
|
||||
code = await require(doBundle).createBundle(compiler.options)
|
||||
let code = "";
|
||||
if (typeof doBundle === "string") {
|
||||
code = await require(doBundle).createBundle(compiler.options);
|
||||
}
|
||||
|
||||
if (input === STDIN_FLAG && (code = code || dequote(await getStdIn(process.stdin)))) {
|
||||
compiler.stdinUsed = true
|
||||
compiler.entrypoint = './__nexe_stdin.js'
|
||||
await compiler.addResource(resolve(cwd, compiler.entrypoint), code)
|
||||
return next()
|
||||
if (
|
||||
input === STDIN_FLAG &&
|
||||
(code = code || dequote(await getStdIn(process.stdin)))
|
||||
) {
|
||||
compiler.stdinUsed = true;
|
||||
compiler.entrypoint = "./__nexe_stdin.js";
|
||||
await compiler.addResource(resolve(cwd, compiler.entrypoint), code);
|
||||
return next();
|
||||
}
|
||||
|
||||
if (input === STDIN_FLAG) {
|
||||
const maybeInput = resolveSync(cwd, '.')
|
||||
const maybeInput = resolveSync(cwd, ".");
|
||||
if (!maybeInput || !maybeInput.absPath) {
|
||||
throw new NexeError('No valid input detected')
|
||||
throw new NexeError("No valid input detected");
|
||||
}
|
||||
input = maybeInput.absPath
|
||||
compiler.entrypoint = './' + relative(cwd, input)
|
||||
input = maybeInput.absPath;
|
||||
compiler.entrypoint = "./" + relative(cwd, input);
|
||||
}
|
||||
|
||||
const { files, warnings } = resolveFiles(
|
||||
input,
|
||||
...Object.keys(compiler.bundle.list).filter((x) => x.endsWith('.js')),
|
||||
{ cwd, expand: 'variable', loadContent: false }
|
||||
)
|
||||
const step = compiler.log.step("Resolving dependencies...");
|
||||
|
||||
const { files, warnings } = await resolveFiles(input, {
|
||||
cwd,
|
||||
expand: "variable",
|
||||
loadContent: false,
|
||||
});
|
||||
|
||||
if (
|
||||
warnings.filter((x) => x.startsWith('Error parsing file') && !x.includes('node_modules')).length
|
||||
warnings.filter(
|
||||
(x) => x.startsWith("Error parsing file") && !x.includes("node_modules")
|
||||
).length
|
||||
) {
|
||||
throw new NexeError('Parsing Error:\n' + warnings.join('\n'))
|
||||
throw new NexeError("Parsing Error:\n" + warnings.join("\n"));
|
||||
}
|
||||
|
||||
//TODO: warnings.forEach((x) => console.log(x))
|
||||
// If no bundle rules, use original behavior
|
||||
if (Object.keys(rules).length === 0) {
|
||||
const pkgJsonsToAdd = new Set<string>();
|
||||
|
||||
await Promise.all(
|
||||
Object.entries(files).map(([key]) => {
|
||||
step.log(`Including dependency: ${key}`);
|
||||
// Collect the package.json for each bundled module so Node can
|
||||
// resolve the correct entry point (main/exports) at runtime.
|
||||
const moduleName = getModuleName(key);
|
||||
if (moduleName) {
|
||||
const basePath = getModuleBasePath(key, moduleName);
|
||||
if (basePath) {
|
||||
pkgJsonsToAdd.add(join(basePath, "package.json"));
|
||||
}
|
||||
}
|
||||
return compiler.addResource(key);
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
Array.from(pkgJsonsToAdd).map((pkgJson) => {
|
||||
try {
|
||||
statSync(pkgJson);
|
||||
step.log(`Including package.json: ${pkgJson}`);
|
||||
return compiler.addResource(pkgJson);
|
||||
} catch (e) {
|
||||
// package.json doesn't exist on disk, skip
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
// Track modules with explicit bundle rules
|
||||
const modulesWithRules = new Map<string, string>();
|
||||
|
||||
for (const file of Object.keys(files)) {
|
||||
const moduleName = getModuleName(file);
|
||||
if (moduleName && rules[moduleName] && !modulesWithRules.has(moduleName)) {
|
||||
const basePath = getModuleBasePath(file, moduleName);
|
||||
if (basePath) {
|
||||
modulesWithRules.set(moduleName, basePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect files to include
|
||||
const filesToInclude = new Set<string>();
|
||||
|
||||
// Add all files that AREN'T from modules with rules (preserve original behavior)
|
||||
for (const file of Object.keys(files)) {
|
||||
const moduleName = getModuleName(file);
|
||||
|
||||
// If this file is from a module with rules, skip it here
|
||||
if (moduleName && modulesWithRules.has(moduleName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// All other files get included as normal
|
||||
filesToInclude.add(file);
|
||||
}
|
||||
|
||||
// For modules WITH rules: scan and filter
|
||||
for (const [moduleName, basePath] of modulesWithRules) {
|
||||
const rule = rules[moduleName];
|
||||
|
||||
step.log(`Scanning ${moduleName} with bundle rules...`);
|
||||
|
||||
const moduleFiles = getAllFilesInModule(basePath);
|
||||
|
||||
for (const file of moduleFiles) {
|
||||
const rel = getModuleRelativePath(file, moduleName);
|
||||
|
||||
if (shouldIncludeFile(file, moduleName, rule)) {
|
||||
filesToInclude.add(file);
|
||||
step.log(`Including ${rel} from ${moduleName}`);
|
||||
} else {
|
||||
step.log(`Excluding ${rel} from ${moduleName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all files
|
||||
await Promise.all(
|
||||
Object.entries(files).map(([key, file]) => {
|
||||
return compiler.addResource(key, file)
|
||||
Array.from(filesToInclude).map((file) => {
|
||||
step.log(`Including dependency: ${file}`);
|
||||
return compiler.addResource(file);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return next()
|
||||
}
|
||||
return next();
|
||||
}
|
||||
+18
-13
@@ -1,19 +1,24 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { rimrafAsync } from '../util'
|
||||
import { NexeTarget } from '../target'
|
||||
import { NexeCompiler } from "../compiler";
|
||||
import { rimrafAsync } from "../util";
|
||||
import { NexeTarget } from "../target";
|
||||
|
||||
export default async function clean(compiler: NexeCompiler, next: () => Promise<any>) {
|
||||
const { options } = compiler
|
||||
export default async function clean(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<any>
|
||||
) {
|
||||
const { options } = compiler;
|
||||
if (options.clean) {
|
||||
let path = compiler.src
|
||||
let path = compiler.src;
|
||||
if (!options.build) {
|
||||
path = compiler.getNodeExecutableLocation(compiler.options.targets[0] as NexeTarget)
|
||||
path = compiler.getNodeExecutableLocation(
|
||||
compiler.options.targets[0] as NexeTarget
|
||||
);
|
||||
}
|
||||
const step = compiler.log.step('Cleaning up nexe build artifacts...')
|
||||
step.log(`Deleting contents at: ${path}`)
|
||||
await rimrafAsync(path)
|
||||
step.log(`Deleted contents at: ${path}`)
|
||||
return compiler.quit()
|
||||
const step = compiler.log.step("Cleaning up nexe build artifacts...");
|
||||
step.log(`Deleting contents at: ${path}`);
|
||||
await rimrafAsync(path);
|
||||
step.log(`Deleted contents at: ${path}`);
|
||||
return compiler.quit();
|
||||
}
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
+46
-30
@@ -1,9 +1,17 @@
|
||||
import { dirname, normalize, relative, resolve } from 'path'
|
||||
import { createWriteStream, chmodSync, statSync } from 'fs'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { NexeTarget } from '../target'
|
||||
import { STDIN_FLAG } from '../util'
|
||||
import mkdirp = require('mkdirp')
|
||||
import { platform } from "os";
|
||||
import { dirname, normalize, relative, resolve } from "path";
|
||||
import {
|
||||
createWriteStream,
|
||||
chmodSync,
|
||||
statSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
} from "fs";
|
||||
import { NexeCompiler } from "../compiler";
|
||||
import { NexeTarget } from "../target";
|
||||
import { STDIN_FLAG } from "../util";
|
||||
import { patchMachOExecutable } from "./mach-o";
|
||||
import mkdirp = require("mkdirp");
|
||||
|
||||
/**
|
||||
* The "cli" step detects the appropriate input. If no input options are passed,
|
||||
@@ -15,45 +23,53 @@ import mkdirp = require('mkdirp')
|
||||
* @param {*} compiler
|
||||
* @param {*} next
|
||||
*/
|
||||
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
await next()
|
||||
export default async function cli(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
await next();
|
||||
const { log } = compiler,
|
||||
target = compiler.options.targets.shift() as NexeTarget,
|
||||
deliverable = await compiler.compileAsync(target),
|
||||
output = normalize(compiler.output!)
|
||||
output = normalize(compiler.output!);
|
||||
|
||||
mkdirp.sync(dirname(output))
|
||||
mkdirp.sync(dirname(output));
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
const step = log.step('Writing result to file')
|
||||
const step = log.step("Writing result to file");
|
||||
deliverable
|
||||
.pipe(createWriteStream(output))
|
||||
.on('error', rej)
|
||||
.once('close', (e: Error) => {
|
||||
if (e) {
|
||||
rej(e)
|
||||
} else if (compiler.output) {
|
||||
.on("error", rej)
|
||||
.once("close", () => {
|
||||
if (compiler.output) {
|
||||
const output = compiler.output,
|
||||
mode = statSync(output).mode | 0o111,
|
||||
inputFileLogOutput = relative(
|
||||
process.cwd(),
|
||||
resolve(compiler.options.cwd, compiler.entrypoint || compiler.options.input)
|
||||
resolve(
|
||||
compiler.options.cwd,
|
||||
compiler.entrypoint || compiler.options.input
|
||||
)
|
||||
),
|
||||
outputFileLogOutput = relative(process.cwd(), output)
|
||||
outputFileLogOutput = relative(process.cwd(), output);
|
||||
|
||||
chmodSync(output, mode.toString(8).slice(-3))
|
||||
if (platform() === "darwin") {
|
||||
step.log("Preparing binary for macOS signing.");
|
||||
writeFileSync(output, patchMachOExecutable(readFileSync(output)));
|
||||
}
|
||||
|
||||
chmodSync(output, mode.toString(8).slice(-3));
|
||||
step.log(
|
||||
`Entry: '${
|
||||
compiler.stdinUsed
|
||||
? compiler.options.mangle
|
||||
? STDIN_FLAG
|
||||
: '[none]'
|
||||
: inputFileLogOutput
|
||||
`Entry: '${compiler.stdinUsed
|
||||
? compiler.options.mangle
|
||||
? STDIN_FLAG
|
||||
: "[none]"
|
||||
: inputFileLogOutput
|
||||
}' written to: ${outputFileLogOutput}`
|
||||
)
|
||||
compiler.quit()
|
||||
res(output)
|
||||
);
|
||||
compiler.quit();
|
||||
res(output);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+136
-45
@@ -1,48 +1,126 @@
|
||||
import download = require('download')
|
||||
import { pathExistsAsync } from '../util'
|
||||
import { LogStep } from '../logger'
|
||||
import { IncomingMessage } from 'http'
|
||||
import { NexeCompiler, NexeError } from '../compiler'
|
||||
import { dirname } from 'path'
|
||||
import axios from "axios";
|
||||
import { pathExistsAsync } from "../util";
|
||||
import { LogStep } from "../logger";
|
||||
import { NexeCompiler, NexeError } from "../compiler";
|
||||
import { dirname } from "path";
|
||||
import { createWriteStream } from "fs";
|
||||
import { pipeline } from "stream/promises";
|
||||
import { createGunzip } from "zlib";
|
||||
import * as tar from 'tar';
|
||||
import fs from "fs/promises";
|
||||
|
||||
function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) {
|
||||
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
|
||||
return download(url, dest, Object.assign(options, { extract: true, strip: 1 }))
|
||||
.on('response', (res: IncomingMessage) => {
|
||||
const total = +res.headers['content-length']!
|
||||
let current = 0
|
||||
res.on('data', (data) => {
|
||||
current += data.length
|
||||
setText((current / total) * 100)
|
||||
if (current === total) {
|
||||
step.log('Extracting Node...')
|
||||
}
|
||||
})
|
||||
async function downloadWithProgress(
|
||||
url: string,
|
||||
dest: string,
|
||||
options: any = {},
|
||||
step?: LogStep
|
||||
): Promise<void> {
|
||||
// Ensure destination directory exists
|
||||
await fs.mkdir(dirname(dest), { recursive: true });
|
||||
|
||||
const response = await axios({
|
||||
url,
|
||||
method: "GET",
|
||||
responseType: "stream",
|
||||
...options,
|
||||
});
|
||||
|
||||
const total = parseInt(String(response.headers["content-length"] || "0"), 10);
|
||||
let current = 0;
|
||||
|
||||
// Create write stream
|
||||
const writer = createWriteStream(dest);
|
||||
|
||||
// Handle progress
|
||||
response.data.on("data", (chunk: Buffer) => {
|
||||
current += chunk.length;
|
||||
if (step && total > 0) {
|
||||
step.modify(`Downloading...${((current / total) * 100).toFixed()}%`);
|
||||
}
|
||||
});
|
||||
|
||||
// Pipe the response to file
|
||||
await pipeline(response.data, writer);
|
||||
|
||||
if (step) {
|
||||
step.log(`Download completed: ${dest}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchNodeSourceAsync(
|
||||
dest: string,
|
||||
url: string,
|
||||
step: LogStep,
|
||||
options = {}
|
||||
) {
|
||||
// Ensure destination directory exists
|
||||
await fs.mkdir(dest, { recursive: true });
|
||||
|
||||
const setText = (p: number) =>
|
||||
step.modify(`Downloading Node: ${p.toFixed()}%...`);
|
||||
|
||||
// Download the file first
|
||||
const tempFile = dest + ".tar.gz";
|
||||
const response = await axios({
|
||||
url,
|
||||
method: "GET",
|
||||
responseType: "stream",
|
||||
...options,
|
||||
});
|
||||
|
||||
const total = parseInt(String(response.headers["content-length"] || "0"), 10);
|
||||
let current = 0;
|
||||
|
||||
// Create write stream for the temp file
|
||||
const writer = createWriteStream(tempFile);
|
||||
|
||||
// Track progress
|
||||
response.data.on("data", (chunk: Buffer) => {
|
||||
current += chunk.length;
|
||||
if (total > 0) {
|
||||
setText((current / total) * 100);
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for download to complete
|
||||
await pipeline(response.data, writer);
|
||||
|
||||
step.log("Extracting Node...");
|
||||
|
||||
// Extract the tar.gz file
|
||||
await pipeline(
|
||||
createReadStream(tempFile),
|
||||
createGunzip(),
|
||||
tar.x({
|
||||
cwd: dest,
|
||||
strip: 1,
|
||||
})
|
||||
.then(() => step.log(`Node source extracted to: ${dest}`))
|
||||
);
|
||||
|
||||
// Clean up temp file
|
||||
await fs.unlink(tempFile);
|
||||
|
||||
step.log(`Node source extracted to: ${dest}`);
|
||||
}
|
||||
|
||||
async function fetchPrebuiltBinary(compiler: NexeCompiler, step: any) {
|
||||
const { target, remoteAsset } = compiler,
|
||||
filename = compiler.getNodeExecutableLocation(target)
|
||||
filename = compiler.getNodeExecutableLocation(target);
|
||||
|
||||
try {
|
||||
await download(remoteAsset, dirname(filename), compiler.options.downloadOptions).on(
|
||||
'response',
|
||||
(res: IncomingMessage) => {
|
||||
const total = +res.headers['content-length']!
|
||||
let current = 0
|
||||
res.on('data', (data) => {
|
||||
current += data.length
|
||||
step!.modify(`Downloading...${((current / total) * 100).toFixed()}%`)
|
||||
})
|
||||
}
|
||||
)
|
||||
await downloadWithProgress(
|
||||
remoteAsset,
|
||||
filename,
|
||||
compiler.options.downloadOptions,
|
||||
step
|
||||
);
|
||||
} catch (e: any) {
|
||||
if (e.statusCode === 404) {
|
||||
throw new NexeError(`${remoteAsset} is not available, create it using the --build flag`)
|
||||
if (e.response?.status === 404) {
|
||||
throw new NexeError(
|
||||
`${remoteAsset} is not available, create it using the --build flag`
|
||||
);
|
||||
} else {
|
||||
throw new NexeError('Error downloading prebuilt binary: ' + e)
|
||||
throw new NexeError("Error downloading prebuilt binary: " + e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,27 +130,40 @@ async function fetchPrebuiltBinary(compiler: NexeCompiler, step: any) {
|
||||
* @param {*} compiler
|
||||
* @param {*} next
|
||||
*/
|
||||
export default async function downloadNode(compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
export default async function downloadNode(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
const { src, log, target } = compiler,
|
||||
{ version } = target,
|
||||
{ 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(
|
||||
`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),
|
||||
downloadExists = await pathExistsAsync(build ? src : exeLocation)
|
||||
exeLocation = compiler.getNodeExecutableLocation(
|
||||
build ? undefined : target
|
||||
),
|
||||
downloadExists = await pathExistsAsync(build ? src : exeLocation);
|
||||
|
||||
if (downloadExists) {
|
||||
step.log('Already downloaded...')
|
||||
return next()
|
||||
step.log("Already downloaded...");
|
||||
return next();
|
||||
}
|
||||
|
||||
if (build) {
|
||||
await fetchNodeSourceAsync(src, url, step, downloadOptions)
|
||||
await fetchNodeSourceAsync(src, url, step, downloadOptions);
|
||||
} else {
|
||||
await fetchPrebuiltBinary(compiler, step)
|
||||
await fetchPrebuiltBinary(compiler, step);
|
||||
}
|
||||
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
|
||||
// Helper function to create a readable stream
|
||||
function createReadStream(path: string) {
|
||||
return require("fs").createReadStream(path);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2021 Vercel, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* This code is originally from the [pkg](https://github.com/vercel/pkg).
|
||||
*/
|
||||
|
||||
function parseCStr(buf: Buffer) {
|
||||
for (let i = 0; i < buf.length; i += 1) {
|
||||
if (buf[i] === 0) {
|
||||
return buf.slice(0, i).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function patchCommand(type: number, buf: Buffer, file: Buffer) {
|
||||
// segment_64
|
||||
if (type === 0x19) {
|
||||
const name = parseCStr(buf.slice(0, 16));
|
||||
|
||||
if (name === "__LINKEDIT") {
|
||||
const fileoff = buf.readBigUInt64LE(32);
|
||||
const vmsizePatched = BigInt(file.length) - fileoff;
|
||||
const filesizePatched = vmsizePatched;
|
||||
|
||||
buf.writeBigUInt64LE(vmsizePatched, 24);
|
||||
buf.writeBigUInt64LE(filesizePatched, 40);
|
||||
}
|
||||
}
|
||||
|
||||
// symtab
|
||||
if (type === 0x2) {
|
||||
const stroff = buf.readUInt32LE(8);
|
||||
const strsizePatched = file.length - stroff;
|
||||
|
||||
buf.writeUInt32LE(strsizePatched, 12);
|
||||
}
|
||||
}
|
||||
|
||||
function patchMachOExecutable(file: Buffer) {
|
||||
const align = 8;
|
||||
const hsize = 32;
|
||||
|
||||
const ncmds = file.readUInt32LE(16);
|
||||
const buf = file.slice(hsize);
|
||||
|
||||
for (let offset = 0, i = 0; i < ncmds; i += 1) {
|
||||
const type = buf.readUInt32LE(offset);
|
||||
|
||||
offset += 4;
|
||||
const size = buf.readUInt32LE(offset) - 8;
|
||||
|
||||
offset += 4;
|
||||
patchCommand(type, buf.slice(offset, offset + size), file);
|
||||
|
||||
offset += size;
|
||||
if (offset & align) {
|
||||
offset += align - (offset & align);
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
export { patchMachOExecutable };
|
||||
+25
-17
@@ -1,25 +1,33 @@
|
||||
import { each } from '../util'
|
||||
import * as globs from 'globby'
|
||||
import { resolve } from 'path'
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { each } from "../util";
|
||||
import globs from "globby";
|
||||
import { resolve } from "path";
|
||||
import { NexeCompiler } from "../compiler";
|
||||
|
||||
export default async function resource(compiler: NexeCompiler, next: () => Promise<any>) {
|
||||
const { cwd, resources } = compiler.options
|
||||
export default async function resource(
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<any>
|
||||
) {
|
||||
const { cwd, resources } = compiler.options;
|
||||
if (!resources.length) {
|
||||
return next()
|
||||
return next();
|
||||
}
|
||||
const step = compiler.log.step('Bundling Resources...')
|
||||
let count = 0
|
||||
const step = compiler.log.step("Bundling Resources...");
|
||||
let count = 0;
|
||||
|
||||
// workaround for https://github.com/sindresorhus/globby/issues/127
|
||||
// and https://github.com/mrmlnc/fast-glob#pattern-syntax
|
||||
const resourcesWithForwardSlashes = resources.map((r) => r.replace(/\\/g, '/'))
|
||||
const resourcesWithForwardSlashes = resources.map((r) =>
|
||||
r.replace(/\\/g, "/")
|
||||
);
|
||||
|
||||
await each(globs(resourcesWithForwardSlashes, { cwd, onlyFiles: true }), async (file) => {
|
||||
count++
|
||||
step.log(`Including file: ${file}`)
|
||||
await compiler.addResource(resolve(cwd, file))
|
||||
})
|
||||
step.log(`Included ${count} file(s)`)
|
||||
return next()
|
||||
await each(
|
||||
globs(resourcesWithForwardSlashes, { cwd, onlyFiles: true }),
|
||||
async (file) => {
|
||||
count++;
|
||||
step.log(`Including file: ${file}`);
|
||||
await compiler.addResource(resolve(cwd, file));
|
||||
}
|
||||
);
|
||||
step.log(`Included ${count} file(s)`);
|
||||
return next();
|
||||
}
|
||||
|
||||
+26
-11
@@ -1,17 +1,32 @@
|
||||
import { NexeCompiler } from '../compiler'
|
||||
import { wrap } from '../util'
|
||||
import { NexeCompiler } from "../compiler";
|
||||
import { wrap } from "../util";
|
||||
|
||||
export default async function (compiler: NexeCompiler, next: () => Promise<void>) {
|
||||
await next()
|
||||
export default async function (
|
||||
compiler: NexeCompiler,
|
||||
next: () => Promise<void>
|
||||
) {
|
||||
await next();
|
||||
compiler.shims.push(
|
||||
wrap(
|
||||
'' +
|
||||
'{{replace:lib/fs/patch.js}}' +
|
||||
'\nshimFs(process.__nexe)' +
|
||||
`\n${compiler.options.fs ? '' : 'restoreFs()'}`
|
||||
[
|
||||
"process.__nexe = {};",
|
||||
"const fsPatcher = (function() {",
|
||||
"const module = {exports: {}};",
|
||||
"const exports = module.exports;",
|
||||
'{{file("lib/fs/patch.bundle.js")}}',
|
||||
"return module.exports;",
|
||||
"})()",
|
||||
"fsPatcher.shimFs(process.__nexe);",
|
||||
// Re-apply ENOENT-tolerant realpathSync after shimFs overwrites boot-nexe's patch.
|
||||
// Node 22.22+ / 24 no longer catches ENOENT in ESM finalizeResolution.
|
||||
// ESM resolver captures fs.realpathSync by destructuring at first ESM load,
|
||||
// which happens after the shim runs — so this wrapper is what it captures.
|
||||
"(function(){var _fs=require('fs'),_o=_fs.realpathSync;_fs.realpathSync=function(p,o){try{return _o.call(this,p,o);}catch(e){if(e&&e.code==='ENOENT')return p;throw e;}};_fs.realpathSync.native=_o.native||_o;})()",
|
||||
compiler.options.fs ? "" : "restoreFs();",
|
||||
].join("\n")
|
||||
//TODO support only restoring specific methods
|
||||
)
|
||||
)
|
||||
);
|
||||
compiler.shims.push(
|
||||
wrap(`
|
||||
if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
|
||||
@@ -20,7 +35,7 @@ export default async function (compiler: NexeCompiler, next: () => Promise<void>
|
||||
delete process.env.NODE_UNIQUE_ID
|
||||
}
|
||||
`)
|
||||
)
|
||||
);
|
||||
|
||||
compiler.shims.push(
|
||||
wrap(`
|
||||
@@ -32,5 +47,5 @@ export default async function (compiler: NexeCompiler, next: () => Promise<void>
|
||||
process.argv.splice(1,0, entry)
|
||||
}
|
||||
`)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
+61
-50
@@ -1,96 +1,107 @@
|
||||
export type NodePlatform = 'windows' | 'mac' | 'alpine' | 'linux'
|
||||
export type NodeArch = 'x86' | 'x64' | 'arm' | 'arm64'
|
||||
export type NodePlatform = "windows" | "mac" | "alpine" | "linux";
|
||||
export type NodeArch = "x86" | "x64" | "arm" | "arm64";
|
||||
|
||||
const platforms: NodePlatform[] = ['windows', 'mac', 'alpine', 'linux'],
|
||||
architectures: NodeArch[] = ['x86', 'x64', 'arm', 'arm64']
|
||||
const platforms: NodePlatform[] = ["windows", "mac", "alpine", "linux"],
|
||||
architectures: NodeArch[] = ["x86", "x64", "arm", "arm64"];
|
||||
|
||||
export { platforms, architectures }
|
||||
export { platforms, architectures };
|
||||
|
||||
export interface NexeTarget {
|
||||
version: string
|
||||
platform: NodePlatform | string
|
||||
arch: NodeArch | string
|
||||
version: string;
|
||||
platform: NodePlatform | string;
|
||||
arch: NodeArch | string;
|
||||
}
|
||||
|
||||
const prettyPlatform: { [key: string]: NodePlatform } = {
|
||||
win32: 'windows',
|
||||
windows: 'windows',
|
||||
win: 'windows',
|
||||
darwin: 'mac',
|
||||
macos: 'mac',
|
||||
mac: 'mac',
|
||||
linux: 'linux',
|
||||
static: 'alpine',
|
||||
alpine: 'alpine',
|
||||
}
|
||||
win32: "windows",
|
||||
windows: "windows",
|
||||
win: "windows",
|
||||
darwin: "mac",
|
||||
macos: "mac",
|
||||
mac: "mac",
|
||||
linux: "linux",
|
||||
static: "alpine",
|
||||
alpine: "alpine",
|
||||
};
|
||||
|
||||
const prettyArch: { [key: string]: NodeArch } = {
|
||||
x86: 'x86',
|
||||
arm6: 'arm',
|
||||
arm64: 'arm64',
|
||||
arm6l: 'arm',
|
||||
arm: 'arm',
|
||||
arm7: 'arm',
|
||||
arm7l: 'arm',
|
||||
amd64: 'x64',
|
||||
ia32: 'x86',
|
||||
x32: 'x86',
|
||||
x64: 'x64',
|
||||
}
|
||||
x86: "x86",
|
||||
arm6: "arm",
|
||||
arm64: "arm64",
|
||||
arm6l: "arm",
|
||||
arm: "arm",
|
||||
arm7: "arm",
|
||||
arm7l: "arm",
|
||||
amd64: "x64",
|
||||
ia32: "x86",
|
||||
x32: "x86",
|
||||
x64: "x64",
|
||||
};
|
||||
|
||||
function isVersion(x: string) {
|
||||
if (!x) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
return /^[\d]+$/.test(x.replace(/v|\.|\s+/g, ''))
|
||||
return /^[\d]+$/.test(x.replace(/v|\.|\s+/g, ""));
|
||||
}
|
||||
|
||||
function isPlatform(x: string): x is NodePlatform {
|
||||
return x in prettyPlatform
|
||||
return x in prettyPlatform;
|
||||
}
|
||||
|
||||
function isArch(x: string): x is NodeArch {
|
||||
return x in prettyArch
|
||||
return x in prettyArch;
|
||||
}
|
||||
|
||||
class Target implements NexeTarget {
|
||||
constructor(public arch: NodeArch, public platform: NodePlatform, public version: string) {}
|
||||
constructor(
|
||||
public arch: NodeArch,
|
||||
public platform: NodePlatform,
|
||||
public version: string
|
||||
) { }
|
||||
toJSON() {
|
||||
return this.toString()
|
||||
return this.toString();
|
||||
}
|
||||
toString() {
|
||||
return `${this.platform}-${this.arch}-${this.version}`
|
||||
return `${this.platform}-${this.arch}-${this.version}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function targetsEqual(a: NexeTarget, b: NexeTarget) {
|
||||
return a.arch === b.arch && a.platform === b.platform && a.version === b.version
|
||||
return (
|
||||
a.arch === b.arch && a.platform === b.platform && a.version === b.version
|
||||
);
|
||||
}
|
||||
|
||||
export function getTarget(target: string | Partial<NexeTarget> = ''): NexeTarget {
|
||||
const currentArch = process.arch
|
||||
let arch = currentArch in prettyArch ? prettyArch[process.arch] : (process.arch as NodeArch),
|
||||
export function getTarget(
|
||||
target: string | Partial<NexeTarget> = ""
|
||||
): NexeTarget {
|
||||
const currentArch = process.arch;
|
||||
let arch =
|
||||
currentArch in prettyArch
|
||||
? prettyArch[process.arch]
|
||||
: (process.arch as NodeArch),
|
||||
platform = prettyPlatform[process.platform],
|
||||
version = process.version.slice(1)
|
||||
version = process.version.slice(1);
|
||||
|
||||
if (typeof target !== 'string') {
|
||||
target = `${target.platform}-${target.arch}-${target.version}`
|
||||
if (typeof target !== "string") {
|
||||
target = `${target.platform}-${target.arch}-${target.version}`;
|
||||
}
|
||||
|
||||
target
|
||||
.toLowerCase()
|
||||
.split('-')
|
||||
.split("-")
|
||||
.forEach((x) => {
|
||||
if (isVersion(x)) {
|
||||
version = x.replace(/v/g, '')
|
||||
version = x.replace(/v/g, "");
|
||||
}
|
||||
if (isPlatform(x)) {
|
||||
platform = prettyPlatform[x]
|
||||
platform = prettyPlatform[x];
|
||||
}
|
||||
if (isArch(x)) {
|
||||
arch = prettyArch[x]
|
||||
arch = prettyArch[x];
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return new Target(arch, platform, version)
|
||||
return new Target(arch, platform, version);
|
||||
}
|
||||
|
||||
Vendored
-23
@@ -1,23 +0,0 @@
|
||||
declare module 'got' {
|
||||
interface GotFn {
|
||||
(url: string, options?: any): Promise<{ body: string }>
|
||||
stream(url: string, optoins?: any): any
|
||||
}
|
||||
const got: GotFn
|
||||
export = got
|
||||
}
|
||||
declare module 'download' {
|
||||
import { Duplex } from 'stream'
|
||||
interface DownloadOptions {
|
||||
extract?: boolean
|
||||
strip?: number
|
||||
filename?: string
|
||||
proxy?: string
|
||||
}
|
||||
function download(
|
||||
url: string,
|
||||
destination?: string | DownloadOptions,
|
||||
options?: DownloadOptions
|
||||
): PromiseLike<Buffer> & Duplex
|
||||
export = download
|
||||
}
|
||||
+40
-40
@@ -1,100 +1,100 @@
|
||||
import { readFile, writeFile, stat } from 'fs'
|
||||
import { execFile } from 'child_process'
|
||||
import { promisify } from 'util'
|
||||
import rimraf = require('rimraf')
|
||||
import { readFile, writeFile, stat } from "fs";
|
||||
import { execFile } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import rimraf = require("rimraf");
|
||||
|
||||
const rimrafAsync = promisify(rimraf)
|
||||
export const STDIN_FLAG = '[stdin]'
|
||||
const rimrafAsync = promisify(rimraf);
|
||||
export const STDIN_FLAG = "[stdin]";
|
||||
|
||||
export async function each<T>(
|
||||
list: T[] | Promise<T[]>,
|
||||
action: (item: T, index: number, list: T[]) => Promise<any>
|
||||
) {
|
||||
const l = await list
|
||||
return Promise.all(l.map(action))
|
||||
const l = await list;
|
||||
return Promise.all(l.map(action));
|
||||
}
|
||||
|
||||
export function wrap(code: string) {
|
||||
return '!(function () {' + code + '})();'
|
||||
return "!(function () {" + code + "})();";
|
||||
}
|
||||
|
||||
function falseOnEnoent(e: any) {
|
||||
if (e.code === 'ENOENT') {
|
||||
return false
|
||||
if (e.code === "ENOENT") {
|
||||
return false;
|
||||
}
|
||||
throw e
|
||||
throw e;
|
||||
}
|
||||
|
||||
function padRight(str: string, l: number) {
|
||||
return (str + ' '.repeat(l)).substr(0, l)
|
||||
return (str + " ".repeat(l)).slice(0, l);
|
||||
}
|
||||
|
||||
const bound: MethodDecorator = function bound<T>(
|
||||
target: Object,
|
||||
propertyKey: string | Symbol,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: TypedPropertyDescriptor<T>
|
||||
) {
|
||||
const configurable = true
|
||||
const configurable = true;
|
||||
return {
|
||||
configurable,
|
||||
get(this: T) {
|
||||
const value = (descriptor.value as any).bind(this)
|
||||
const value = (descriptor.value as any).bind(this);
|
||||
Object.defineProperty(this, propertyKey as string, {
|
||||
configurable,
|
||||
value,
|
||||
writable: true,
|
||||
})
|
||||
return value
|
||||
});
|
||||
return value;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function dequote(input: string) {
|
||||
input = input.trim()
|
||||
const singleQuote = input.startsWith("'") && input.endsWith("'")
|
||||
const doubleQuote = input.startsWith('"') && input.endsWith('"')
|
||||
input = input.trim();
|
||||
const singleQuote = input.startsWith("'") && input.endsWith("'");
|
||||
const doubleQuote = input.startsWith('"') && input.endsWith('"');
|
||||
if (singleQuote || doubleQuote) {
|
||||
return input.slice(1).slice(0, -1)
|
||||
return input.slice(1).slice(0, -1);
|
||||
}
|
||||
return input
|
||||
return input;
|
||||
}
|
||||
|
||||
export interface ReadFileAsync {
|
||||
(path: string): Promise<Buffer>
|
||||
(path: string, encoding: string): Promise<string>
|
||||
(path: string): Promise<Buffer>;
|
||||
(path: string, encoding: string): Promise<string>;
|
||||
}
|
||||
|
||||
const readFileAsync = promisify(readFile)
|
||||
const writeFileAsync = promisify(writeFile)
|
||||
const statAsync = promisify(stat)
|
||||
const execFileAsync = promisify(execFile)
|
||||
const isWindows = process.platform === 'win32'
|
||||
const readFileAsync = promisify(readFile);
|
||||
const writeFileAsync = promisify(writeFile);
|
||||
const statAsync = promisify(stat);
|
||||
const execFileAsync = promisify(execFile);
|
||||
const isWindows = process.platform === "win32";
|
||||
|
||||
function pathExistsAsync(path: string) {
|
||||
return statAsync(path)
|
||||
.then((x) => true)
|
||||
.catch(falseOnEnoent)
|
||||
.catch(falseOnEnoent);
|
||||
}
|
||||
|
||||
function isDirectoryAsync(path: string) {
|
||||
return statAsync(path)
|
||||
.then((x) => x.isDirectory())
|
||||
.catch(falseOnEnoent)
|
||||
.catch(falseOnEnoent);
|
||||
}
|
||||
/**
|
||||
* @param version See if this version is greather than the second one
|
||||
* @param operand Version to compare against
|
||||
*/
|
||||
function semverGt(version: string, operand: string) {
|
||||
const [cMajor, cMinor, cPatch] = version.split('.').map(Number)
|
||||
let [major, minor, patch] = operand.split('.').map(Number)
|
||||
if (!minor) minor = 0
|
||||
if (!patch) patch = 0
|
||||
const [cMajor, cMinor, cPatch] = version.split(".").map(Number);
|
||||
let [major, minor, patch] = operand.split(".").map(Number);
|
||||
if (!minor) minor = 0;
|
||||
if (!patch) patch = 0;
|
||||
return (
|
||||
cMajor > major ||
|
||||
(cMajor === major && cMinor > minor) ||
|
||||
(cMajor === major && cMinor === minor && cPatch > patch)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -110,4 +110,4 @@ export {
|
||||
pathExistsAsync,
|
||||
isDirectoryAsync,
|
||||
writeFileAsync,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
'use strict'
|
||||
|
||||
const { resolve } = require('path')
|
||||
const resolvePath = (path) => resolve(__dirname, '..', path)
|
||||
|
||||
async function compile() {
|
||||
const executableSuffix = require('os').platform().startsWith('win') ? '.exe' : ''
|
||||
const builtExecutablePath = require('path').join(process.env.NEXE_TMP, process.versions.node, `out/Release/node${executableSuffix}`)
|
||||
try {
|
||||
require('fs').unlinkSync(builtExecutablePath)
|
||||
} catch(e) {}
|
||||
const nexe = require('..')
|
||||
console.error('Building asset')
|
||||
return nexe.compile({
|
||||
loglevel: 'verbose',
|
||||
python: process.env.PYTHON || 'python',
|
||||
mangle: false,
|
||||
build: true,
|
||||
output: process.env.NEXE_ASSET || `nexe-asset${executableSuffix}`,
|
||||
input: resolvePath('test/asset-build-input.js'),
|
||||
configure: process.env.MUSL_BUILD ? ['--fully-static'] : [],
|
||||
temp: process.env.NEXE_TMP,
|
||||
})
|
||||
}
|
||||
|
||||
if (!('MAKEFLAGS' in process.env)) {
|
||||
process.env.MAKEFLAGS = `-j${require('os').cpus().length + 1}`
|
||||
}
|
||||
|
||||
compile()
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict'
|
||||
|
||||
const { resolve } = require('path')
|
||||
const resolvePath = (path) => resolve(__dirname, '..', path)
|
||||
|
||||
async function build() {
|
||||
const nexe = require('..')
|
||||
console.error('Doing test build using asset')
|
||||
if (!require('fs').existsSync(process.env.NEXE_ASSET)) {
|
||||
console.error(`Pre-built asset not found at ${process.env.NEXE_ASSET}`)
|
||||
process.exitCode = 1
|
||||
return
|
||||
}
|
||||
return nexe.compile({
|
||||
loglevel: 'verbose',
|
||||
python: process.env.PYTHON || 'python',
|
||||
build: true,
|
||||
output: resolvePath('integration-tests'),
|
||||
input: resolvePath('test/integration/index.js'),
|
||||
resources: [resolvePath('test/integration'), resolvePath('node_modules')],
|
||||
asset: process.env.NEXE_ASSET,
|
||||
})
|
||||
}
|
||||
|
||||
build()
|
||||
+93
-69
@@ -1,116 +1,140 @@
|
||||
import * as nexe from '../lib/nexe'
|
||||
import { getUnBuiltReleases, getLatestGitRelease } from '../lib/releases'
|
||||
import { runDockerBuild } from './docker'
|
||||
import { getTarget, targetsEqual, NexeTarget } from '../lib/target'
|
||||
import { pathExistsAsync, readFileAsync, execFileAsync, semverGt } from '../lib/util'
|
||||
import got from 'got'
|
||||
import { cpus } from 'os'
|
||||
import * as nexe from "../lib/nexe";
|
||||
import { getUnBuiltReleases, getLatestGitRelease } from "../lib/releases";
|
||||
import { runDockerBuild } from "./docker";
|
||||
import { getTarget, targetsEqual, NexeTarget } from "../lib/target";
|
||||
import {
|
||||
pathExistsAsync,
|
||||
readFileAsync,
|
||||
execFileAsync,
|
||||
semverGt,
|
||||
} from "../lib/util";
|
||||
import axios from "axios";
|
||||
import FormData from "form-data";
|
||||
import { cpus } from "os";
|
||||
|
||||
const env = process.env,
|
||||
isPullRequest = env.BUILD_REASON === 'PullRequest',
|
||||
isWindows = process.platform === 'win32',
|
||||
isLinux = process.platform === 'linux',
|
||||
buildHost = env.AGENT_JOBNAME || (isWindows && 'windows_2017_2015') || '',
|
||||
isMac = process.platform === 'darwin',
|
||||
isPullRequest = env.BUILD_REASON === "PullRequest",
|
||||
isWindows = process.platform === "win32",
|
||||
isLinux = process.platform === "linux",
|
||||
buildHost = env.AGENT_JOBNAME || (isWindows && "windows_2017_2015") || "",
|
||||
isMac = process.platform === "darwin",
|
||||
headers = {
|
||||
Authorization: 'token ' + env.GITHUB_TOKEN,
|
||||
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)',
|
||||
}
|
||||
Authorization: "token " + env.GITHUB_TOKEN,
|
||||
"User-Agent": "nexe (https://www.npmjs.com/package/nexe)",
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
if (!isPullRequest) {
|
||||
build().catch((x) => {
|
||||
console.error(x)
|
||||
process.exit(1)
|
||||
})
|
||||
console.error(x);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function build() {
|
||||
const releases = await getUnBuiltReleases({ headers })
|
||||
const releases = await getUnBuiltReleases({ headers });
|
||||
if (!releases.length) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const windowsBuild = releases.find((x) => x.platform === 'windows'),
|
||||
macBuild = releases.find((x) => x.platform === 'mac'),
|
||||
linux = releases.find((x) => x.platform === 'linux'),
|
||||
alpine = releases.find((x) => x.platform === 'alpine')
|
||||
const windowsBuild = releases.find((x) => x.platform === "windows"),
|
||||
macBuild = releases.find((x) => x.platform === "mac"),
|
||||
linux = releases.find((x) => x.platform === "linux"),
|
||||
alpine = releases.find((x) => x.platform === "alpine");
|
||||
|
||||
let target: NexeTarget | undefined
|
||||
let target: NexeTarget | undefined;
|
||||
|
||||
if (env.NEXE_VERSION) target = getTarget(env.NEXE_VERSION)
|
||||
else if (isWindows) target = windowsBuild
|
||||
else if (isMac) target = macBuild
|
||||
else if (isLinux) target = linux
|
||||
if (buildHost.includes('alpine')) target = alpine
|
||||
if (env.NEXE_VERSION) target = getTarget(env.NEXE_VERSION);
|
||||
else if (isWindows) target = windowsBuild;
|
||||
else if (isMac) target = macBuild;
|
||||
else if (isLinux) target = linux;
|
||||
if (buildHost.includes("alpine")) target = alpine;
|
||||
|
||||
if (!target) {
|
||||
return console.log('Nothing to build...')
|
||||
return console.log("Nothing to build...");
|
||||
}
|
||||
|
||||
if (isWindows && buildHost.includes('2017') && !semverGt(target.version, '9.99.99')) {
|
||||
return console.log(`Not building ${target} on this host...`)
|
||||
if (
|
||||
isWindows &&
|
||||
buildHost.includes("2017") &&
|
||||
!semverGt(target.version, "9.99.99")
|
||||
) {
|
||||
return console.log(`Not building ${target} on this host...`);
|
||||
}
|
||||
if (isWindows && buildHost.includes('2015') && semverGt(target.version, '9.99.99')) {
|
||||
return console.log(`Not building ${target} on this host...`)
|
||||
if (
|
||||
isWindows &&
|
||||
buildHost.includes("2015") &&
|
||||
semverGt(target.version, "9.99.99")
|
||||
) {
|
||||
return console.log(`Not building ${target} on this host...`);
|
||||
}
|
||||
|
||||
const output = isWindows ? './out.exe' : './out',
|
||||
const output = isWindows ? "./out.exe" : "./out",
|
||||
options = {
|
||||
mangle: false,
|
||||
build: true,
|
||||
verbose: Boolean(env.NEXE_VERBOSE!),
|
||||
target,
|
||||
make: ['-j' + cpus().length],
|
||||
make: ["-j" + cpus().length],
|
||||
output,
|
||||
}
|
||||
console.log('Building: ' + target + ' on ' + buildHost)
|
||||
const stop = keepalive()
|
||||
};
|
||||
console.log("Building: " + target + " on " + buildHost);
|
||||
const stop = keepalive();
|
||||
|
||||
if (
|
||||
[/*'arm7l', 'arm6l', 'arm64', */ 'alpine'].includes(target.platform) &&
|
||||
buildHost.includes('alpine')
|
||||
[/*'arm7l', 'arm6l', 'arm64', */ "alpine"].includes(target.platform) &&
|
||||
buildHost.includes("alpine")
|
||||
) {
|
||||
await runDockerBuild(target)
|
||||
await runDockerBuild(target);
|
||||
} else {
|
||||
await nexe.compile(options)
|
||||
await nexe.compile(options);
|
||||
}
|
||||
stop()
|
||||
stop();
|
||||
if (await pathExistsAsync(output)) {
|
||||
await assertNexeBinary(output)
|
||||
await assertNexeBinary(output);
|
||||
const gitRelease = await getLatestGitRelease({ headers }),
|
||||
unbuiltReleases = await getUnBuiltReleases({ headers })
|
||||
unbuiltReleases = await getUnBuiltReleases({ headers });
|
||||
if (!unbuiltReleases.some((x) => targetsEqual(x, target!))) {
|
||||
console.log(`${target} already uploaded.`)
|
||||
process.exit(0)
|
||||
return
|
||||
console.log(`${target} already uploaded.`);
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
await got(gitRelease.upload_url.split('{')[0], {
|
||||
searchParams: { name: target.toString() },
|
||||
body: await readFileAsync(output),
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/octet-stream',
|
||||
},
|
||||
}).catch((reason) => {
|
||||
console.log(reason && reason.response && reason.response.body)
|
||||
throw reason
|
||||
})
|
||||
console.log(target + ' uploaded.')
|
||||
process.exit(0)
|
||||
|
||||
// Create form data for file upload
|
||||
const formData = new FormData();
|
||||
formData.append("file", await readFileAsync(output), {
|
||||
filename: target.toString(),
|
||||
contentType: "application/octet-stream",
|
||||
});
|
||||
|
||||
const uploadUrl = gitRelease.upload_url.split("{")[0];
|
||||
await axios
|
||||
.post(uploadUrl, formData, {
|
||||
params: { name: target.toString() },
|
||||
headers: {
|
||||
...headers,
|
||||
...formData.getHeaders(),
|
||||
},
|
||||
})
|
||||
.catch((reason) => {
|
||||
console.log(reason && reason.response && reason.response.data);
|
||||
throw reason;
|
||||
});
|
||||
console.log(target + " uploaded.");
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
function keepalive() {
|
||||
const keepalive = setInterval(() => console.log('Building...'), 300 * 1000)
|
||||
return () => clearInterval(keepalive)
|
||||
const keepalive = setInterval(() => console.log("Building..."), 300 * 1000);
|
||||
return () => clearInterval(keepalive);
|
||||
}
|
||||
|
||||
function assertNexeBinary(file: string) {
|
||||
return execFileAsync(file).catch((e) => {
|
||||
if (e && e.stack && e.stack.includes('Invalid Nexe binary')) {
|
||||
return
|
||||
if (e && e.stack && e.stack.includes("Invalid Nexe binary")) {
|
||||
return;
|
||||
}
|
||||
throw e
|
||||
})
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
+40
-29
@@ -1,12 +1,12 @@
|
||||
import { NexeTarget, architectures } from '../lib/target'
|
||||
import { writeFileAsync, readFileAsync } from '../lib/util'
|
||||
import got from 'got'
|
||||
import execa = require('execa')
|
||||
import { appendFileSync } from 'fs'
|
||||
import { NexeTarget, architectures } from "../lib/target";
|
||||
import { writeFileAsync, readFileAsync } from "../lib/util";
|
||||
import axios from "axios";
|
||||
import execa = require("execa");
|
||||
import { appendFileSync } from "fs";
|
||||
|
||||
function alpine(target: NexeTarget) {
|
||||
return `
|
||||
FROM ${target.arch === 'x64' ? '' : 'i386/'}alpine:3.12
|
||||
FROM ${target.arch === "x64" ? "" : "i386/"}alpine:3.12
|
||||
RUN apk add --no-cache curl make gcc g++ binutils-gold python linux-headers paxctl libgcc libstdc++ git vim tar gzip wget
|
||||
ENV NODE_VERSION=${target.version}
|
||||
ENV NEXE_VERSION=latest
|
||||
@@ -23,7 +23,7 @@ RUN rm /nexe_temp/\${NODE_VERSION}/out/Release/node && \
|
||||
npm install -g nexe@\${NEXE_VERSION}
|
||||
RUN echo "console.log('hello world')" >> index.js && \
|
||||
nexe --build --no-mangle --temp /nexe_temp -c="--fully-static" -o out
|
||||
`.trim()
|
||||
`.trim();
|
||||
}
|
||||
|
||||
function arm(target: NexeTarget) {
|
||||
@@ -34,35 +34,46 @@ WORKDIR /
|
||||
|
||||
RUN yarn global add nexe@\${NEXE_VERSION} && \
|
||||
nexe --build --no-mangle -o out -t ${target.version}
|
||||
`.trim()
|
||||
`.trim();
|
||||
}
|
||||
|
||||
export async function runDockerBuild(target: NexeTarget) {
|
||||
//todo switch on alpine and arm
|
||||
const dockerfile = alpine(target)
|
||||
await writeFileAsync('Dockerfile', dockerfile)
|
||||
const outFilename = 'nexe-docker-build-log.txt'
|
||||
await writeFileAsync(outFilename, '')
|
||||
let output: any = []
|
||||
const dockerfile = alpine(target);
|
||||
await writeFileAsync("Dockerfile", dockerfile);
|
||||
const outFilename = "nexe-docker-build-log.txt";
|
||||
await writeFileAsync(outFilename, "");
|
||||
const output: any = [];
|
||||
|
||||
try {
|
||||
output.push(await execa(`docker build -t nexe-docker .`, { shell: true }))
|
||||
output.push(await execa(`docker run -d --name nexe nexe-docker sh`, { shell: true }))
|
||||
output.push(await execa(`docker cp nexe:/out out`, { shell: true }))
|
||||
output.push(await execa(`docker rm nexe`, { shell: true }))
|
||||
} catch (e) {
|
||||
console.log('Error running docker')
|
||||
appendFileSync(outFilename, e.message)
|
||||
output.push(await execa(`docker build -t nexe-docker .`, { shell: true }));
|
||||
output.push(
|
||||
await execa(`docker run -d --name nexe nexe-docker sh`, { shell: true })
|
||||
);
|
||||
output.push(await execa(`docker cp nexe:/out out`, { shell: true }));
|
||||
output.push(await execa(`docker rm nexe`, { shell: true }));
|
||||
} catch (e: any) {
|
||||
console.log("Error running docker");
|
||||
appendFileSync(outFilename, e.message);
|
||||
} finally {
|
||||
output.forEach((x: any) => {
|
||||
appendFileSync(outFilename, x.stderr)
|
||||
appendFileSync(outFilename, x.stdout)
|
||||
})
|
||||
await got(`https://transfer.sh/${Math.random().toString(36).substring(2)}.txt`, {
|
||||
body: await readFileAsync(outFilename),
|
||||
method: 'PUT',
|
||||
})
|
||||
.then((x) => console.log('Posted docker log: ', x.body))
|
||||
.catch((e) => console.log('Error posting log', e))
|
||||
appendFileSync(outFilename, x.stderr);
|
||||
appendFileSync(outFilename, x.stdout);
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await axios.put(
|
||||
`https://transfer.sh/${Math.random().toString(36).substring(2)}.txt`,
|
||||
await readFileAsync(outFilename),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("Posted docker log: ", response.data);
|
||||
} catch (e: any) {
|
||||
console.log("Error posting log", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-28
@@ -1,38 +1,31 @@
|
||||
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).
|
||||
* '{{replace:path/to/file}}' => "file contents"
|
||||
* post build step to insert code files into code files.
|
||||
* And the package.json version.
|
||||
*/
|
||||
//inject('plugins/nexe-daemon/lib/index.js')
|
||||
cp('src/fs/package.json', 'lib/fs/package.json')
|
||||
cp('src/fs/bootstrap.js', 'lib/fs/bootstrap.js')
|
||||
cp('src/fs/README.md', 'lib/fs/README.md')
|
||||
cp("src/fs/package.json", "lib/fs/package.json");
|
||||
cp("src/fs/bootstrap.js", "lib/fs/bootstrap.js");
|
||||
cp("src/fs/README.md", "lib/fs/README.md");
|
||||
|
||||
inject('lib/patches/third-party-main.js')
|
||||
inject('lib/steps/shim.js')
|
||||
inject('lib/options.js', JSON.stringify(require('../package.json').version))
|
||||
inject("lib/patches/third-party-main.js");
|
||||
inject("lib/steps/shim.js");
|
||||
inject("lib/options.js", JSON.stringify(require("../package.json").version));
|
||||
|
||||
function inject(filename: string, ...replacements: string[]) {
|
||||
let contents = readFileSync(filename, 'utf8')
|
||||
contents = contents.replace(/('{{(.*)}}')/g, (substring: string, ...matches: string[]) => {
|
||||
if (!matches || !matches[1]) {
|
||||
return substring
|
||||
}
|
||||
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)
|
||||
console.log(`Wrote: ${filename}`)
|
||||
function inject(filename: string, version?: string) {
|
||||
const contents = template(readFileSync(filename, "utf8"), {
|
||||
interpolate: /['"]\{\{([\s\S]*?)\}\}['"]/,
|
||||
})({
|
||||
file: (path: string) => JSON.stringify(readFileSync(path, "utf8")),
|
||||
version,
|
||||
});
|
||||
writeFileSync(filename, contents);
|
||||
console.log(`Wrote: ${filename}`);
|
||||
}
|
||||
|
||||
function cp(from: string, to: string) {
|
||||
const file = readFileSync(from)
|
||||
writeFileSync(to, file)
|
||||
console.log('Copied: ', from, 'To: ', to)
|
||||
const file = readFileSync(from);
|
||||
writeFileSync(to, file);
|
||||
console.log("Copied: ", from, "To: ", to);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
},
|
||||
"include":[
|
||||
"*.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
const { mkdtemp, copyFile, realpath } = require('fs/promises')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const rimraf = require('rimraf')
|
||||
const cp = require('child_process')
|
||||
|
||||
async function runTests() {
|
||||
const tempdir = await realpath(await mkdtemp(path.join(os.tmpdir(), 'nexe-integration-tests-')))
|
||||
const secondTempdir = await mkdtemp(path.join(os.tmpdir(), 'nexe-integration-tests-without-executable-'))
|
||||
const executable = path.join(tempdir, path.basename(process.argv[0]))
|
||||
await copyFile(process.argv[0], executable)
|
||||
process.on('beforeExit', () => {
|
||||
try {
|
||||
rimraf.sync(tempdir)
|
||||
rimraf.sync(secondTempdir)
|
||||
} catch(e) {
|
||||
console.error(`Ignoring error during integration test cleanup of "${tempdir}".`)
|
||||
console.error(`Error: ${e}`)
|
||||
}
|
||||
})
|
||||
console.error('Running integration tests with the binary in the current working directory.')
|
||||
spawnExecutable(tempdir, () => {
|
||||
console.error('Running integration tests with the binary in another directory.')
|
||||
spawnExecutable(secondTempdir)
|
||||
})
|
||||
|
||||
function spawnExecutable(cwd, cb) {
|
||||
cp.spawn(executable,
|
||||
[
|
||||
path.join(tempdir, 'node_modules/mocha/bin/mocha.js'),
|
||||
path.join(tempdir, 'test/integration/tests.integration-spec.js')
|
||||
],
|
||||
{ stdio: ['inherit', 'inherit', 'inherit', 'ipc'], cwd }
|
||||
).on('exit', (code) => {
|
||||
process.exitCode = process.exitCode || code
|
||||
|
||||
if(cb) cb()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
runTests()
|
||||
@@ -0,0 +1,120 @@
|
||||
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 fs', () => {
|
||||
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())
|
||||
})
|
||||
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' })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -43,7 +43,7 @@ describe('options', () => {
|
||||
describe('remote', () => {
|
||||
it('should use default remote', () => {
|
||||
const options = normalizeOptions({})
|
||||
expect(options.remote).to.equal('https://github.com/nexe/nexe/releases/download/v3.3.3/')
|
||||
expect(options.remote).to.equal(`https://github.com/oxmc/nexe/releases/download/v${require('../package.json').version}/`)
|
||||
})
|
||||
it('should append trailing slash to third-party remote if necessary', () => {
|
||||
const options = normalizeOptions({
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"target":"es2015",
|
||||
"skipLibCheck": true,
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["es2018"],
|
||||
"module": "commonjs",
|
||||
"outDir": "./lib",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"rulesDirectory": ["tslint-plugin-prettier", "tslint-config-prettier"],
|
||||
"linterOptions": {
|
||||
"exclude": ["**/node_modules/**/*"]
|
||||
},
|
||||
"rules": {
|
||||
"prettier": [true, { "semi": false, "printWidth": 100, "singleQuote": true }]
|
||||
}
|
||||
}
|
||||
@@ -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