Compare commits

...

13 Commits

Author SHA1 Message Date
calebboyd ad7346afb2 bump deps 2022-12-23 13:32:04 -06:00
calebboyd 0b0ac7109c chore: lint 2022-12-23 12:19:16 -06:00
calebboyd d361ed0c34 refactor: update all deps, ship esm (and cjs)
BREAKING: drop support for node < 14

BREAKING: removed snapshot, ico, and rc options

BREAKING: use python3 as default python binary
2022-12-23 12:17:51 -06:00
calebboyd fdd9d97db3 bump rc.2 2022-12-19 16:12:55 -06:00
bruce-one a746e00379 fix: node v18.12 startup (#1026)
Fixes https://github.com/nexe/nexe/issues/1019
2022-12-19 08:15:24 -06:00
calebboyd 1eeac1b538 Revert "build: add workflow for autoclose issues without response"
This reverts commit 37ee352408.
2022-08-10 08:32:37 -05:00
Caleb Boyd ac1d444b17 refactor: replace cherow with meriyah for parsing es (#986) 2022-05-05 11:50:46 -05:00
CommanderRoot af7e1cb1be refactor: replace deprecated String.prototype.substr() (#975)
.substr() is deprecated so we replace it with .slice() which works similarily but isn't deprecated

Signed-off-by: Tobias Speicher <rootcommander@gmail.com>
2022-05-03 10:51:58 -05:00
bmaupin ec1fcf66c6 docs: python 3 should be used (#972) 2022-03-17 13:26:31 -05:00
James McCallum 51b478a18d fix: use correct ignore ssl env var (#932) 2021-10-29 06:47:16 -05:00
Akihito Mikazuki 95050d0472 docs:python2 to python3 in cli help 2021-10-04 14:36:04 -05:00
calebboyd eb2ba5007a build: typecheck tasks 2021-09-27 12:59:55 -05:00
calebboyd e5ae6a124a chore: any error in build log 2021-09-27 09:46:03 -05:00
66 changed files with 6299 additions and 5010 deletions
+4
View File
@@ -0,0 +1,4 @@
FROM node:fermium-alpine3.10
RUN apk add --no-cache build-base linux-headers bash clang python3
SHELL ["/bin/bash", "--login", "-c"]
CMD ["bash"]
+16
View File
@@ -0,0 +1,16 @@
# action.yml
name: 'Hello World'
description: 'Greet someone and record the time'
inputs:
who-to-greet: # id of input
description: 'Who to greet'
required: true
default: 'World'
outputs:
time: # id of output
description: 'The time we greeted you'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.who-to-greet }}
+23
View File
@@ -0,0 +1,23 @@
FROM centos:7
RUN yum install -y which centos-release-scl && \
yum install -y devtoolset-9-gcc \
devtoolset-9-gcc-c++ \
make \
wget \
python3 && \
echo "source /opt/rh/devtoolset-9/enable" >> /etc/bashrc
SHELL ["/bin/bash", "--login", "-c"]
ADD flags.sh flags.sh
ARG NODE_VERSION=14.20.0
RUN wget https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}.tar.gz -O node.tar.gz && \
mkdir node && tar -zxf node.tar.gz -C node --strip-components=1 && \
rm node.tar.gz && \
cd node && \
. ../flags.sh && \
./configure $(echo $CONFIGURE_FLAGS) && \
make $(echo $MAKE_FLAGS) && make install
+16
View File
@@ -0,0 +1,16 @@
# action.yml
name: 'Hello World'
description: 'Greet someone and record the time'
inputs:
who-to-greet: # id of input
description: 'Who to greet'
required: true
default: 'World'
outputs:
time: # id of output
description: 'The time we greeted you'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.who-to-greet }}
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
ARCH=$(uname -m)
case $ARCH in
"x86_64")
CONFIGURE_FLAGS="--dest-cpu=x64"
;;
"aarch64")
;&
"armv7l")
CONFIGURE_FLAGS="--openssl-no-asm --dest-cpu=arm --with-arm-float-abi=hard"
;;
*)
CONFIGURE_FLAGS=""
;;
esac
MAKE_FLAGS="-j$(getconf _NPROCESSORS_ONLN)"
@@ -6,7 +6,6 @@ on:
issue_comment: issue_comment:
types: [created] types: [created]
schedule: schedule:
# 9AM Central
- cron: '0 14 * * *' - cron: '0 14 * * *'
jobs: jobs:
+52
View File
@@ -0,0 +1,52 @@
name: Base Builds
on:
push:
branches:
- refactor-old-node
jobs:
build:
strategy:
fail-fast: false
matrix:
version: ['14.20.0', '16.16.0', '18.5.0']
arch: [x64, x86]
plat: [arm, macos, windows, linux, alpine]
exclude:
- plat: arm
arch: x86
- plat: macos
arch: x86
include:
- plat: arm
os: ubuntu-20.04
- plat: macos
os: macos-12
- plat: windows
os: windows-2022
- plat: linux
os: ubuntu-20.04
- plat: alpine
os: ubuntu-20.04
name: 'Target: ${{matrix.plat}}-${{ matrix.arch }}-${{ matrix.version }} on ${{ matrix.os }}'
runs-on: ${{ matrix.os }}
steps:
- name: Setup Python
uses: actions/setup-python@v3.1.2
with:
python-version: '3.10.4'
- name: Setup Nasm
if: ${{ matrix.plat == 'windows' }}
uses: ilammy/setup-nasm@v1.3.0
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.version }}
- uses: actions/checkout@v3
with:
node-version: 18
- name: 'Install Dependencies'
run: npm ci
- name: 'Build nexe base binary'
env:
NEXE_TARGET: ${{ matrix.plat }}-${{ matrix.arch }}-${{ matrix.version }}
run: npx ts-node tasks/build.ts
+1
View File
@@ -0,0 +1 @@
18.12.1
+19
View File
@@ -0,0 +1,19 @@
{
"module": {
"type": "commonjs",
"ignoreDynamic": true
},
"jsc": {
"parser": {
"syntax": "typescript",
"dynamicImport": true,
"decorators": true
},
"transform": {
"legacyDecorator": true
},
"target": "es2019",
"externalHelpers": false
}
}
+4 -1
View File
@@ -1,3 +1,6 @@
{ {
"recommendations": [ "ms-vscode.vscode-typescript-tslint-plugin" ] "recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
]
} }
+28 -4
View File
@@ -1,7 +1,4 @@
{ {
"tslint.enable": true,
"tslint.run": "onType",
"tslint.autoFixOnSave": true,
"files.exclude": { "files.exclude": {
"**/.git": true, "**/.git": true,
"**/.svn": true, "**/.svn": true,
@@ -14,5 +11,32 @@
"**/node_modules": true, "**/node_modules": true,
"**/bower_components": true "**/bower_components": true
}, },
"typescript.tsdk": "node_modules\\typescript\\lib" "eslint.workingDirectories": [{ "mode": "auto" }],
"eslint.format.enable": true,
"editor.formatOnSave": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"[javascript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
} }
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2013-2018 Nexe Contributors Copyright (c) 2013-2022 Nexe Contributors
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
+9 -29
View File
@@ -23,7 +23,7 @@
- Distribute binaries without needing node / npm. - Distribute binaries without needing node / npm.
- Idempotent builds - Idempotent builds
- Start and deploy faster. - Start and deploy faster.
- Lockdown specific application versions, and easily rollback. - Lock-down specific application versions, and easily rollback.
- Flexible build pipeline - Flexible build pipeline
- Cross platform builds - Cross platform builds
@@ -51,7 +51,7 @@ Additional files or resources can be added to the binary by passing `-r "glob/pa
## Compiling the nexe Executable ## 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 ### Linux and macOS
[Prerequisites & details](https://github.com/nodejs/node/blob/master/BUILDING.md#unix-and-macos) [Prerequisites & details](https://github.com/nodejs/node/blob/master/BUILDING.md#unix-and-macos)
@@ -74,12 +74,11 @@ Install-BoxstarterPackage https://raw.githubusercontent.com/nodejs/node/master/t
**Set config:** **Set config:**
``` ```
npm config set msvs_version 2019 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). Where `2019` is the version of Visual Studio you have (if you have it).
**Notes:** **Notes:**
- The above works and has been tested with node.js `14.5.4` and `15.8.0`
- Python 3 and Python 2 can coexist and `nexe` will still work, considering the `set config` area above - Python 3 and Python 2 can coexist and `nexe` will still work, considering the `set config` area above
- Don't use `npm install windows-build-tools` unless you're having some type of issue, because the above commands configures and installs the latest/preferred too. - Don't use `npm install windows-build-tools` unless you're having some type of issue, because the above commands configures and installs the latest/preferred too.
@@ -117,14 +116,14 @@ compile({
- Output executable file path - Output executable file path
- default: same as `name` with an OS specific extension. - default: same as `name` with an OS specific extension.
- #### `target: string | object` - #### `target: string | object`
- An object or string describing platform-arch-version. e.g. `'windows-ia32-10.13.0'` - An object or string describing platform-arch-version. e.g. `'windows-ia32-18.1.0'`
- each segment is optional, and will be merged with the current environment - each segment is optional, and will be merged with the current environment
- Examples: ([full list](https://github.com/nexe/nexe/releases)) - Examples: ([full list](https://github.com/nexe/nexe/releases))
- `'win32-x86-10.13.0` - `'win32-x86-18.1.0`
- `{ platform: 'alpine' }` - `{ platform: 'alpine' }`
- `darwin-10.13.0` - `darwin-14.19.1`
- `linux-x64` - `linux-x64`
- `macos-10.13.0` - `macos-16.15.0`
See [test/target.spec.ts](test/target.spec.ts) See [test/target.spec.ts](test/target.spec.ts)
- If the [`build`](#build-boolean) flag is set, the platform portion of the target is ignored. - If the [`build`](#build-boolean) flag is set, the platform portion of the target is ignored.
@@ -154,7 +153,7 @@ compile({
- #### `asset: string` - #### `asset: string`
- Provide a pre-built nexe binary asset, this is a file path is resolved relative to cwd. - Provide a pre-built nexe binary asset, this is a file path is resolved relative to cwd.
- #### `python: string` - #### `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 - On Windows this is the directory where `python` can be accessed
- default: `null` - default: `null`
- #### `flags: string[]` - #### `flags: string[]`
@@ -171,9 +170,6 @@ compile({
- #### `vcBuild: string[]` - #### `vcBuild: string[]`
- Options for windows build - Options for windows build
- default: `['nosign', 'release']` - default: `['nosign', 'release']`
- #### `snapshot: string`
- path to a file to be used as the warmup snapshot for the build
- default: `null`
- #### `resources: string[]` - #### `resources: string[]`
- Array of globs with files to include in the build - Array of globs with files to include in the build
- Example: `['./public/**/*']` - Example: `['./public/**/*']`
@@ -182,20 +178,6 @@ compile({
- Path to use for storing nexe's build files - Path to use for storing nexe's build files
- Override in the env with `NEXE_TEMP` - Override in the env with `NEXE_TEMP`
- default: `~/.nexe` - default: `~/.nexe`
- #### `ico: string`
- Path to a user provided icon to be used (Windows only). Requires `--build` to be set.
- #### `rc: object`
- Settings for patching the [node.rc](https://github.com/nodejs/node/blob/master/src/res/node.rc) configuration file (Windows only).
- Example (keys may vary depending on the version. Reference the file linked above):
```javascript
{
CompanyName: "ACME Corp",
PRODUCTVERSION: "17,3,0,0",
FILEVERSION: "1,2,3,4"
...
}
```
- default: `{}`
- #### `clean: boolean` - #### `clean: boolean`
- If included, nexe will remove temporary files for the accompanying configuration and exit - If included, nexe will remove temporary files for the accompanying configuration and exit
- #### `enableNodeCli: boolean` - #### `enableNodeCli: boolean`
@@ -254,14 +236,12 @@ In order to use native modules, the native binaries must be shipped alongside th
## Troubleshooting ## Troubleshooting
`Error: Entry file "" not found!` means you need to provide `nexe` with input. Either use `-i` or pipe data to it.
`Error: https://github.com/nexe/nexe/releases/download/v3.3.3/windows-x64-15.8.0 is not available, create it using the --build flag` or similar message means that it either: `Error: https://github.com/nexe/nexe/releases/download/v3.3.3/windows-x64-15.8.0 is not available, create it using the --build flag` or similar message means that it either:
- You are having networking issues such as the download being blocked - You are having networking issues such as the download being blocked
- You should specify the target so `nexe` knows what version of the executable to use. - You should specify the target so `nexe` knows what version of the executable to use.
- See the [releases page](https://github.com/nexe/nexe/releases) to find the executable's version number - See the [releases page](https://github.com/nexe/nexe/releases) to find the executable's version number
- Example - Example
- `nexe -i "app.js" -r "public/**/*.html" -o "dist/myApp.exe" -t x64-14.15.3` - `nexe -i "app.js" -r "public/**/*.html" -o "dist/myApp.exe" -t x64-18.1.0`
- where `-i` specifies the input, `-r` specifies resources to embed, `-o` specifies the output, `-t` specifies the target. - where `-i` specifies the input, `-r` specifies resources to embed, `-o` specifies the output, `-t` specifies the target.
- Alternatively you can compile the executable yourself, see that section for details - Alternatively you can compile the executable yourself, see that section for details
-69
View File
@@ -1,69 +0,0 @@
schedules:
- cron: "0 8 * * *"
displayName: "Daily Build 1"
always: true
branches:
include:
- master
- cron: "0 13 * * *"
displayName: "Daily Build 2"
always: true
branches:
include:
- master
- cron: "0 19 * * *"
displayName: "Daily Build 3"
always: true
branches:
include:
- master
jobs:
- job: BaseBuild
timeoutInMinutes: 360
strategy:
matrix:
linux:
imageName: 'ubuntu-16.04'
mac:
imageName: 'macos-10.15'
windows_2017:
imageName: 'vs2017-win2016'
windows_2015:
imageName: 'vs2017-win2016'
alpine:
imageName: 'ubuntu-16.04'
pool:
vmImage: $(imageName)
steps:
- task: UsePythonVersion@0
displayName: Setup Python
inputs:
versionSpec: '2.7'
architecture: 'x64'
- task: NodeTool@0
displayName: Install Node
inputs:
versionSpec: '12.x'
- 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
condition: contains(variables['Agent.JobName'], 'windows_2015')
displayName: Install Windows build dependencies
- script: npm install && npm run ci:build
displayName: Build Nexe
env:
GITHUB_TOKEN: $(PersonalGithubToken)
NEXE_VERBOSE: $(NEXE_VERBOSE)
msbuild_args: /3G
condition: not(contains(variables['Agent.JobName'], 'windows'))
- powershell: |
iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
scoop install nasm
npm install
npm run ci:build
displayName: Build Nexe (Windows)
condition: contains(variables['Agent.JobName'], 'windows')
env:
GITHUB_TOKEN: $(PersonalGithubToken)
NEXE_VERBOSE: $(NEXE_VERBOSE)
+13
View File
@@ -0,0 +1,13 @@
FROM centos:7
RUN yum install -y which centos-release-scl \
&& yum install -y devtoolset-9-gcc \
devtoolset-9-gcc-c++ \
make \
python3 \
curl \
&& curl -sL https://rpm.nodesource.com/setup_16.x | bash - \
&& yum install -y nodejs && npm install nexe -g \
&& echo "source /opt/rh/devtoolset-9/enable" >> /etc/bashrc
SHELL ["/bin/bash", "--login", "-c"]
+4
View File
@@ -0,0 +1,4 @@
FROM node:16-alpine
RUN apk add --no-cache build-base linux-headers bash clang python3
SHELL ["/bin/bash", "--login", "-c"]
CMD ["bash"]
-8
View File
@@ -1,8 +0,0 @@
const express = require('express')
const path = require('path')
const app = express()
app.use('/', express.static(path.join(__dirname, 'public')))
app.listen(8888)
-373
View File
@@ -1,373 +0,0 @@
{
"name": "express-app",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
"requires": {
"mime-types": "~2.1.18",
"negotiator": "0.6.1"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"body-parser": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
"integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
"requires": {
"bytes": "3.0.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.1",
"http-errors": "~1.6.2",
"iconv-lite": "0.4.19",
"on-finished": "~2.3.0",
"qs": "6.5.1",
"raw-body": "2.3.2",
"type-is": "~1.6.15"
}
},
"bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
},
"content-disposition": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"express": {
"version": "4.16.3",
"resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz",
"integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=",
"requires": {
"accepts": "~1.3.5",
"array-flatten": "1.1.1",
"body-parser": "1.18.2",
"content-disposition": "0.5.2",
"content-type": "~1.0.4",
"cookie": "0.3.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.1.1",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.2",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.3",
"qs": "6.5.1",
"range-parser": "~1.2.0",
"safe-buffer": "5.1.1",
"send": "0.16.2",
"serve-static": "1.13.2",
"setprototypeof": "1.1.0",
"statuses": "~1.4.0",
"type-is": "~1.6.16",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}
},
"finalhandler": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.2",
"statuses": "~1.4.0",
"unpipe": "~1.0.0"
}
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.0",
"statuses": ">= 1.4.0 < 2"
}
},
"iconv-lite": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ipaddr.js": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
"integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mime": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
},
"mime-db": {
"version": "1.35.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz",
"integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg=="
},
"mime-types": {
"version": "2.1.19",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz",
"integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==",
"requires": {
"mime-db": "~1.35.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"negotiator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"requires": {
"ee-first": "1.1.1"
}
},
"parseurl": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"proxy-addr": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
"integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.8.0"
}
},
"qs": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
},
"range-parser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
},
"raw-body": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
"integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
"requires": {
"bytes": "3.0.0",
"http-errors": "1.6.2",
"iconv-lite": "0.4.19",
"unpipe": "1.0.0"
},
"dependencies": {
"depd": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
"integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
},
"http-errors": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
"integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
"requires": {
"depd": "1.1.1",
"inherits": "2.0.3",
"setprototypeof": "1.0.3",
"statuses": ">= 1.3.1 < 2"
}
},
"setprototypeof": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
"integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
}
}
},
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"send": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.6.2",
"mime": "1.4.1",
"ms": "2.0.0",
"on-finished": "~2.3.0",
"range-parser": "~1.2.0",
"statuses": "~1.4.0"
}
},
"serve-static": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.2",
"send": "0.16.2"
}
},
"setprototypeof": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
},
"statuses": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
},
"type-is": {
"version": "1.6.16",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.18"
}
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}
}
}
-15
View File
@@ -1,15 +0,0 @@
{
"name": "express-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.3"
}
}
-1
View File
@@ -1 +0,0 @@
<h1>Hello World!</h1>
+34 -21
View File
@@ -1,32 +1,45 @@
#!/usr/bin/env node #!/usr/bin/env node
const options = require('./lib/options')
if (require.main === module) { /* eslint-disable no-undef */
//fast path for help/version /* eslint-disable @typescript-eslint/no-var-requires */
const argv = options.argv
const eol = require('os').EOL const eol = require('os').EOL
const showHelp = argv.help || argv._.some(x => x === 'help') async function main() {
const showVersion = argv.version || argv._.some(x => x === 'version') const util = require('./lib/cjs/util.js')
if (showHelp || showVersion) { await util.initEsm()
process.stderr.write(showHelp ? options.help : options.version + eol) const color = util.color,
options = require('./lib/cjs/options.js'),
nexe = require('./lib/cjs/nexe.js'),
NexeError = require('./lib/cjs/compiler.js').NexeError,
del = eol + eol,
argv = options.argv,
showHelp = argv.help || argv._.some((x) => x === 'help'),
showVersion = argv.version || argv._.some((x) => x === 'version')
try {
if (!(showHelp || showVersion)) {
await nexe.compile(argv)
} else { } else {
const nexe = require('./lib/nexe') process.stderr.write(showHelp ? options.help() : options.version + eol)
nexe.compile(argv).catch((error) => { }
const NexeError = require('./lib/compiler').NexeError } catch (error) {
const chalk = require('chalk') if (argv.loglevel !== 'silent') {
const isSilent = Boolean(argv.silent === true || argv.loglevel === 'silent')
if (!isSilent) {
if (error instanceof NexeError) { if (error instanceof NexeError) {
process.stderr.write(eol + chalk.red('Error: ') + error.message + eol process.stderr.write(
+ eol + 'See nexe -h for usage..' + eol + eol `${eol}${color('red', 'Error: ')}${error.message}${del}See nexe -h for usage..${del}`
) )
} else { } else {
process.stderr.write(error.stack + eol) process.stderr.write(error.stack + eol)
} }
} }
process.exit(1) process.exit(1)
})
} }
} else { process.exit(0)
module.exports = require('./lib/nexe') }
if (require.main === module) {
main()
} else {
process.stderr.write('Cannot require(..) the nexe cli')
process.exit(1)
} }
+5197 -3422
View File
File diff suppressed because it is too large Load Diff
+103 -58
View File
@@ -2,78 +2,123 @@
"name": "nexe", "name": "nexe",
"description": "Create a single executable out of your Node.js application", "description": "Create a single executable out of your Node.js application",
"license": "MIT", "license": "MIT",
"version": "4.0.0-beta.19", "type": "commonjs",
"contributors": [ "version": "4.0.0-rc.local",
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)", "bin": {
"Jared Allard <jaredallard@outlook.com>", "nexe": "./index.js"
"Caleb Boyd <caleb.boyd@hotmail.com>" },
], "types": "./lib/types/nexe.d.ts",
"scripts": { "main": "./lib/cjs/nexe.js",
"ci:build": "ts-node tasks/build", "exports": {
"test": "mocha", "./package.json": "./package.json",
"lint": "tslint \"{src,plugins,tasks}/**/*.ts\" --fix", ".": {
"prepare": "npm run lint && npm run build && npm test", "types": "./lib/types/nexe.d.ts",
"prebuild": "rimraf lib", "node": {
"build": "tsc --declaration", "import": "./lib/esm/nexe.js",
"postbuild": "ts-node tasks/post-build" "require": "./lib/cjs/nexe.js"
}
}
},
"engines": {
"node": ">=14"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/nexe/nexe.git" "url": "git://github.com/nexe/nexe.git"
}, },
"files": [ "files": [
"lib" "lib",
"index.js"
], ],
"typings": "lib/nexe.d.ts", "scripts": {
"main": "index.js", "lint": "tsc --noEmit && eslint \"{src,tasks,test}/**/*.ts\" --fix",
"bin": { "test": "vitest run && node test/exports.mjs",
"nexe": "index.js" "prepublishOnly": "npm run lint && npm run build && npm test",
}, "build": "npm run cjs:build & npm run esm:build",
"engines": { "prebuild": "rm -rf lib",
"node": ">=10" "cjs:build": "swc src -d lib/cjs --copy-files",
}, "esm:build": "swc src -d lib/esm --copy-files -C module.type=es6",
"mocha": { "postbuild": "node -p \"JSON.stringify({type:'module'})\" > lib/esm/package.json && tsc --emitDeclarationOnly && ts-node tasks/post-build"
"spec": "./test/**/*.spec.ts",
"checkLeaks": true,
"require": [
"ts-node/register"
]
}, },
"contributors": [
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
"Jared Allard <jaredallard@outlook.com>",
"Caleb Boyd <caleb.boyd@hotmail.com>"
],
"dependencies": { "dependencies": {
"@calebboyd/semaphore": "^1.3.1",
"app-builder": "^7.0.4",
"caw": "^2.0.1", "caw": "^2.0.1",
"chalk": "^2.4.2", "chalk": "^5.2.0",
"cherow": "1.6.9",
"download": "^8.0.0", "download": "^8.0.0",
"globby": "^11.0.2", "fast-glob": "^3.2.12",
"got": "^11.8.2", "got": "^12.5.3",
"minimist": "^1.2.5", "meriyah": "^4.3.3",
"mkdirp": "^1.0.4", "minimist": "^1.2.7",
"multistream": "^4.1.0", "multistream": "^4.1.0",
"ora": "^3.4.0", "ora": "^6.1.2",
"resolve-dependencies": "^6.0.7", "resolve-dependencies": "^6.0.8"
"rimraf": "^3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4", "@swc/cli": "^0.1.57",
"@types/download": "^6", "@swc/core": "^1.3.24",
"@types/globby": "^9",
"@types/minimist": "^1.2.2", "@types/minimist": "^1.2.2",
"@types/mkdirp": "^1.0.2", "@types/multistream": "^4.1.0",
"@types/mocha": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.47.0",
"@types/multistream": "^2.1.1", "@typescript-eslint/parser": "^5.47.0",
"@types/ora": "^3.2.0", "eslint": "^8.30.0",
"@types/rimraf": "3.0.2", "eslint-config-prettier": "~8.5.0",
"@types/semver": "^7.3.8", "eslint-plugin-import": "^2.26.0",
"chai": "^4.3.4", "eslint-plugin-prettier": "^4.2.1",
"execa": "^5.1.1", "prettier": "^2.8.1",
"mocha": "^9.1.2", "ts-node": "^10.9.1",
"prettier": "^2.4.1", "typescript": "^4.9.4",
"ts-node": "^10.2.1", "vitest": "^0.26.2"
"tslint": "^6.1.1", },
"tslint-config-prettier": "^1.18.0", "prettier": {
"tslint-plugin-prettier": "^2.3.0", "parser": "typescript",
"typescript": "^4.4.3" "semi": false,
"singleQuote": true,
"printWidth": 100
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
"plugins": [
"import",
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
"plugin:prettier/recommended"
],
"rules": {
"@typescript-eslint/consistent-type-imports": [
2,
{
"prefer": "type-imports",
"disallowTypeAnnotations": false
}
],
"import/extensions": [
2,
"ignorePackages"
],
"@typescript-eslint/no-unused-vars": [
2,
{
"args": "after-used",
"ignoreRestSiblings": true,
"argsIgnorePattern": "^_"
}
],
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"no-warning-comments": 1,
"one-var": [
"error",
"consecutive"
]
}
} }
} }
+65 -89
View File
@@ -1,28 +1,22 @@
import { delimiter, resolve, normalize, join } from 'path' import { resolve, normalize, join } from 'node:path'
import { Buffer } from 'buffer' import { Buffer } from 'node:buffer'
import { createReadStream, ReadStream } from 'fs' import type { ReadStream } from 'node:fs'
import { spawn } from 'child_process' import { createReadStream } from 'node:fs'
import { Logger, LogStep } from './logger' import { spawn } from 'node:child_process'
import {
readFileAsync,
writeFileAsync,
pathExistsAsync,
statAsync,
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'
const isBsd = Boolean(~process.platform.indexOf('bsd')) import type { File } from 'resolve-dependencies'
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make' import MultiStream from 'multistream'
const configure = isWindows ? 'configure' : './configure'
import type { LogStep } from './logger.js'
import { Logger } from './logger.js'
import { readFile, writeFile, pathExists, isWindows, bound, wrap } from './util.js'
import { version } from './options.js'
import type { NexeOptions } from './options.js'
import type { NexeTarget } from './target.js'
import { Bundle, toStream } from './fs/bundle.js'
const isBsd = Boolean(~process.platform.indexOf('bsd')),
make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
type StringReplacer = (match: string) => string type StringReplacer = (match: string) => string
@@ -45,7 +39,7 @@ export class NexeCompiler {
/** /**
* Epoch of when compilation started * Epoch of when compilation started
*/ */
private start = Date.now() private readonly start = Date.now()
private compileStep: LogStep | undefined private compileStep: LogStep | undefined
public log = new Logger(this.options.loglevel) public log = new Logger(this.options.loglevel)
/** /**
@@ -71,7 +65,7 @@ export class NexeCompiler {
/** /**
* The last shim (defaults to "require('module').runMain()") * The last shim (defaults to "require('module').runMain()")
*/ */
public startup: string = '' public startup = ''
/** /**
* The main entrypoint filename for your application - eg. node mainFile.js * The main entrypoint filename for your application - eg. node mainFile.js
*/ */
@@ -92,10 +86,6 @@ export class NexeCompiler {
* Flag to indicate whether or notstdin was used for input * Flag to indicate whether or notstdin was used for input
*/ */
public stdinUsed = false public stdinUsed = false
/**
* Path to the configure script
*/
public configureScript: string
/** /**
* The file path of node binary * The file path of node binary
*/ */
@@ -107,37 +97,29 @@ export class NexeCompiler {
constructor(public options: NexeOptions) { constructor(public options: NexeOptions) {
const { python } = (this.options = options) const { python } = (this.options = options)
//SOMEDAY iterate over multiple targets with `--outDir` // SOMEDAY iterate over multiple targets with `--outDir`
this.targets = options.targets as NexeTarget[] this.targets = options.targets as NexeTarget[]
this.target = this.targets[0] 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}`) 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 + this.target.toString()
this.src = join(this.options.temp, this.target.version) this.src = join(this.options.temp, this.target.version)
this.configureScript = configure + (semverGt(this.target.version, '10.10.0') ? '.py' : '')
this.nodeSrcBinPath = isWindows this.nodeSrcBinPath = isWindows
? join(this.src, 'Release', 'node.exe') ? join(this.src, 'Release', 'node.exe')
: join(this.src, 'out', 'Release', 'node') : join(this.src, 'out', 'Release', 'node')
this.log.step('nexe ' + version, 'info') this.log.step('nexe ' + version, 'info')
this.bundle = new Bundle(options) this.bundle = new Bundle(options)
if (isWindows) {
const originalPath = process.env.PATH
delete process.env.PATH
this.env = { ...process.env } this.env = { ...process.env }
this.env.PATH = python if (python) {
? (this.env.PATH = dequote(normalize(python)) + delimiter + originalPath) this.env.PYTHON = python
: originalPath
process.env.PATH = originalPath
} else {
this.env = { ...process.env }
python && (this.env.PYTHON = python)
} }
} }
@bound @bound
addResource(absoluteFileName: string, content?: Buffer | string | File) { async addResource(absoluteFileName: string, content?: Buffer | string | File) {
return this.bundle.addResource(absoluteFileName, content) return await this.bundle.addResource(absoluteFileName, content)
} }
@bound @bound
@@ -149,7 +131,7 @@ export class NexeCompiler {
cachedFile = { cachedFile = {
absPath, absPath,
filename: file, filename: file,
contents: await readFileAsync(absPath, 'utf-8').catch((x) => { contents: await readFile(absPath, 'utf-8').catch((x) => {
if (x.code === 'ENOENT') return '' if (x.code === 'ENOENT') return ''
throw x throw x
}), }),
@@ -160,15 +142,26 @@ export class NexeCompiler {
} }
@bound @bound
writeFileAsync(file: string, contents: string | Buffer) { async writeFileAsync(file: string, contents: string | Buffer) {
this.assertBuild() this.assertBuild()
return writeFileAsync(join(this.src, file), contents) return await writeFile(join(this.src, file), contents)
} }
@bound @bound
async replaceInFileAsync(file: string, replace: string | RegExp, value: string | StringReplacer) { async replaceInFileAsync(
file: string,
replace: string | RegExp,
value: string | StringReplacer
): Promise<boolean> {
const entry = await this.readFileAsync(file) const entry = await this.readFileAsync(file)
entry.contents = entry.contents.toString().replace(replace, value as any) let replaced = false
const replacer = (match: string) => {
replaced = true
if (typeof value === 'function') return value(match)
return value
}
entry.contents = entry.contents.toString().replace(replace, replacer)
return replaced
} }
@bound @bound
@@ -177,10 +170,10 @@ export class NexeCompiler {
entry.contents = contents entry.contents = contents
} }
quit(error?: any) { async quit(error?: any) {
const time = Date.now() - this.start const time = Date.now() - this.start
this.log.write(`Finished in ${time / 1000}s`, error ? 'red' : 'green') this.log.write(`Finished in ${time / 1000}s`, error ? 'red' : 'green')
return this.log.flush() return await this.log.flush()
} }
assertBuild() { assertBuild() {
@@ -199,11 +192,11 @@ export class NexeCompiler {
return this.nodeSrcBinPath return this.nodeSrcBinPath
} }
private _runBuildCommandAsync(command: string, args: string[]) { private async _runBuildCommandAsync(command: string, args: string[]) {
if (this.log.verbose) { if (this.log.verbose) {
this.compileStep!.pause() this.compileStep?.pause()
} }
return new Promise<void>((resolve, reject) => { return await new Promise<void>((resolve, reject) => {
spawn(command, args, { spawn(command, args, {
cwd: this.src, cwd: this.src,
env: this.env, env: this.env,
@@ -211,13 +204,13 @@ export class NexeCompiler {
}) })
.once('error', (e: Error) => { .once('error', (e: Error) => {
if (this.log.verbose) { 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) { if (this.log.verbose) {
this.compileStep!.resume() this.compileStep?.resume()
} }
if (code != 0) { if (code != 0) {
const error = `${command} ${args.join(' ')} exited with code: ${code}` const error = `${command} ${args.join(' ')} exited with code: ${code}`
@@ -228,59 +221,42 @@ export class NexeCompiler {
}) })
} }
private _configureAsync() { private async _configureAsync() {
if (isWindows && semverGt(this.target.version, '10.15.99')) { if (isWindows) {
return Promise.resolve() return await Promise.resolve()
} }
return this._runBuildCommandAsync(this.env.PYTHON || 'python', [ return await this._runBuildCommandAsync(this.env.PYTHON || 'python3', [
this.configureScript, './configure.py',
...this.options.configure, ...this.options.configure,
]) ])
} }
public async build(): Promise<ReadStream> { public async build(): Promise<ReadStream> {
this.compileStep!.log( this.compileStep?.log(
`Configuring node build${ `Configuring node build${
this.options.configure.length ? ': ' + this.options.configure : '...' this.options.configure.length > 0 ? ': ' + this.options.configure : '...'
}` }`
) )
await this._configureAsync() await this._configureAsync()
const buildOptions = this.options.make const buildOptions = this.options.make
this.compileStep!.log( this.compileStep?.log(
`Compiling Node${buildOptions.length ? ' with arguments: ' + buildOptions : '...'}` `Compiling Node${buildOptions.length > 0 ? ' with arguments: ' + buildOptions : '...'}`
) )
await this._runBuildCommandAsync(make, buildOptions) await this._runBuildCommandAsync(make, buildOptions)
return createReadStream(this.getNodeExecutableLocation()) return createReadStream(this.getNodeExecutableLocation())
} }
private async _shouldCompileBinaryAsync(binary: NodeJS.ReadableStream | null, location: string) { async compileAsync(target: NexeTarget) {
//SOMEDAY combine make/configure/vcBuild/and modified times of included files const step = (this.compileStep = this.log.step('Compiling result')),
const { snapshot, build } = this.options build = this.options.build,
location = this.getNodeExecutableLocation(build ? undefined : target)
let binary = (await pathExists(location)) ? createReadStream(location) : null
if (!binary) { if (!binary) {
return true
}
if (build && snapshot != null && (await pathExistsAsync(snapshot))) {
const snapshotLastModified = (await statAsync(snapshot)).mtimeMs
const binaryLastModified = (await statAsync(location)).mtimeMs
return snapshotLastModified > binaryLastModified
}
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
if (await this._shouldCompileBinaryAsync(binary, location)) {
binary = await this.build() binary = await this.build()
step.log('Node binary compiled') step.log('Node binary compiled')
} }
return this._assembleDeliverable(binary!) return await this._assembleDeliverable(binary)
} }
code() { code() {
+14 -13
View File
@@ -1,20 +1,21 @@
if (true) { if (true) {
const __nexe_patches = (process.nexe = { patches: {} }).patches const patches = (process.nexe = { patches: {} }).patches
const slice = [].slice
const __nexe_noop_patch = function (original) { ;["ReadFile", "ReadJSON", "Stat"].forEach((x) => {
const args = slice.call(arguments, 1) patch(process.binding("fs"), "internalModule" + x, noop)
})
function noop(original, ...args) {
return original.apply(this, args) return original.apply(this, args)
} }
const __nexe_patch = function (obj, method, patch) {
const original = obj[method] function patch(obj, method, patch) {
const original = obj[method];
if (!original) return if (!original) return
__nexe_patches[method] = patch patches[method] = patch
obj[method] = function() { obj[method] = function (...args) {
const args = [original].concat(slice.call(arguments)) args.unshift(original)
return __nexe_patches[method].apply(this, args) return 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)
} }
+11 -16
View File
@@ -1,17 +1,12 @@
import * as fs from 'fs' import { lstat, realpath, stat } from 'node:fs/promises'
import { promisify } from 'util' import { createReadStream } from 'node:fs'
import { relative } from 'path' import { relative } from 'node:path'
import { Readable } from 'stream' import { Readable } from 'node:stream'
import { argv } from '../options'
import { File } from 'resolve-dependencies'
import MultiStream = require('multistream')
const lstat = promisify(fs.lstat), import type { File } from 'resolve-dependencies'
realpath = promisify(fs.realpath), import MultiStream from 'multistream'
createReadStream = fs.createReadStream,
stat = promisify(fs.stat)
export type MultiStreams = (Readable | (() => Readable))[] export type MultiStreams = Array<Readable | (() => Readable)>
function makeRelative(cwd: string, path: string) { function makeRelative(cwd: string, path: string) {
return './' + relative(cwd, path) return './' + relative(cwd, path)
@@ -79,7 +74,7 @@ export class Bundle {
this.files[absoluteFileName] = { this.files[absoluteFileName] = {
size: Buffer.byteLength(content), size: Buffer.byteLength(content),
moduleType: 'commonjs', moduleType: 'commonjs',
contents: content as any, //todo type is wrong here... should allow buffer contents: content.toString(),
deps: {}, deps: {},
absPath: absoluteFileName, absPath: absoluteFileName,
} }
@@ -108,8 +103,8 @@ export class Bundle {
throw new Error('Bundle index already rendered') throw new Error('Bundle index already rendered')
} }
const files = Object.entries(this.files), const files = Object.entries(this.files),
realFiles: [string, File][] = [], realFiles: Array<[string, File]> = [],
symLinks: [string, File][] = [] symLinks: Array<[string, File]> = []
for (const entry of files) { for (const entry of files) {
if (entry[1].realPath) { if (entry[1].realPath) {
symLinks.push(entry) symLinks.push(entry)
@@ -154,6 +149,6 @@ export class Bundle {
} }
toStream() { toStream() {
return new (MultiStream as any)(this.streams) return new MultiStream(this.streams)
} }
} }
-15
View File
@@ -1,15 +0,0 @@
{
"name": "nexe-fs",
"version": "1.0.1",
"description": "Virtual File System used by nexe",
"main": "bundle.js",
"files": [
"*.d.ts",
"*.js"
],
"author": "Caleb Boyd",
"dependencies": {
"multistream": "^2.1.1"
},
"license": "MIT"
}
+54 -53
View File
@@ -1,5 +1,7 @@
import { Stats } from 'fs' /* eslint-disable prefer-rest-params */
import { getLatestGitRelease } from '../releases' /* eslint-disable @typescript-eslint/no-var-requires */
import type { Stats } from 'fs'
import type { Func } from '../util'
export interface NexeBinary { export interface NexeBinary {
blobPath: string blobPath: string
@@ -13,13 +15,12 @@ export interface NexeBinary {
} }
} }
let originalFsMethods: any = null let originalFsMethods: any = null,
let lazyRestoreFs = () => {} lazyRestoreFs = () => void 0
// optional Win32 file namespace prefix followed by drive letter and colon // optional Win32 file namespace prefix followed by drive letter and colon
const windowsFullPathRegex = /^(\\{2}\?\\)?([a-zA-Z]):/ const windowsFullPathRegex = /^(\\{2}\?\\)?([a-zA-Z]):/,
upcaseDriveLetter = (s: string): string =>
const upcaseDriveLetter = (s: string): string =>
s.replace(windowsFullPathRegex, (_match, ns, drive) => `${ns || ''}${drive.toUpperCase()}:`) s.replace(windowsFullPathRegex, (_match, ns, drive) => `${ns || ''}${drive.toUpperCase()}:`)
function shimFs(binary: NexeBinary, fs: any = require('fs')) { function shimFs(binary: NexeBinary, fs: any = require('fs')) {
@@ -33,13 +34,13 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
notAFile = '!@#$%^&*', notAFile = '!@#$%^&*',
isWin = process.platform.startsWith('win'), isWin = process.platform.startsWith('win'),
isString = (x: any): x is string => typeof x === 'string' || x instanceof String, isString = (x: any): x is string => typeof x === 'string' || x instanceof String,
noop = () => {}, noop = () => void 0
path = require('path'), const path = require('path')
winPath: (key: string) => string = isWin ? upcaseDriveLetter : (s) => s, const winPath: (key: string) => string = isWin ? upcaseDriveLetter : (s) => s,
baseDir = winPath(path.dirname(process.execPath)) baseDir = winPath(path.dirname(process.execPath))
let log = (_: string) => true let log = (_: string) => true,
let loggedManifest = false loggedManifest = false
if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) { if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {
log = (text: string) => { log = (text: string) => {
setupManifest() setupManifest()
@@ -59,12 +60,11 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
if (!isString(filepath)) { if (!isString(filepath)) {
return notAFile return notAFile
} }
let key = path.resolve(baseDir, filepath) const key = path.resolve(baseDir, filepath)
return winPath(key) return winPath(key)
} },
statTime = function () {
const statTime = function () {
return { return {
atime: new Date(stat.atime), atime: new Date(stat.atime),
mtime: new Date(stat.mtime), mtime: new Date(stat.mtime),
@@ -73,16 +73,18 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
} }
} }
let BigInt: Function let BigInt: any
try { try {
BigInt = eval('BigInt') BigInt = eval('BigInt')
} catch (ignored) {} } catch (ignored) {
void ignored
}
const minBlocks = Math.max(Math.ceil(stat.blksize / 512), 1) const minBlocks = Math.max(Math.ceil(stat.blksize / 512), 1),
const createStat = function (extensions: any, options: any) { createStat = function (extensions: any, options: any) {
const stat = Object.assign(new fs.Stats(), binary.layout.stat, statTime(), extensions) const stat = Object.assign(new fs.Stats(), binary.layout.stat, statTime(), extensions)
if ('size' in extensions) { if ('size' in extensions) {
//Assume non adjustable allocation size for file system // Assume non adjustable allocation size for file system
stat.blocks = Math.ceil(stat.size / stat.blksize) * minBlocks stat.blocks = Math.ceil(stat.size / stat.blksize) * minBlocks
} }
if (options && options.bigint && typeof BigInt !== 'undefined') { if (options && options.bigint && typeof BigInt !== 'undefined') {
@@ -93,9 +95,8 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
} }
} }
return stat return stat
} },
ownStat = function (filepath: any, options: any) {
const ownStat = function (filepath: any, options: any) {
setupManifest() setupManifest()
const key = getKey(filepath) const key = getKey(filepath)
if (directories[key]) { if (directories[key]) {
@@ -107,9 +108,8 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
if (manifest[key]) { if (manifest[key]) {
return createStat({ size: manifest[key][1] }, options) return createStat({ size: manifest[key][1] }, options)
} }
} },
getStat = function (fn: string) {
const getStat = function (fn: string) {
return function stat(filepath: string | Buffer, options: any, callback: any) { return function stat(filepath: string | Buffer, options: any, callback: any) {
let stat: any let stat: any
if (typeof options === 'function') { if (typeof options === 'function') {
@@ -129,7 +129,7 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
} }
function makeLong(filepath: string) { function makeLong(filepath: string) {
return (path as any)._makeLong && (path as any)._makeLong(filepath) return path._makeLong && path._makeLong(filepath)
} }
function fileOpts(options: any) { function fileOpts(options: any) {
@@ -138,10 +138,10 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
let setupManifest = () => { let setupManifest = () => {
Object.keys(manifest).forEach((filepath) => { Object.keys(manifest).forEach((filepath) => {
const entry = manifest[filepath] const entry = manifest[filepath],
const absolutePath = getKey(filepath) absolutePath = getKey(filepath),
const longPath = makeLong(absolutePath) longPath = makeLong(absolutePath),
const normalizedPath = winPath(path.normalize(filepath)) normalizedPath = winPath(path.normalize(filepath))
if (!manifest[absolutePath]) { if (!manifest[absolutePath]) {
manifest[absolutePath] = entry manifest[absolutePath] = entry
@@ -153,8 +153,8 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
manifest[normalizedPath] = manifest[filepath] manifest[normalizedPath] = manifest[filepath]
} }
let currentDir = path.dirname(absolutePath) let currentDir = path.dirname(absolutePath),
let prevDir = absolutePath prevDir = absolutePath
while (currentDir !== prevDir) { while (currentDir !== prevDir) {
directories[currentDir] = directories[currentDir] || {} directories[currentDir] = directories[currentDir] || {}
@@ -172,7 +172,7 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
setupManifest = noop setupManifest = noop
} }
//naive patches intended to work for most use cases // naive patches intended to work for most use cases
const nfs: any = { const nfs: any = {
existsSync: function existsSync(filepath: string) { existsSync: function existsSync(filepath: string) {
setupManifest() setupManifest()
@@ -202,7 +202,7 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
setupManifest() setupManifest()
const dir = directories[getKey(filepath)] const dir = directories[getKey(filepath)]
if (dir) { if (dir) {
if ('function' === typeof options) { if (typeof options === 'function') {
callback = options callback = options
options = { encoding: 'utf8' } options = { encoding: 'utf8' }
} }
@@ -211,7 +211,7 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
return originalFsMethods.readdir.apply(fs, arguments) return originalFsMethods.readdir.apply(fs, arguments)
} }
}, },
readdirSync: function readdirSync(filepath: string | Buffer, options: any) { readdirSync: function readdirSync(filepath: string | Buffer) {
setupManifest() setupManifest()
const dir = directories[getKey(filepath)] const dir = directories[getKey(filepath)]
if (dir) { if (dir) {
@@ -226,9 +226,9 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
if (!entry) { if (!entry) {
return originalFsMethods.readFile.apply(fs, arguments) return originalFsMethods.readFile.apply(fs, arguments)
} }
const [offset, length] = entry const [offset, length] = entry,
const resourceOffset = resourceStart + offset resourceOffset = resourceStart + offset,
const encoding = fileOpts(options).encoding encoding = fileOpts(options).encoding
callback = typeof options === 'function' ? options : callback callback = typeof options === 'function' ? options : callback
originalFsMethods.open(blobPath, 'r', function (err: Error, fd: number) { originalFsMethods.open(blobPath, 'r', function (err: Error, fd: number) {
@@ -261,9 +261,9 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
if (!entry) { if (!entry) {
return originalFsMethods.createReadStream.apply(fs, arguments) return originalFsMethods.createReadStream.apply(fs, arguments)
} }
const [offset, length] = entry const [offset, length] = entry,
const resourceOffset = resourceStart + offset resourceOffset = resourceStart + offset,
const opts = fileOpts(options) opts = fileOpts(options)
return originalFsMethods.createReadStream( return originalFsMethods.createReadStream(
blobPath, blobPath,
@@ -279,11 +279,11 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
if (!entry) { if (!entry) {
return originalFsMethods.readFileSync.apply(fs, arguments) return originalFsMethods.readFileSync.apply(fs, arguments)
} }
const [offset, length] = entry const [offset, length] = entry,
const resourceOffset = resourceStart + offset resourceOffset = resourceStart + offset,
const encoding = fileOpts(options).encoding encoding = fileOpts(options).encoding,
const fd = originalFsMethods.openSync(process.execPath, 'r') fd = originalFsMethods.openSync(process.execPath, 'r'),
const result = Buffer.alloc(length) result = Buffer.alloc(length)
originalFsMethods.readSync(fd, result, 0, length, resourceOffset) originalFsMethods.readSync(fd, result, 0, length, resourceOffset)
originalFsMethods.closeSync(fd) originalFsMethods.closeSync(fd)
return encoding ? result.toString(encoding) : result return encoding ? result.toString(encoding) : result
@@ -306,7 +306,7 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
}, },
} }
if (typeof fs.exists === 'function') { if (typeof fs.exists === 'function') {
nfs.exists = function (filepath: string, cb: Function) { nfs.exists = function (filepath: string, cb: Func) {
cb = cb || noop cb = cb || noop
const exists = nfs.existsSync(filepath) const exists = nfs.existsSync(filepath)
process.nextTick(() => cb(exists)) process.nextTick(() => cb(exists))
@@ -327,7 +327,7 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
} }
let returningArray: boolean let returningArray: boolean
patches.internalModuleReadJSON = function (this: any, original: any, ...args: any[]) { patches.internalModuleReadJSON = function (this: any, original: any, ...args: any[]) {
if (returningArray == null) returningArray = Array.isArray(original.call(this, '')) if (!returningArray) returningArray = Array.isArray(original.call(this, ''))
const res = patches.internalModuleReadFile.call(this, original, ...args) const res = patches.internalModuleReadFile.call(this, original, ...args)
return returningArray && !Array.isArray(res) return returningArray && !Array.isArray(res)
? [res, /"(main|name|type|exports|imports)"/.test(res)] ? [res, /"(main|name|type|exports|imports)"/.test(res)]
@@ -356,7 +356,7 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
} }
if (typeof fs.exists === 'function') { if (typeof fs.exists === 'function') {
nfs.exists = function (filepath: string, cb: Function) { nfs.exists = function (filepath: string, cb: Func) {
cb = cb || noop cb = cb || noop
const exists = nfs.existsSync(filepath) const exists = nfs.existsSync(filepath)
if (!exists) { if (!exists) {
@@ -367,7 +367,7 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
} }
if (typeof fs.copyFile === 'function') { if (typeof fs.copyFile === 'function') {
nfs.copyFile = function (filepath: string, dest: string, flags: number, callback: Function) { nfs.copyFile = function (filepath: string, dest: string, flags: number, callback: Func) {
setupManifest() setupManifest()
const entry = manifest[getKey(filepath)] const entry = manifest[getKey(filepath)]
if (!entry) { if (!entry) {
@@ -424,7 +424,8 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
Object.keys(nfs).forEach((key) => { Object.keys(nfs).forEach((key) => {
fs[key] = originalFsMethods[key] fs[key] = originalFsMethods[key]
}) })
lazyRestoreFs = () => {} lazyRestoreFs = () => void 0
return void 0
} }
return true return true
} }
+17 -15
View File
@@ -1,45 +1,45 @@
import colors from 'chalk' import type { Func } from './util.js'
import ora from 'ora' import { color, esm } from './util.js'
const frameLength = 120 const frameLength = 120
export interface LogStep { export interface LogStep {
modify(text: string, color?: string): void modify: (text: string, color?: string) => void
log(text: string, color?: string): void log: (text: string, color?: string) => void
pause(): void pause: () => void
resume(): void resume: () => void
} }
export class Logger { export class Logger {
public verbose: boolean public verbose: boolean
private silent: boolean private readonly silent: boolean
private ora: any private ora: any
private modify: Function private readonly modify: Func
public write: (text: string, color?: string) => void public write: (text: string, color?: string) => void
constructor(level: 'verbose' | 'silent' | 'info') { constructor(level: 'verbose' | 'silent' | 'info') {
this.verbose = level === 'verbose' this.verbose = level === 'verbose'
this.silent = level === 'silent' this.silent = level === 'silent'
if (!this.silent) { if (!this.silent) {
this.ora = ora({ this.ora = esm.ora?.default({
text: 'Starting...', text: 'Starting...',
color: 'blue', color: 'blue',
spinner: 'dots', spinner: 'dots',
}) })
this.ora.stop() this.ora.stop()
} }
const noop = () => {} const noop = () => void 0
this.modify = this.silent ? noop : this._modify.bind(this) this.modify = this.silent ? noop : this._modify.bind(this)
this.write = this.silent ? noop : this._write.bind(this) this.write = this.silent ? noop : this._write.bind(this)
} }
flush() { async flush() {
!this.silent && this.ora.succeed() !this.silent && this.ora.succeed()
return new Promise((resolve) => setTimeout(resolve, frameLength)) return await new Promise((resolve) => setTimeout(resolve, frameLength))
} }
_write(update: string, color = 'green') { _write(update: string, clr = 'green') {
this.ora.succeed().text = (colors as any)[color](update) this.ora.succeed().text = color(clr, update)
this.ora.start() this.ora.start()
} }
@@ -48,10 +48,12 @@ export class Logger {
this.ora.color = color this.ora.color = color
} }
step(text: string, method: string = 'succeed'): LogStep { step(text: string, method = 'succeed', clr?: string): LogStep {
if (this.silent) { if (this.silent) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
return { modify() {}, log() {}, pause() {}, resume() {} } return { modify() {}, log() {}, pause() {}, resume() {} }
} }
text = color(clr, text)
if (!this.ora.id) { if (!this.ora.id) {
this.ora.start().text = text this.ora.start().text = text
if (method !== 'succeed') { if (method !== 'succeed') {
+31 -25
View File
@@ -1,20 +1,22 @@
import { EOL } from 'os' import { NexeCompiler } from './compiler.js'
import { compose } from 'app-builder' import type { NexeOptions, NexePatch } from './options.js'
import { NexeCompiler, NexeError } from './compiler' import { normalizeOptions } from './options.js'
import { normalizeOptions, NexeOptions, NexePatch } from './options'
import resource from './steps/resource' import resource from './steps/resource.js'
import clean from './steps/clean' import clean from './steps/clean.js'
import cli from './steps/cli' import cli from './steps/cli.js'
import bundle from './steps/bundle' import bundle from './steps/bundle.js'
import download from './steps/download' import download from './steps/download.js'
import shim from './steps/shim' import shim from './steps/shim.js'
import artifacts from './steps/artifacts' import artifacts from './steps/artifacts.js'
import patches from './patches' import patches from './patches/index.js'
import { initEsm } from './util.js'
async function compile( async function compile(
compilerOptions?: Partial<NexeOptions>, compilerOptions?: Partial<NexeOptions>,
callback?: (err: Error | null) => void callback?: (err: Error | null) => void
) { ) {
await initEsm()
let error: Error | null = null, let error: Error | null = null,
options: NexeOptions | null = null, options: NexeOptions | null = null,
compiler: NexeCompiler | null = null compiler: NexeCompiler | null = null
@@ -22,28 +24,32 @@ async function compile(
try { try {
options = normalizeOptions(compilerOptions) options = normalizeOptions(compilerOptions)
compiler = new NexeCompiler(options) compiler = new NexeCompiler(options)
await compose( const plugins = options.plugins as NexePatch[],
clean, buildSteps = options.build ? [artifacts, ...patches, ...(options.patches as NexePatch[])] : []
resource, await exec([clean, resource, cli, bundle, shim, download, ...buildSteps, ...plugins], compiler)
cli,
bundle,
shim,
download,
options.build ? [artifacts, ...patches, ...(options.patches as NexePatch[])] : [],
options.plugins as NexePatch[]
)(compiler)
} catch (e: any) { } catch (e: any) {
error = e error = e
} }
if (error) { if (error) {
compiler && compiler.quit(error) compiler?.quit(error)
if (callback) return callback(error) if (callback) return callback(error)
return Promise.reject(error) throw error
} }
if (callback) callback(null) if (callback) callback(null)
} }
export { compile, NexeCompiler } export { compile, NexeCompiler }
export { argv, version, NexeOptions, help } from './options' export { argv, version, help } from './options.js'
export type { NexeOptions } from './options.js'
function exec<T>(mw: any[], ctx: T): Promise<void> | void {
let i = -1
const nxt = (): void | Promise<void> => {
if (++i < mw.length) {
return mw[i](ctx, nxt)
}
}
return nxt()
}
+57 -68
View File
@@ -1,25 +1,32 @@
import * as parseArgv from 'minimist' import { basename, extname, join, isAbsolute, resolve, dirname } from 'node:path'
import { NexeCompiler, NexeError } from './compiler' import { fileURLToPath } from 'node:url'
import { isWindows, STDIN_FLAG } from './util' import { EOL, homedir } from 'node:os'
import { basename, extname, join, isAbsolute, resolve } from 'path' import { globalAgent } from 'node:https'
import { getTarget, NexeTarget } from './target' import { createRequire } from 'node:module'
import { EOL, homedir } from 'os'
import chalk from 'chalk' const dir = typeof __dirname === 'string' ? __dirname : dirname(fileURLToPath(import.meta.url)),
cjs = createRequire(dir)
import { resolveSync } from 'resolve-dependencies' import { resolveSync } from 'resolve-dependencies'
const caw = require('caw') import parseArgv from 'minimist'
const c = process.platform === 'win32' ? chalk.constructor({ enabled: false }) : chalk
import caw from 'caw'
import type { NexeCompiler } from './compiler.js'
import { NexeError } from './compiler.js'
import type { NexeTarget } from './target.js'
import { getTarget } from './target.js'
import { isWindows, STDIN_FLAG, esm } from './util.js'
export const version = '{{replace:0}}' export const version = '{{replace:0}}'
export interface NexePatch { export type NexePatch = (compiler: NexeCompiler, next: () => Promise<void>) => Promise<void>
(compiler: NexeCompiler, next: () => Promise<void>): Promise<void>
}
export interface NexeOptions { export interface NexeOptions {
build: boolean build: boolean
input: string input: string
output: string output: string
targets: (string | NexeTarget)[] targets: Array<string | NexeTarget>
name: string name: string
remote: string remote: string
asset: string asset: string
@@ -29,14 +36,12 @@ export interface NexeOptions {
configure: string[] configure: string[]
vcBuild: string[] vcBuild: string[]
make: string[] make: string[]
snapshot?: string
resources: string[] resources: string[]
temp: string temp: string
rc: { [key: string]: string }
enableNodeCli: boolean enableNodeCli: boolean
bundle: boolean | string bundle: boolean | string
patches: (string | NexePatch)[] patches: Array<string | NexePatch>
plugins: (string | NexePatch)[] plugins: Array<string | NexePatch>
native: any native: any
mangle: boolean mangle: boolean
ghToken: string ghToken: string
@@ -48,7 +53,6 @@ export interface NexeOptions {
fakeArgv?: boolean fakeArgv?: boolean
verbose?: boolean verbose?: boolean
info?: boolean info?: boolean
ico?: string
debugBundle?: boolean debugBundle?: boolean
warmup?: string warmup?: string
clean?: boolean clean?: boolean
@@ -73,8 +77,8 @@ const defaults = {
patches: [], patches: [],
plugins: [], plugins: [],
remote: 'https://github.com/nexe/nexe/releases/download/v3.3.3/', remote: 'https://github.com/nexe/nexe/releases/download/v3.3.3/',
} },
const alias = { alias = {
i: 'input', i: 'input',
o: 'output', o: 'output',
v: 'version', v: 'version',
@@ -91,34 +95,35 @@ const alias = {
l: 'loglevel', l: 'loglevel',
'fake-argv': 'fakeArgv', 'fake-argv': 'fakeArgv',
'gh-token': 'ghToken', 'gh-token': 'ghToken',
} },
const argv = parseArgv(process.argv, { alias, default: { ...defaults } }) argv = parseArgv(process.argv, { alias, default: { ...defaults } }),
let help = ` help = () => {
const c = esm.chalk?.default as typeof import('chalk').default
return (
EOL +
`
${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 -i --input -- application entry point
-o --output -- path to output file -o --output -- path to output file
-t --target -- node version description -t --target -- * node version description
-n --name -- main app module name -n --name -- main app module name
-r --resource -- *embed files (glob) within the binary -r --resource -- * embed additional files (glob) within the binary
--remote -- alternate location (URL) to download pre-built base (nexe) binaries from --remote -- alternate root directory for pre-built base (nexe) binaries from
--plugin -- extend nexe runtime behavior --plugin -- extend nexe runtime behavior
${c.underline.bold('Building from source:')} ${c.underline.bold('Building from source:')}
-b --build -- build from source -b --build -- build from source
-p --python -- python2 (as python) executable path -p --python -- python3 executable path
-f --flag -- *v8 flags to include during compilation -f --flag -- * v8 flags to include during compilation
-c --configure -- *arguments to the configure step -c --configure -- * arguments to the configure step
-m --make -- *arguments to the make/build step -m --make -- * arguments to the make/build step
--patch -- module with middleware default export for adding a build patch --patch -- * extend patches applied to source
--no-mangle -- used when generating base binaries, or when patching _third_party_main manually --no-mangle -- used when generating base binaries, or when patching _third_party_main manually
--snapshot -- path to a warmup snapshot --sourceUrl -- pass an alternate source tarball (node.tar.gz) url
--ico -- file name for alternate icon file (windows)
--rc-* -- populate rc file options (windows)
--sourceUrl -- pass an alternate source (node.tar.gz) url
--enableNodeCli -- enable node cli enforcement (blocks app cli) --enableNodeCli -- enable node cli enforcement (blocks app cli)
${c.underline.bold('Other options:')} ${c.underline.bold('Other options:')}
@@ -131,30 +136,15 @@ ${c.bold('nexe <entry-file> [options]')}
--silent -- disable logging --silent -- disable logging
--verbose -- set logging to verbose --verbose -- set logging to verbose
-* variable key name * option can be used more than once`.trim() "*" indicates an option can be used more than once`.trim() +
help = EOL + help + EOL EOL
)
}
function flatten(...args: any[]): string[] { function flatten(...args: any[]): string[] {
return ([] as string[]).concat(...args).filter((x) => x) return ([] as string[]).concat(...args).filter((x) => x)
} }
/**
* Extract keys such as { "rc-CompanyName": "Node.js" } to
* { CompanyName: "Node.js" }
* @param {*} match
* @param {*} options
*/
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)
}
function extractLogLevel(options: NexeOptions) { function extractLogLevel(options: NexeOptions) {
if (options.loglevel) return options.loglevel if (options.loglevel) return options.loglevel
if (options.silent) return 'silent' if (options.silent) return 'silent'
@@ -168,11 +158,11 @@ function isName(name: string) {
function extractName(options: NexeOptions) { 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 // try and use the input filename as the output filename if its not index
if (!isName(name) && typeof options.input === 'string') { if (!isName(name) && typeof options.input === 'string') {
name = basename(options.input).replace(extname(options.input), '') name = basename(options.input).replace(extname(options.input), '')
} }
//try and use the directory as the filename // try and use the directory as the filename
if (!isName(name) && basename(options.cwd)) { if (!isName(name) && basename(options.cwd)) {
name = basename(options.cwd) name = basename(options.cwd)
} }
@@ -213,14 +203,14 @@ export function resolveEntry(
const inputPath = padRelative(maybeEntry) const inputPath = padRelative(maybeEntry)
result = resolveSync(cwd, inputPath) result = resolveSync(cwd, inputPath)
} }
if (!process.stdin.isTTY && (!result || !result.absPath) && bundle === defaults.bundle) { if (!process.stdin.isTTY && !result?.absPath && bundle === defaults.bundle) {
return STDIN_FLAG return STDIN_FLAG
} }
if (!result || !result.absPath) { if (!result?.absPath) {
result = resolveSync(cwd, '.') result = resolveSync(cwd, '.')
} }
if (!result.absPath) { if (!result.absPath) {
throw new NexeError(`Entry file "${input || ''}" not found!`) throw new NexeError(`Entry file "${input || maybeEntry}" not found!`)
} }
return result.absPath return result.absPath
} }
@@ -230,9 +220,9 @@ function isCli(options?: Partial<NexeOptions>) {
} }
function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions { function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
const options = Object.assign({}, defaults, input) as NexeOptions const options = Object.assign({}, defaults, input) as NexeOptions,
const opts = options as any opts = options as any,
const cwd = (options.cwd = resolve(options.cwd)) cwd = (options.cwd = resolve(options.cwd))
options.temp = options.temp options.temp = options.temp
? resolve(cwd, options.temp) ? resolve(cwd, options.temp)
: process.env.NEXE_TEMP || join(homedir(), '.nexe') : process.env.NEXE_TEMP || join(homedir(), '.nexe')
@@ -243,7 +233,7 @@ function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
options.loglevel = extractLogLevel(options) options.loglevel = extractLogLevel(options)
options.flags = flatten(opts.flag, options.flags) options.flags = flatten(opts.flag, options.flags)
options.targets = flatten(opts.target, options.targets).map(getTarget) options.targets = flatten(opts.target, options.targets).map(getTarget)
if (!options.targets.length) { if (options.targets.length === 0) {
options.targets.push(getTarget()) options.targets.push(getTarget())
} }
options.ghToken = options.ghToken || process.env.GITHUB_TOKEN || '' options.ghToken = options.ghToken || process.env.GITHUB_TOKEN || ''
@@ -260,10 +250,9 @@ function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
options.downloadOptions.headers['User-Agent'] = 'nexe (https://www.npmjs.com/package/nexe)' options.downloadOptions.headers['User-Agent'] = 'nexe (https://www.npmjs.com/package/nexe)'
options.downloadOptions.agent = process.env.HTTPS_PROXY options.downloadOptions.agent = process.env.HTTPS_PROXY
? caw(process.env.HTTPS_PROXY, { protocol: 'https' }) ? caw(process.env.HTTPS_PROXY, { protocol: 'https' })
: options.downloadOptions.agent || require('https').globalAgent : options.downloadOptions.agent || globalAgent
options.downloadOptions.rejectUnauthorized = process.env.HTTPS_PROXY ? false : true options.downloadOptions.rejectUnauthorized = !process.env.NODE_TLS_REJECT_UNAUTHORIZED
options.rc = options.rc || extractCliMap(/^rc-.*/, options)
options.output = options.output =
(options.targets[0] as NexeTarget).platform === 'windows' (options.targets[0] as NexeTarget).platform === 'windows'
? `${(options.output || options.name).replace(/\.exe$/, '')}.exe` ? `${(options.output || options.name).replace(/\.exe$/, '')}.exe`
@@ -272,7 +261,7 @@ function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
const requireDefault = (x: string) => { const requireDefault = (x: string) => {
if (typeof x === 'string') { if (typeof x === 'string') {
return require(x).default return cjs(x).default
} }
return x return x
} }
@@ -281,7 +270,7 @@ function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault) options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault)
options.patches = flatten(opts.patch, options.patches).map(requireDefault) options.patches = flatten(opts.patch, options.patches).map(requireDefault)
if ((!options.mangle && !options.bundle) || options.patches.length) { if ((!options.mangle && !options.bundle) || options.patches.length > 0) {
options.build = true options.build = true
} }
+7 -5
View File
@@ -1,5 +1,6 @@
const fs = require('fs'), /* eslint-disable @typescript-eslint/no-var-requires */
fd = fs.openSync(process.execPath, 'r'), const fs = require('fs')
const fd = fs.openSync(process.execPath, 'r'),
stat = fs.statSync(process.execPath), stat = fs.statSync(process.execPath),
tailSize = Math.min(stat.size, 16000), tailSize = Math.min(stat.size, 16000),
tailWindow = Buffer.from(Array(tailSize)) tailWindow = Buffer.from(Array(tailSize))
@@ -8,7 +9,8 @@ fs.readSync(fd, tailWindow, 0, tailSize, stat.size - tailSize)
const footerPosition = tailWindow.indexOf('<nexe~~sentinel>') const footerPosition = tailWindow.indexOf('<nexe~~sentinel>')
if (footerPosition == -1) { if (footerPosition == -1) {
throw 'Invalid Nexe binary' process.stderr.write('\nInvalid Nexe binary\n')
process.exit(1)
} }
const footer = tailWindow.slice(footerPosition, footerPosition + 32), const footer = tailWindow.slice(footerPosition, footerPosition + 32),
@@ -48,8 +50,8 @@ Object.defineProperty(
})() })()
) )
const contentBuffer = Buffer.from(Array(contentSize)), const contentBuffer = Buffer.from(Array(contentSize))
Module = require('module') const Module = require('module')
fs.readSync(fd, contentBuffer, 0, contentSize, contentStart) fs.readSync(fd, contentBuffer, 0, contentSize, contentStart)
fs.closeSync(fd) fs.closeSync(fd)
-17
View File
@@ -1,17 +0,0 @@
import { NexeCompiler } from '../compiler'
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',
'"%p%python.exe" -V 2>&1'
)
return next()
}
+11 -35
View File
@@ -1,44 +1,20 @@
import { NexeCompiler } from '../compiler' import type { NexeCompiler } from '../compiler.js'
import { semverGt } from '../util' import { NexeError } from '../compiler.js'
import { semverGt } from '../util.js'
export default async function disableNodeCli(compiler: NexeCompiler, next: () => Promise<void>) { export default async function disableNodeCli(compiler: NexeCompiler, next: () => Promise<void>) {
if (compiler.options.enableNodeCli) { if (compiler.options.enableNodeCli) {
return next() return await next()
} }
if (semverGt(compiler.target.version, '11.6.0')) { const [regex, replacement] = semverGt(compiler.target.version, '18.99')
await compiler.replaceInFileAsync( ? [/(?<!int )ProcessGlobalArgsInternal\(argv[^;]*;/gm, 'ExitCode::kNoFailure']
'src/node.cc', : [/(?<!int )ProcessGlobalArgs\(argv[^;]*;/gm, 0],
/(?<!int )ProcessGlobalArgs\(argv[^;]*;/gm, replaced = await compiler.replaceInFileAsync('src/node.cc', regex, `${replacement};/*$&*/`)
'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')) {
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
})
} else {
const nodeccMarker = 'argv[index][0] =='
await compiler.replaceInFileAsync( if (!replaced) {
'src/node.cc', throw new NexeError('There was an error applying the Node CLI patch. Please open an issue.')
`${nodeccMarker} '-'`,
// allow NODE_OPTIONS, introduced in 8.0
semverGt(compiler.target.version, '7.99')
? `(${nodeccMarker} (is_env ? '-' : ']'))`
: `(${nodeccMarker} ']')`
)
} }
return next() return await next()
} }
+10 -5
View File
@@ -1,16 +1,21 @@
import { NexeCompiler } from '../compiler' import type { NexeCompiler } from '../compiler.js'
import { NexeError } from '../compiler.js'
export default async function flags(compiler: NexeCompiler, next: () => Promise<void>) { export default async function flags(compiler: NexeCompiler, next: () => Promise<void>) {
const nodeflags = compiler.options.flags const nodeflags = compiler.options.flags
if (!nodeflags.length) { if (nodeflags.length === 0) {
return next() return await next()
} }
await compiler.replaceInFileAsync( const replaced = await compiler.replaceInFileAsync(
'node.gyp', 'node.gyp',
"'node_v8_options%': ''", "'node_v8_options%': ''",
`'node_v8_options%': '${nodeflags.join(' ')}'` `'node_v8_options%': '${nodeflags.join(' ')}'`
) )
return next() if (!replaced) {
throw new NexeError('Node Flags not applied. Please report an issue')
}
return await next()
} }
-21
View File
@@ -1,21 +0,0 @@
import { NexeCompiler } from '../compiler'
export default async function nodeGyp(
{ files, replaceInFileAsync }: NexeCompiler,
next: () => Promise<void>
) {
await next()
const nodeGypMarker = "'lib/fs.js',"
await replaceInFileAsync(
'node.gyp',
nodeGypMarker,
`
${nodeGypMarker}
${files
.filter((x) => x.filename.startsWith('lib'))
.map((x) => `'${x.filename}'`)
.toString()},
`.trim()
)
}
-12
View File
@@ -1,12 +0,0 @@
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
if (!iconFile) {
return next()
}
await compiler.setFileContentsAsync('src/res/node.ico', await readFileAsync(normalize(iconFile)))
return next()
}
+4 -9
View File
@@ -1,12 +1,7 @@
import gyp from './gyp' import bootNexe from './third-party-main.js'
import bootNexe from './third-party-main' import cli from './disable-node-cli.js'
import buildFixes from './build-fixes' import flags from './flags.js'
import cli from './disable-node-cli'
import flags from './flags'
import ico from './ico'
import rc from './node-rc'
import snapshot from './snapshot'
const patches = [gyp, bootNexe, buildFixes, cli, flags, ico, rc, snapshot] const patches = [bootNexe, cli, flags]
export default patches export default patches
-28
View File
@@ -1,28 +0,0 @@
import { NexeCompiler } from '../compiler'
export default async function nodeRc(compiler: NexeCompiler, next: () => Promise<void>) {
const options = compiler.options.rc
if (!options) {
return next()
}
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}"`
file.contents = file.contents
.toString()
.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]}`)
}
})
return next()
}
-27
View File
@@ -1,27 +0,0 @@
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
if (!snapshot) {
return next()
}
const variablePrefix = semverGt(compiler.target.version, '11.0.0') ? 'v8_' : ''
await compiler.replaceInFileAsync(
compiler.configureScript,
'def configure_v8(o):',
`def configure_v8(o):\n o['variables']['${variablePrefix}embed_script'] = r'${resolve(
cwd,
snapshot
)}'\n o['variables']['${variablePrefix}warmup_script'] = r'${resolve(
cwd,
warmup || snapshot
)}'`
)
return next()
}
+69 -90
View File
@@ -1,14 +1,78 @@
import { NexeCompiler } from '../compiler' import { parse } from 'meriyah'
import { parse } from 'cherow' import type { NexeCompiler } from '../compiler.js'
import { wrap, semverGt } from '../util' import { NexeError } from '../compiler.js'
import { wrap, semverGt } from '../util.js'
function walkSome(node: any, visit: Function) { export default async function main(compiler: NexeCompiler, next: () => Promise<void>) {
let bootFile = 'lib/internal/bootstrap/pre_execution.js'
if (semverGt(compiler.target.version, '18.11.99')) {
bootFile = 'lib/internal/process/pre_execution.js'
}
const file = await compiler.readFileAsync(bootFile),
ast = parse(file.contents.toString(), {
next: true,
globalReturn: true,
loc: true,
specDeviation: true,
}),
location = { start: { line: 0 } }
walkSome(ast, (node: any) => {
if (!location.start.line && node.type === 'BlockStatement') {
// Find the first block statement and mark the location
Object.assign(location, node.loc)
return true
}
})
const fileLines = file.contents.toString().split('\n')
fileLines.splice(
location.start.line,
0,
'{{replace:lib/MOD/fs/bootstrap.js}}' + '\n' + 'expandArgv1 = false;\n'
)
file.contents = fileLines.join('\n')
const boot = await compiler.replaceInFileAsync(
bootFile,
'initializeFrozenIntrinsics();',
'initializeFrozenIntrinsics();\n' + wrap('{{replace:lib/MOD/patches/boot-nexe.js}}')
),
userModuleAssertion = await compiler.replaceInFileAsync(
bootFile,
'assert(!CJSLoader.hasLoadedAnyUserCJSModule)',
'/*assert(!CJSLoader.hasLoadedAnyUserCJSModule)*/'
),
workerSupport = 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'
)
assertPatches({ boot, userModuleAssertion, workerSupport })
return await next()
}
function assertPatches(patches: Record<string, boolean>) {
if (!Object.values(patches).every((x) => x)) {
let message = ''
Object.entries(patches).forEach(([k, v]) => {
if (!v) message += `\nFailed to apply patch for: "${k}". Please open an issue.`
})
throw new NexeError(message.trim())
}
}
function walkSome(node: any, visit: (node: any) => boolean | undefined) {
if (!node || typeof node.type !== 'string' || node._visited) { if (!node || typeof node.type !== 'string' || node._visited) {
return false return false
} }
visit(node) visit(node)
node._visited = true node._visited = true
for (let childNode in node) { for (const childNode in node) {
const child = node[childNode] const child = node[childNode]
if (Array.isArray(child)) { if (Array.isArray(child)) {
for (let i = 0; i < child.length; i++) { for (let i = 0; i < child.length; i++) {
@@ -22,88 +86,3 @@ function walkSome(node: any, visit: Function) {
} }
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
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'
}
const file = await compiler.readFileAsync(bootFile),
ast = parse(file.contents.toString(), {
loc: true,
tolerant: true,
next: true,
globalReturn: true,
node: true,
skipShebang: true,
}),
location = { start: { line: 0 } }
walkSome(ast, (node: any) => {
if (!location.start.line && node.type === 'BlockStatement') {
//Find the first block statement and mark the location
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')
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}}')
)
} else {
await compiler.replaceInFileAsync(
bootFile,
'initializePolicy();',
'initializePolicy();\n' + wrap('{{replace: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) {')) {
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'
)
} 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'
)
}
} else {
await compiler.setFileContentsAsync(
'lib/_third_party_main.js',
'{{replace:lib/patches/boot-nexe.js}}'
)
}
return next()
}
+10 -8
View File
@@ -1,5 +1,4 @@
import got = require('got') import { platforms, architectures, NexeTarget, getTarget, targetsEqual } from './target.js'
import { platforms, architectures, NexeTarget, getTarget, targetsEqual } from './target'
export { NexeTarget } 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]
@@ -21,6 +20,7 @@ interface NodeRelease {
} }
async function getJson<T>(url: string, options?: any) { async function getJson<T>(url: string, options?: any) {
const { default: got } = await import('got')
return JSON.parse((await got(url, options)).body) as T return JSON.parse((await got(url, options)).body) as T
} }
@@ -31,17 +31,19 @@ function isBuildableVersion(version: string) {
return !versionsToSkip.includes(Number(version.split('.')[0])) return !versionsToSkip.includes(Number(version.split('.')[0]))
} }
export function getLatestGitRelease(options?: any) { export async function getLatestGitRelease(options?: any) {
return getJson<GitRelease>('https://api.github.com/repos/nexe/nexe/releases/latest', options) return await getJson<GitRelease>(
'https://api.github.com/repos/nexe/nexe/releases/latest',
options
)
} }
export async function getUnBuiltReleases(options?: any) { export async function getUnBuiltReleases(options?: any) {
const nodeReleases = await getJson<NodeRelease[]>( const nodeReleases = await getJson<NodeRelease[]>(
'https://nodejs.org/download/release/index.json' 'https://nodejs.org/download/release/index.json'
) ),
const existingVersions = (await getLatestGitRelease(options)).assets.map((x) => getTarget(x.name)) existingVersions = (await getLatestGitRelease(options)).assets.map((x) => getTarget(x.name)),
versionMap: { [key: string]: true } = {}
const versionMap: { [key: string]: true } = {}
return nodeReleases return nodeReleases
.reduce((versions: NexeTarget[], { version }) => { .reduce((versions: NexeTarget[], { version }) => {
version = version.replace('v', '').trim() version = version.replace('v', '').trim()
+23 -29
View File
@@ -1,28 +1,22 @@
import { join, dirname } from 'path' import { join, dirname } from 'node:path'
import * as fs from 'fs' import { unlink, readdir, mkdir } from 'node:fs/promises'
import { promisify } from 'util'
import { readFileAsync, writeFileAsync, isDirectoryAsync } from '../util'
import mkdirpAsync = require('mkdirp')
import { NexeCompiler } from '../compiler'
const unlinkAsync = promisify(fs.unlink), import { readFile, writeFile, isDirectory } from '../util.js'
readdirAsync = promisify(fs.readdir) import type { NexeCompiler } from '../compiler.js'
function readDirAsync(dir: string): Promise<string[]> { async function readDirAsync(dir: string): Promise<string[]> {
return readdirAsync(dir).then((paths) => { return await readdir(dir).then(async (paths) => {
return Promise.all( return await Promise.all(
paths.map((file: string) => { paths.map(async (file: string) => {
const path = join(dir, file) const path = join(dir, file)
return isDirectoryAsync(path).then((x) => (x ? readDirAsync(path) : (path as any))) return await isDirectory(path).then((x) => (x ? readDirAsync(path) : (path as any)))
})
).then((result) => {
return [].concat(...(result as any))
}) })
).then((result) => result.flat())
}) })
} }
function maybeReadFileContentsAsync(file: string) { async function maybeReadFileContentsAsync(file: string) {
return readFileAsync(file, 'utf-8').catch((e) => { return await readFile(file, 'utf-8').catch((e) => {
if (e.code === 'ENOENT') { if (e.code === 'ENOENT') {
return '' return ''
} }
@@ -40,28 +34,28 @@ function maybeReadFileContentsAsync(file: string) {
* - Finally, The patched files are written into source. * - Finally, The patched files are written into source.
*/ */
export default async function artifacts(compiler: NexeCompiler, next: () => Promise<void>) { export default async function artifacts(compiler: NexeCompiler, next: () => Promise<void>) {
const { src } = compiler const { src } = compiler,
const temp = join(src, 'nexe') temp = join(src, 'nexe')
await mkdirpAsync(temp) await mkdir(temp, { recursive: true })
const tmpFiles = await readDirAsync(temp) const tmpFiles = await readDirAsync(temp)
await Promise.all( await Promise.all(
tmpFiles.map(async (path) => { tmpFiles.map(async (path) => {
return compiler.writeFileAsync(path.replace(temp, ''), await readFileAsync(path, 'utf-8')) return await compiler.writeFileAsync(path.replace(temp, ''), await readFile(path, 'utf-8'))
}) })
) )
await next() await next()
await Promise.all(tmpFiles.map((x) => unlinkAsync(x))) await Promise.all(tmpFiles.map(async (x) => await unlink(x)))
return Promise.all( return await Promise.all(
compiler.files.map(async (file) => { compiler.files.map(async (file) => {
const sourceFile = join(src, file.filename) const sourceFile = join(src, file.filename),
const tempFile = join(temp, file.filename) tempFile = join(temp, file.filename),
const fileContents = await maybeReadFileContentsAsync(sourceFile) fileContents = await maybeReadFileContentsAsync(sourceFile)
await mkdirpAsync(dirname(tempFile)) await mkdir(dirname(tempFile), { recursive: true })
await writeFileAsync(tempFile, fileContents) await writeFile(tempFile, fileContents)
await compiler.writeFileAsync(file.filename, file.contents) await compiler.writeFileAsync(file.filename, file.contents)
}) })
) )
+14 -15
View File
@@ -1,12 +1,14 @@
import { NexeCompiler, NexeError } from '../compiler' import { resolve, relative } from 'node:path'
import { resolve, relative } from 'path' import type { Readable } from 'node:stream'
import resolveFiles, { resolveSync } from 'resolve-dependencies' import resolveFiles, { resolveSync } from 'resolve-dependencies'
import { dequote, STDIN_FLAG, semverGt, each } from '../util'
import { Readable } from 'stream'
function getStdIn(stdin: Readable): Promise<string> { import type { NexeCompiler } from '../compiler.js'
import { NexeError } from '../compiler.js'
import { dequote, STDIN_FLAG } from '../util.js'
async function getStdIn(stdin: Readable): Promise<string> {
let out = '' let out = ''
return new Promise((resolve) => { return await new Promise((resolve) => {
stdin stdin
.setEncoding('utf8') .setEncoding('utf8')
.on('readable', () => { .on('readable', () => {
@@ -28,12 +30,7 @@ export default async function bundle(compiler: NexeCompiler, next: any) {
const { bundle: doBundle, cwd, input: inputPath } = compiler.options const { bundle: doBundle, cwd, input: inputPath } = compiler.options
let input = inputPath let input = inputPath
compiler.entrypoint = './' + relative(cwd, input) compiler.entrypoint = './' + relative(cwd, input)
if (semverGt(compiler.target.version, '11.99')) {
compiler.startup = '' compiler.startup = ''
} else {
compiler.startup = ';require("module").runMain();'
}
if (!doBundle) { if (!doBundle) {
await compiler.addResource(resolve(cwd, input)) await compiler.addResource(resolve(cwd, input))
@@ -42,6 +39,7 @@ export default async function bundle(compiler: NexeCompiler, next: any) {
let code = '' let code = ''
if (typeof doBundle === 'string') { if (typeof doBundle === 'string') {
// eslint-disable-next-line @typescript-eslint/no-var-requires
code = await require(doBundle).createBundle(compiler.options) code = await require(doBundle).createBundle(compiler.options)
} }
@@ -68,16 +66,17 @@ export default async function bundle(compiler: NexeCompiler, next: any) {
) )
if ( 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 > 0
) { ) {
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)) warnings.forEach((x: string) => compiler.log.step('[WARNING]: ' + x, 'stopAndPersist', 'yellow'))
await Promise.all( await Promise.all(
Object.entries(files).map(([key, file]) => { Object.entries(files).map(async ([key, file]) => {
return compiler.addResource(key, file) return await compiler.addResource(key, file)
}) })
) )
+7 -6
View File
@@ -1,6 +1,7 @@
import { NexeCompiler } from '../compiler' import { rm } from 'node:fs/promises'
import { rimrafAsync } from '../util'
import { NexeTarget } from '../target' import type { NexeCompiler } from '../compiler.js'
import type { NexeTarget } from '../target.js'
export default async function clean(compiler: NexeCompiler, next: () => Promise<any>) { export default async function clean(compiler: NexeCompiler, next: () => Promise<any>) {
const { options } = compiler const { options } = compiler
@@ -11,9 +12,9 @@ export default async function clean(compiler: NexeCompiler, next: () => Promise<
} }
const step = compiler.log.step('Cleaning up nexe build artifacts...') const step = compiler.log.step('Cleaning up nexe build artifacts...')
step.log(`Deleting contents at: ${path}`) step.log(`Deleting contents at: ${path}`)
await rimrafAsync(path) await rm(path, { recursive: true, force: true })
step.log(`Deleted contents at: ${path}`) step.log(`Deleted contents at: ${path}`)
return compiler.quit() return await compiler.quit()
} }
return next() return await next()
} }
+8 -9
View File
@@ -1,9 +1,8 @@
import { dirname, normalize, relative, resolve } from 'path' import { dirname, normalize, relative, resolve } from 'node:path'
import { createWriteStream, chmodSync, statSync } from 'fs' import { createWriteStream, chmodSync, statSync, mkdirSync } from 'node:fs'
import { NexeCompiler } from '../compiler' import type { NexeCompiler } from '../compiler.js'
import { NexeTarget } from '../target' import type { NexeTarget } from '../target.js'
import { STDIN_FLAG } from '../util' import { STDIN_FLAG } from '../util.js'
import mkdirp = require('mkdirp')
/** /**
* The "cli" step detects the appropriate input. If no input options are passed, * The "cli" step detects the appropriate input. If no input options are passed,
@@ -20,11 +19,11 @@ export default async function cli(compiler: NexeCompiler, next: () => Promise<vo
const { log } = compiler, const { log } = compiler,
target = compiler.options.targets.shift() as NexeTarget, target = compiler.options.targets.shift() as NexeTarget,
deliverable = await compiler.compileAsync(target), deliverable = await compiler.compileAsync(target),
output = normalize(compiler.output!) output = normalize(compiler.output)
mkdirp.sync(dirname(output)) mkdirSync(dirname(output), { recursive: true })
return new Promise((res, rej) => { return await new Promise((res, rej) => {
const step = log.step('Writing result to file') const step = log.step('Writing result to file')
deliverable deliverable
.pipe(createWriteStream(output)) .pipe(createWriteStream(output))
+17 -13
View File
@@ -1,15 +1,19 @@
import download = require('download') import { dirname } from 'node:path'
import { pathExistsAsync } from '../util'
import { LogStep } from '../logger' import download from 'download'
import { IncomingMessage } from 'http'
import { NexeCompiler, NexeError } from '../compiler' import { pathExists } from '../util.js'
import { dirname } from 'path' import type { LogStep } from '../logger.js'
import type { NexeCompiler } from '../compiler.js'
import { NexeError } from '../compiler.js'
import type { IncomingMessage } from 'node:http'
function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) { function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) {
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`) const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
return download(url, dest, Object.assign(options, { extract: true, strip: 1 })) return download(url, dest, Object.assign(options, { extract: true, strip: 1 }))
.on('response', (res: IncomingMessage) => { .on('response', (res: IncomingMessage) => {
const total = +res.headers['content-length']! const total = Number(res.headers['content-length'])
let current = 0 let current = 0
res.on('data', (data) => { res.on('data', (data) => {
current += data.length current += data.length
@@ -30,11 +34,11 @@ async function fetchPrebuiltBinary(compiler: NexeCompiler, step: any) {
await download(remoteAsset, dirname(filename), compiler.options.downloadOptions).on( await download(remoteAsset, dirname(filename), compiler.options.downloadOptions).on(
'response', 'response',
(res: IncomingMessage) => { (res: IncomingMessage) => {
const total = +res.headers['content-length']! const total = Number(res.headers['content-length'])
let current = 0 let current = 0
res.on('data', (data) => { res.on('data', (data) => {
current += data.length current += data.length
step!.modify(`Downloading...${((current / total) * 100).toFixed()}%`) step?.modify(`Downloading...${((current / total) * 100).toFixed()}%`)
}) })
} }
) )
@@ -58,14 +62,14 @@ export default async function downloadNode(compiler: NexeCompiler, next: () => P
{ sourceUrl, downloadOptions, build } = compiler.options, { sourceUrl, downloadOptions, build } = compiler.options,
url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`, url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`,
step = log.step( step = log.step(
`Downloading ${build ? '' : 'pre-built '}Node.js${build ? `source from: ${url}` : ''}` `Downloading ${build ? '' : 'pre-built '}Node.js${build ? ` source from: ${url}` : ''}`
), ),
exeLocation = compiler.getNodeExecutableLocation(build ? undefined : target), exeLocation = compiler.getNodeExecutableLocation(build ? undefined : target),
downloadExists = await pathExistsAsync(build ? src : exeLocation) downloadExists = await pathExists(build ? src : exeLocation)
if (downloadExists) { if (downloadExists) {
step.log('Already downloaded...') step.log('Already downloaded...')
return next() return await next()
} }
if (build) { if (build) {
@@ -74,5 +78,5 @@ export default async function downloadNode(compiler: NexeCompiler, next: () => P
await fetchPrebuiltBinary(compiler, step) await fetchPrebuiltBinary(compiler, step)
} }
return next() return await next()
} }
+26 -17
View File
@@ -1,25 +1,34 @@
import { each } from '../util' import { relative } from 'node:path'
import * as globs from 'globby'
import { resolve } from 'path' import fg from 'fast-glob'
import { NexeCompiler } from '../compiler'
import { each } from '../util.js'
import type { NexeCompiler } from '../compiler.js'
export default async function resource(compiler: NexeCompiler, next: () => Promise<any>) { export default async function resource(compiler: NexeCompiler, next: () => Promise<any>) {
const { cwd, resources } = compiler.options const { cwd, resources } = compiler.options
if (!resources.length) { if (resources.length === 0) {
return next() return await next()
} }
const step = compiler.log.step('Bundling Resources...')
let count = 0 let count = 0
// workaround for https://github.com/sindresorhus/globby/issues/127 resources.forEach((x: string) => {
// and https://github.com/mrmlnc/fast-glob#pattern-syntax if (x.includes('node_modules/**')) {
const resourcesWithForwardSlashes = resources.map((r) => r.replace(/\\/g, '/')) compiler.log.step(
`[WARNING]: pattern "${x}" will include many files, consider narrowing the pattern`,
await each(globs(resourcesWithForwardSlashes, { cwd, onlyFiles: true }), async (file) => { 'stopAndPersist',
count++ 'yellow'
step.log(`Including file: ${file}`) )
await compiler.addResource(resolve(cwd, file)) }
}) })
step.log(`Included ${count} file(s)`)
return next() const step = compiler.log.step('Bundling Resources...')
await each(fg(resources, { cwd, absolute: true, onlyFiles: true }), async (file) => {
count++
step.log(`Including file: ${relative(cwd, file)}`)
await compiler.addResource(file)
})
compiler.log.step(`Included ${count} file(s)`, 'stopAndPersist')
return await next()
} }
+3 -4
View File
@@ -1,15 +1,14 @@
import { NexeCompiler } from '../compiler' import type { NexeCompiler } from '../compiler.js'
import { wrap } from '../util' import { wrap } from '../util.js'
export default async function (compiler: NexeCompiler, next: () => Promise<void>) { export default async function (compiler: NexeCompiler, next: () => Promise<void>) {
await next() await next()
compiler.shims.push( compiler.shims.push(
wrap( wrap(
'' + '' +
'{{replace:lib/fs/patch.js}}' + '{{replace:lib/MOD/fs/patch.js}}' +
'\nshimFs(process.__nexe)' + '\nshimFs(process.__nexe)' +
`\n${compiler.options.fs ? '' : 'restoreFs()'}` `\n${compiler.options.fs ? '' : 'restoreFs()'}`
//TODO support only restoring specific methods
) )
) )
compiler.shims.push( compiler.shims.push(
+4 -4
View File
@@ -22,9 +22,8 @@ const prettyPlatform: { [key: string]: NodePlatform } = {
linux: 'linux', linux: 'linux',
static: 'alpine', static: 'alpine',
alpine: 'alpine', alpine: 'alpine',
} },
prettyArch: { [key: string]: NodeArch } = {
const prettyArch: { [key: string]: NodeArch } = {
x86: 'x86', x86: 'x86',
arm6: 'arm', arm6: 'arm',
arm64: 'arm64', arm64: 'arm64',
@@ -36,7 +35,7 @@ const prettyArch: { [key: string]: NodeArch } = {
ia32: 'x86', ia32: 'x86',
x32: 'x86', x32: 'x86',
x64: 'x64', x64: 'x64',
} }
function isVersion(x: string) { function isVersion(x: string) {
if (!x) { if (!x) {
@@ -58,6 +57,7 @@ class Target implements NexeTarget {
toJSON() { toJSON() {
return this.toString() return this.toString()
} }
toString() { toString() {
return `${this.platform}-${this.arch}-${this.version}` return `${this.platform}-${this.arch}-${this.version}`
} }
+4 -1
View File
@@ -6,8 +6,11 @@ declare module 'got' {
const got: GotFn const got: GotFn
export = got export = got
} }
declare module 'caw'
declare module 'download' { declare module 'download' {
import { Duplex } from 'stream' import type { Duplex } from 'stream'
interface DownloadOptions { interface DownloadOptions {
extract?: boolean extract?: boolean
strip?: number strip?: number
+50 -33
View File
@@ -1,17 +1,40 @@
import { readFile, writeFile, stat } from 'fs' import { readFile, writeFile, stat } from 'node:fs/promises'
import { execFile } from 'child_process' import { execFile as exec } from 'node:child_process'
import { promisify } from 'util' import { promisify } from 'node:util'
import rimraf = require('rimraf')
const rimrafAsync = promisify(rimraf)
export const STDIN_FLAG = '[stdin]' export const STDIN_FLAG = '[stdin]'
export const esm: {
chalk: typeof import('chalk') | null
ora: typeof import('ora') | null
got: typeof import('got') | null
} = {
got: null,
chalk: null,
ora: null,
}
export async function initEsm() {
await Promise.all(
(['chalk', 'got', 'ora'] as const).map(async (x) => {
esm[x] = await import(x)
})
)
}
const id = (x: any) => x
export function color(color?: string, text?: string) {
return !color ? text : ((esm.chalk?.default as any)[color] || id)(text)
}
export type Func<T = any, Args = any> = (...args: Args[]) => T
export async function each<T>( export async function each<T>(
list: T[] | Promise<T[]>, list: T[] | Promise<T[]>,
action: (item: T, index: number, list: T[]) => Promise<any> action: (item: T, index: number, list: T[]) => Promise<any>
) { ) {
const l = await list const l = await list
return Promise.all(l.map(action)) return await Promise.all(l.map(action))
} }
export function wrap(code: string) { export function wrap(code: string) {
@@ -26,12 +49,12 @@ function falseOnEnoent(e: any) {
} }
function padRight(str: string, l: number) { 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>( const bound: MethodDecorator = function bound<T>(
target: Object, target: object,
propertyKey: string | Symbol, propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<T> descriptor: TypedPropertyDescriptor<T>
) { ) {
const configurable = true const configurable = true
@@ -51,8 +74,8 @@ const bound: MethodDecorator = function bound<T>(
function dequote(input: string) { function dequote(input: string) {
input = input.trim() input = input.trim()
const singleQuote = input.startsWith("'") && input.endsWith("'") const singleQuote = input.startsWith("'") && input.endsWith("'"),
const doubleQuote = input.startsWith('"') && input.endsWith('"') doubleQuote = input.startsWith('"') && input.endsWith('"')
if (singleQuote || doubleQuote) { if (singleQuote || doubleQuote) {
return input.slice(1).slice(0, -1) return input.slice(1).slice(0, -1)
} }
@@ -64,30 +87,25 @@ export interface ReadFileAsync {
(path: string, encoding: string): Promise<string> (path: string, encoding: string): Promise<string>
} }
const readFileAsync = promisify(readFile) const execFile = promisify(exec),
const writeFileAsync = promisify(writeFile) isWindows = process.platform === 'win32'
const statAsync = promisify(stat)
const execFileAsync = promisify(execFile)
const isWindows = process.platform === 'win32'
function pathExistsAsync(path: string) { async function pathExists(path: string) {
return statAsync(path) return await stat(path)
.then((x) => true) .then(() => true)
.catch(falseOnEnoent) .catch(falseOnEnoent)
} }
function isDirectoryAsync(path: string) { async function isDirectory(path: string) {
return statAsync(path) return await stat(path)
.then((x) => x.isDirectory()) .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) { function semverGt(version: string, operand: string) {
const [cMajor, cMinor, cPatch] = version.split('.').map(Number) const [cMajor, cMinor, cPatch] = version.split('.').map(Number)
let [major, minor, patch] = operand.split('.').map(Number) let [major, minor, patch] = operand.split('.').map(Number)
major = Number(major)
if (!minor) minor = 0 if (!minor) minor = 0
if (!patch) patch = 0 if (!patch) patch = 0
return ( return (
@@ -100,14 +118,13 @@ function semverGt(version: string, operand: string) {
export { export {
dequote, dequote,
padRight, padRight,
semverGt,
bound, bound,
isWindows, isWindows,
rimrafAsync, stat,
statAsync, execFile,
execFileAsync, readFile,
readFileAsync, pathExists,
pathExistsAsync, isDirectory,
isDirectoryAsync, writeFile,
writeFileAsync, semverGt,
} }
+45 -96
View File
@@ -1,104 +1,43 @@
import * as nexe from '../lib/nexe' import { env } from 'node:process'
import { getUnBuiltReleases, getLatestGitRelease } from '../lib/releases' import { execSync } from 'node:child_process'
import { runDockerBuild } from './docker' import { buildArm, buildAlpine } from './docker.js'
import { getTarget, targetsEqual, NexeTarget } from '../lib/target'
import { pathExistsAsync, readFileAsync, execFileAsync, semverGt } from '../lib/util'
import got from 'got'
import { cpus } from 'os'
const env = process.env, export async function build(target = env.NEXE_TARGET || null) {
isPullRequest = env.BUILD_REASON === 'PullRequest', if (!target?.trim()) {
isWindows = process.platform === 'win32', throw new Error('Environment variable NEXE_TARGET not found. Received: ' + target)
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)',
} }
if (require.main === module) { const done = keepalive()
if (!isPullRequest) { let file = ''
build().catch((x) => { try {
console.error(x) if (target.includes('alpine')) {
process.exit(1) file = await buildAlpine(target)
}) } else if (target.includes('arm')) {
file = await buildArm(target)
} else {
file = await nexeBuild(target)
assertNexeBinary(file)
} }
} catch (e: any) {
done()
throw e
}
if (!file) {
throw new Error('Binary failed to build')
}
uploadBinary(target, file)
} }
async function build() { function nexeBuild(target: string) {
const releases = await getUnBuiltReleases({ headers }) console.log('building: ' + target)
if (!releases.length) { return Promise.resolve('')
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')
let target: NexeTarget | undefined function uploadBinary(target: string, file: string) {
void file
if (env.NEXE_VERSION) target = getTarget(env.NEXE_VERSION) console.log('uploading to release')
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...')
}
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...`)
}
const output = isWindows ? './out.exe' : './out',
options = {
mangle: false,
build: true,
verbose: Boolean(env.NEXE_VERBOSE!),
target,
make: ['-j' + cpus().length],
output,
}
console.log('Building: ' + target + ' on ' + buildHost)
const stop = keepalive()
if (
[/*'arm7l', 'arm6l', 'arm64', */ 'alpine'].includes(target.platform) &&
buildHost.includes('alpine')
) {
await runDockerBuild(target)
} else {
await nexe.compile(options)
}
stop()
if (await pathExistsAsync(output)) {
await assertNexeBinary(output)
const gitRelease = await getLatestGitRelease({ headers }),
unbuiltReleases = await getUnBuiltReleases({ headers })
if (!unbuiltReleases.some((x) => targetsEqual(x, target!))) {
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)
}
} }
function keepalive() { function keepalive() {
@@ -107,10 +46,20 @@ function keepalive() {
} }
function assertNexeBinary(file: string) { function assertNexeBinary(file: string) {
return execFileAsync(file).catch((e) => { if (!file) throw new Error('Invalid filename: "' + file + '"')
let result = ''
try {
result = execSync(file).toString('utf8')
} catch (e: any) {
if (e && e.stack && e.stack.includes('Invalid Nexe binary')) { if (e && e.stack && e.stack.includes('Invalid Nexe binary')) {
return return
} }
throw e throw e
}) }
throw new Error('Not a bare nexe binary: ' + result)
} }
build().catch((e) => {
console.error(e)
process.exit(1)
})
+2
View File
@@ -0,0 +1,2 @@
export declare function buildArm(target: string): Promise<string>
export declare function buildAlpine(target: string): Promise<string>
+6 -65
View File
@@ -1,68 +1,9 @@
import { NexeTarget, architectures } from '../lib/target' export function buildArm(target: string) {
import { writeFileAsync, readFileAsync } from '../lib/util' console.log('building arm: ' + target)
import got from 'got' return Promise.resolve('')
import execa = require('execa')
import { appendFileSync } from 'fs'
function alpine(target: NexeTarget) {
return `
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
WORKDIR /
RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.tar.gz | tar -xz && \
mkdir /nexe_temp && mv /node-v\${NODE_VERSION} /nexe_temp/\${NODE_VERSION} && \
cd /nexe_temp/\${NODE_VERSION} && \
./configure --prefix=/usr --fully-static && \
make && make install && \
paxctl -cm /usr/bin/node
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()
} }
function arm(target: NexeTarget) { export function buildAlpine(target: string) {
return ` console.log('building alpine: ' + target)
FROM hypriot/rpi-node return Promise.resolve('')
ENV NEXE_VERSION=latest
WORKDIR /
RUN yarn global add nexe@\${NEXE_VERSION} && \
nexe --build --no-mangle -o out -t ${target.version}
`.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 = []
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)
} 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))
}
} }
+18 -15
View File
@@ -1,20 +1,23 @@
import { writeFileSync, readFileSync } from 'fs' import { writeFileSync, readFileSync } from 'node:fs'
import { equal } from 'node:assert'
/** /**
* post build step to insert code files into code files (naively). * post build step to insert code files into code files (naively).
* '{{replace:path/to/file}}' => "file contents" * '{{replace:path/to/file}}' => "file contents"
* And the package.json version. * And the package.json version.
*/ */
//inject('plugins/nexe-daemon/lib/index.js') const totalReplacements = 8
cp('src/fs/package.json', 'lib/fs/package.json') let replaced = 0
cp('src/fs/bootstrap.js', 'lib/fs/bootstrap.js') const mods = ['cjs', 'esm'] as const
cp('src/fs/README.md', 'lib/fs/README.md')
inject('lib/patches/third-party-main.js') mods.forEach((x) => {
inject('lib/steps/shim.js') inject(x, `lib/${x}/patches/third-party-main.js`)
inject('lib/options.js', JSON.stringify(require('../package.json').version)) inject(x, `lib/${x}/steps/shim.js`)
// eslint-disable-next-line @typescript-eslint/no-var-requires
inject(x, `lib/${x}/options.js`, JSON.stringify(require('../package.json').version))
})
function inject(filename: string, ...replacements: string[]) { function inject(mod: typeof mods[number], filename: string, ...replacements: string[]) {
let contents = readFileSync(filename, 'utf8') let contents = readFileSync(filename, 'utf8')
contents = contents.replace(/('{{(.*)}}')/g, (substring: string, ...matches: string[]) => { contents = contents.replace(/('{{(.*)}}')/g, (substring: string, ...matches: string[]) => {
if (!matches || !matches[1]) { if (!matches || !matches[1]) {
@@ -24,15 +27,15 @@ function inject(filename: string, ...replacements: string[]) {
if (replace !== 'replace') { if (replace !== 'replace') {
return substring return substring
} }
++replaced
console.log('Replacing: ', substring) console.log('Replacing: ', substring)
return replacements[+file] ? replacements[+file] : JSON.stringify(readFileSync(file, 'utf8'))
return replacements[+file]
? replacements[+file]
: JSON.stringify(readFileSync(file.replace('MOD', mod), 'utf8').replace('"use strict";', ''))
}) })
writeFileSync(filename, contents) writeFileSync(filename, contents)
console.log(`Wrote: ${filename}`) console.log(`Wrote: ${filename}`)
} }
function cp(from: string, to: string) { equal(replaced, totalReplacements, `Expected to replace ${totalReplacements} things`)
const file = readFileSync(from)
writeFileSync(to, file)
console.log('Copied: ', from, 'To: ', to)
}
+15
View File
@@ -0,0 +1,15 @@
import * as nexeEsm from '../lib/esm/nexe.js'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import { ok } from 'node:assert'
const cjs = createRequire(fileURLToPath(import.meta.url)),
nexeCjs = cjs('../lib/cjs/nexe.js'),
exports = ['NexeCompiler', 'argv', 'compile', 'help', 'version']
for (const exp of exports) {
ok(exp in nexeCjs)
ok(exp in nexeEsm)
}
console.log('esm and cjs modules match')
+10 -9
View File
@@ -1,7 +1,8 @@
import { normalizeOptions } from '../src/options' import { describe, it, expect } from 'vitest'
import { expect } from 'chai'
import * as path from 'path' import * as path from 'path'
import { normalizeOptions } from '../src/options.js'
const ext = process.platform === 'win32' ? '.exe' : '' const ext = process.platform === 'win32' ? '.exe' : ''
describe('options', () => { describe('options', () => {
@@ -27,12 +28,12 @@ describe('options', () => {
expect(options.input).to.equal(path.resolve('./test/fixture/entry.js')) expect(options.input).to.equal(path.resolve('./test/fixture/entry.js'))
}) })
it('should resolve pathed options against cwd', () => { it('should resolve pathed options against cwd', () => {
const cwd = path.join(process.cwd(), 'test/fixture') const cwd = path.join(process.cwd(), 'test/fixture'),
const options = normalizeOptions({ options = normalizeOptions({
cwd, cwd,
input: 'entry', input: 'entry',
output: 'abc', output: 'abc',
temp: './d' temp: './d',
}) })
expect(options.temp).to.equal(path.resolve(cwd, './d')) expect(options.temp).to.equal(path.resolve(cwd, './d'))
expect(options.input).to.equal(path.resolve(cwd, 'entry.js')) expect(options.input).to.equal(path.resolve(cwd, 'entry.js'))
@@ -47,13 +48,13 @@ describe('options', () => {
}) })
it('should append trailing slash to third-party remote if necessary', () => { it('should append trailing slash to third-party remote if necessary', () => {
const options = normalizeOptions({ const options = normalizeOptions({
remote: 'https://sitejs.org/nexe' remote: 'https://sitejs.org/nexe',
}) })
expect(options.remote).to.equal('https://sitejs.org/nexe/') expect(options.remote).to.equal('https://sitejs.org/nexe/')
}) })
it('should not append trailing slash to third-party remote that already has one', () => { it('should not append trailing slash to third-party remote that already has one', () => {
const options = normalizeOptions({ const options = normalizeOptions({
remote: 'https://sitejs.org/nexe/' remote: 'https://sitejs.org/nexe/',
}) })
expect(options.remote).to.equal('https://sitejs.org/nexe/') expect(options.remote).to.equal('https://sitejs.org/nexe/')
}) })
@@ -62,13 +63,13 @@ describe('options', () => {
describe('output', () => { describe('output', () => {
it('should work', () => { it('should work', () => {
const options = normalizeOptions({ const options = normalizeOptions({
output: './some-output' output: './some-output',
}) })
expect(options.output).to.equal(path.resolve(`./some-output${ext}`)) expect(options.output).to.equal(path.resolve(`./some-output${ext}`))
}) })
it('should default to the input file name if not index', () => { it('should default to the input file name if not index', () => {
const options = normalizeOptions({ const options = normalizeOptions({
input: './test/fixture' input: './test/fixture',
}) })
expect(options.output).to.equal(path.resolve(`./entry${ext}`)) expect(options.output).to.equal(path.resolve(`./entry${ext}`))
}) })
+1
View File
@@ -0,0 +1 @@
export {}
+8 -6
View File
@@ -1,9 +1,11 @@
import { padRight, isWindows } from '../src/util' import { describe, it, expect } from 'vitest'
import { expect } from 'chai'
import chalk from 'chalk' import chalk from 'chalk'
import { getTarget, NexeTarget } from '../src/target'
const b = chalk.blue import { padRight } from '../src/util.js'
const arch = process.arch === 'ia32' ? 'x86' : process.arch import { getTarget } from '../src/target.js'
const b = chalk.blue,
arch = process.arch === 'ia32' ? 'x86' : process.arch
describe('Targets', () => { describe('Targets', () => {
;[ ;[
@@ -16,7 +18,7 @@ describe('Targets', () => {
['static-x86-6.10.3', 'alpine-x86-6.10.3'], ['static-x86-6.10.3', 'alpine-x86-6.10.3'],
['linux-x32', `linux-x86-${process.version.slice(1)}`], ['linux-x32', `linux-x86-${process.version.slice(1)}`],
['alpine-notsupported-6.10.3', `alpine-${arch}-6.10.3`], ['alpine-notsupported-6.10.3', `alpine-${arch}-6.10.3`],
['not-a-thing', getTarget(process).toString()] ['not-a-thing', getTarget(process).toString()],
].forEach(([input, expected]) => { ].forEach(([input, expected]) => {
it(`should accept: ${padRight(JSON.stringify(input), 53)} ${b('->')} ${expected}`, () => { it(`should accept: ${padRight(JSON.stringify(input), 53)} ${b('->')} ${expected}`, () => {
expect(getTarget(input).toString()).to.equal(expected) expect(getTarget(input).toString()).to.equal(expected)
+17 -6
View File
@@ -1,14 +1,25 @@
{ {
"compilerOptions": { "compilerOptions": {
"target":"es2015", "outDir": "./lib/types",
"target":"es2020",
"lib": ["es2020"],
"module": "es2020",
"allowSyntheticDefaultImports": true,
"declaration": true,
"skipLibCheck": true, "skipLibCheck": true,
"moduleResolution": "node",
"experimentalDecorators": true, "experimentalDecorators": true,
"lib": ["es2018"],
"module": "commonjs",
"outDir": "./lib",
"strict": true "strict": true
}, },
"include":[ "include":[
"src/**/*.ts" "src/**/*.ts",
] ],
"ts-node": {
"transpileOnly": true,
"transpiler": "ts-node/transpilers/swc",
"compilerOptions": {
"esModuleInterop": true,
"module": "commonjs"
}
}
} }
-9
View File
@@ -1,9 +0,0 @@
{
"rulesDirectory": ["tslint-plugin-prettier", "tslint-config-prettier"],
"linterOptions": {
"exclude": ["**/node_modules/**/*"]
},
"rules": {
"prettier": [true, { "semi": false, "printWidth": 100, "singleQuote": true }]
}
}