release: 5.0.0-beta
CI / infra:
- Replace azure-pipelines with GitHub Actions (build-asset.yml)
- Build and publish pre-built assets for windows-x64, mac-arm64,
linux-x64, and linux-x64-musl on tag push
- Remove EOL Node 18 and 20 from build matrix; default to lts/*, 22, 24
Bug fixes:
- fix: _assembleDeliverable deadlock — bundle.finalize() must be called
before MultiStream starts consuming; calling it in the Transform's
first-chunk callback deadlocked because archiver never emits without
finalize, so the sentinel and trailers were never written
- fix: ESM realpathSync ENOENT on Node 22.22+ / Node 24 — these versions
removed the ENOENT catch in finalizeResolution; VFS paths don't exist
on real FS so binding.lstat throws. Two-pronged fix:
(1) boot-nexe.ts: install ENOENT-tolerant wrapper before _compile()
(2) shim.ts: re-apply wrapper after shimFs() overwrites boot-nexe's
patch, so the ESM resolver captures the tolerant version at first
ESM load
- fix: replace got (unmaintained) with axios in download step
- fix: update default remote to oxmc/nexe releases
Features:
- macOS binary signing support (patchMachOExecutable)
- bundleRules option for fine-grained VFS include/exclude per module
- Improved SnapshotZipFS path handling and fallback behaviour
This commit is contained in:
@@ -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 }}
|
||||
@@ -140,9 +140,7 @@ compile({
|
||||
- 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 to use when evaluating the exports field of a package.
|
||||
Default: `['require', 'node', 'default']`.
|
||||
- `basePath?: string` – The virtual filesystem path where `node_modules` is mounted. Default: `'/snapshot/node_modules'`.
|
||||
- `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
|
||||
|
||||
Generated
+508
-523
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "nexe",
|
||||
"description": "Create a single executable out of your Node.js application",
|
||||
"license": "MIT",
|
||||
"version": "5.0.1-beta",
|
||||
"version": "5.0.0-beta",
|
||||
"contributors": [
|
||||
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
|
||||
"Jared Allard <jaredallard@outlook.com>",
|
||||
|
||||
+11
-7
@@ -106,16 +106,16 @@ export class NexeCompiler {
|
||||
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)) {
|
||||
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.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" : "");
|
||||
@@ -135,7 +135,7 @@ export class NexeCompiler {
|
||||
process.env.PATH = originalPath;
|
||||
} else {
|
||||
this.env = { ...process.env };
|
||||
python && (this.env.PYTHON = python);
|
||||
if (python) this.env.PYTHON = python;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,14 +314,18 @@ export class NexeCompiler {
|
||||
const sentinel = Buffer.from("<nexe~~sentinel>");
|
||||
|
||||
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(launchCode),
|
||||
this.bundle.toStream().pipe(
|
||||
bundleStream.pipe(
|
||||
new Transform({
|
||||
transform: (chunk, _, cb) => {
|
||||
vfsSize || this.bundle.finalize();
|
||||
chunk && (vfsSize += chunk.length);
|
||||
if (chunk) vfsSize += chunk.length;
|
||||
cb(null, chunk);
|
||||
},
|
||||
})
|
||||
|
||||
@@ -169,6 +169,19 @@ export class SnapshotZipFS extends BasePortableFakeFS {
|
||||
};
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
+106
-71
@@ -170,7 +170,15 @@ function shimFs(binary: NexeHeader, fs: typeof import("fs") = require("fs")) {
|
||||
const stat = fs.statSync(mappedPath);
|
||||
return stat.isDirectory() ? 1 : 0;
|
||||
} catch (e) {
|
||||
return -constants.ENOENT;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +240,68 @@ function shimFs(binary: NexeHeader, fs: typeof import("fs") = require("fs")) {
|
||||
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");
|
||||
@@ -240,7 +310,31 @@ function shimFs(binary: NexeHeader, fs: typeof import("fs") = require("fs")) {
|
||||
if (typeof capturedOrigFindPath === "function") {
|
||||
_origFindPath = capturedOrigFindPath;
|
||||
(Module as any)._findPath = function nexeFindPath(this: any, ...args: any[]) {
|
||||
// Try original first
|
||||
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;
|
||||
}
|
||||
if (pkg) {
|
||||
const vfsResult = resolveFromPkg(pkg, basePath, request, nodePath, log, isFile, fs);
|
||||
if (vfsResult) {
|
||||
log(`_findPath VFS resolved ${request} -> ${vfsResult}`);
|
||||
return vfsResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to original for everything else (real fs, relative, absolute)
|
||||
let result: any;
|
||||
try {
|
||||
result = capturedOrigFindPath.apply(this, args);
|
||||
@@ -249,82 +343,23 @@ function shimFs(binary: NexeHeader, fs: typeof import("fs") = require("fs")) {
|
||||
}
|
||||
if (result) return result;
|
||||
|
||||
const request: string = args[0];
|
||||
// Only handle bare module specifiers
|
||||
// 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 basePath = "/snapshot/node_modules";
|
||||
const pkgJsonPath = nodePath.posix.join(basePath, request, "package.json");
|
||||
log(`_findPath trying snapshot package.json: ${pkgJsonPath}`);
|
||||
let pkg: any;
|
||||
const basePath2 = "/snapshot/node_modules";
|
||||
const pkgJsonPath2 = nodePath.posix.join(basePath2, request, "package.json");
|
||||
log(`_findPath secondary snapshot check: ${pkgJsonPath2}`);
|
||||
let pkg2: any;
|
||||
try {
|
||||
pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8") as string);
|
||||
} catch (e) {
|
||||
return result; // not found in snapshot
|
||||
pkg2 = JSON.parse(fs.readFileSync(pkgJsonPath2, "utf8") as string);
|
||||
} catch (_) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 1. Try exports field with appropriate conditions (CJS, Node)
|
||||
if (pkg.exports) {
|
||||
const conditions = ["require", "node", "default"];
|
||||
const exportTarget = resolvePackageExports(pkg.exports, conditions);
|
||||
if (exportTarget) {
|
||||
const candidate = nodePath.posix.join(basePath, request, exportTarget);
|
||||
log(`_findPath trying exports target: ${candidate}`);
|
||||
if (isFile(candidate)) return candidate;
|
||||
// Try with extensions if the export target doesn't have one
|
||||
for (const ext of [".js", ".json", ".node"]) {
|
||||
const withExt = candidate + ext;
|
||||
if (isFile(withExt)) return withExt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback to main field
|
||||
let main = "index.js";
|
||||
if (typeof pkg.main === "string") {
|
||||
main = pkg.main;
|
||||
}
|
||||
|
||||
// Normalize main path
|
||||
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);
|
||||
log(`_findPath trying main: ${mainPath}`);
|
||||
|
||||
// Detailed check for main
|
||||
try {
|
||||
const st = fs.statSync(mainPath);
|
||||
if (st.isFile()) return mainPath;
|
||||
if (st.isDirectory()) {
|
||||
const idxPath = nodePath.posix.join(mainPath, "index.js");
|
||||
if (isFile(idxPath)) return idxPath;
|
||||
}
|
||||
} catch (e) {
|
||||
// mainPath may not exist; proceed to extension checks
|
||||
}
|
||||
|
||||
// 3. Try with extensions
|
||||
for (const ext of [".js", ".json", ".node"]) {
|
||||
const withExt = mainPath + ext;
|
||||
if (isFile(withExt)) return withExt;
|
||||
}
|
||||
|
||||
// 4. Common patterns: if main is missing, look for index.js in the package root
|
||||
const fallbackIndex = nodePath.posix.join(basePath, request, "index.js");
|
||||
if (isFile(fallbackIndex)) return fallbackIndex;
|
||||
|
||||
// 5. If package uses a dist subdirectory (like axios), try dist/index.js or dist/<name>.js
|
||||
const distIndex = nodePath.posix.join(basePath, request, "dist", "index.js");
|
||||
if (isFile(distIndex)) return distIndex;
|
||||
const distMain = nodePath.posix.join(basePath, request, "dist", request + ".js");
|
||||
if (isFile(distMain)) return distMain;
|
||||
|
||||
return result;
|
||||
return resolveFromPkg(pkg2, basePath2, request, nodePath, log, isFile, fs) ?? result;
|
||||
};
|
||||
}
|
||||
} catch (_) {
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ const defaults = {
|
||||
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",
|
||||
|
||||
@@ -77,6 +77,22 @@ const contentBuffer = Buffer.alloc(contentSize),
|
||||
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart);
|
||||
fs.closeSync(fd);
|
||||
|
||||
// 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
|
||||
|
||||
@@ -25,7 +25,7 @@ async function downloadWithProgress(
|
||||
...options,
|
||||
});
|
||||
|
||||
const total = parseInt(response.headers["content-length"] || "0", 10);
|
||||
const total = parseInt(String(response.headers["content-length"] || "0"), 10);
|
||||
let current = 0;
|
||||
|
||||
// Create write stream
|
||||
@@ -68,7 +68,7 @@ async function fetchNodeSourceAsync(
|
||||
...options,
|
||||
});
|
||||
|
||||
const total = parseInt(response.headers["content-length"] || "0", 10);
|
||||
const total = parseInt(String(response.headers["content-length"] || "0"), 10);
|
||||
let current = 0;
|
||||
|
||||
// Create write stream for the temp file
|
||||
|
||||
@@ -17,6 +17,11 @@ export default async function (
|
||||
"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
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user