Compare commits

..

68 Commits

Author SHA1 Message Date
calebboyd e62d728624 fix: uppercase windows path letters after normalize
closes #618
2019-05-28 18:04:16 -05:00
Jason Cooke a0c8d31f03 docs: fix typo (#625) 2019-05-23 18:17:55 -05:00
calebboyd 472b98d839 chore: publish copyFile shim 2019-04-28 14:28:59 -05:00
calebboyd 9ae6ed8ae8 chore: log open handles after a minute or two 2019-04-28 13:28:40 -05:00
calebboyd a0c648bc8b chore: set install directory for nasm 2019-04-26 14:33:26 -05:00
Daniel Ruf 463595f502 chore: typo 2019-04-25 11:23:08 -05:00
calebboyd be4ab09745 chore: update ci in readme 2019-04-25 11:22:19 -05:00
calebboyd a6ad034336 chore: set env variables 2019-04-18 13:27:08 -05:00
calebboyd 0afbe3b732 chore: fix types in azure build script 2019-04-15 23:03:41 -05:00
calebboyd 0865fda5ef fix: don't infer main module when not in tty 2019-04-15 23:01:50 -05:00
calebboyd 7bfdc39700 chore: add azure-pipeline.yml 2019-04-15 17:12:41 -05:00
calebboyd 9486399cec chore: update alpine build script 2019-04-15 14:59:57 -05:00
calebboyd 75bf6a3dc4 chore: naive copyFile patch 2019-04-14 21:37:06 -05:00
calebboyd 0dca0745ee feat(fs): add option to restore all but internal filesystem methods 2019-04-08 22:14:51 -05:00
Antonio Nuno Monteiro 6fc2ed9519 fix: snapshot gyp variable name after node 11 (#606)
this changed from `embed_script` to `v8_embed_script` after node 11
2019-04-08 21:14:16 -05:00
calebboyd 448f4270ea fix: set entrypoint correctly when outside a tty 2019-04-08 16:23:21 -05:00
calebboyd c9c9b5a0a3 fix: mount vfs relative to execPath instead of cwd 2019-04-08 11:10:33 -05:00
calebboyd d2756a5f75 fix: load implicit input if stdin is empty without a tty 2019-04-04 23:54:06 -05:00
calebboyd 255f3887c7 chore: remove unused methods 2019-04-04 19:37:30 -05:00
calebboyd 75ae41b7b9 chore: bump version 2019-04-04 09:48:17 -05:00
calebboyd 3692c142fb doc: describe mangle option 2019-04-04 09:47:20 -05:00
calebboyd af2e381ed4 feat: ensure output directory exists
closes #590
2019-04-04 09:35:15 -05:00
jerry_liu 44393a22da More flexiable nexe footer search range to fix digital sign binary launch issue
Fix #372
2019-04-03 13:37:31 -05:00
jerry_liu 57a08d2cfd remove compress option 2019-04-03 08:15:21 -05:00
calebboyd 6825cef609 feat: asset option 2019-04-03 08:12:26 -05:00
calebboyd b3c2d2e764 fix: use hardcoded version for asset url 2019-04-01 16:38:47 -05:00
calebboyd d73325ba35 chore: analyze resources as entrypoints 2019-03-30 20:18:41 -07:00
calebboyd 6e3cadbe22 chore: dep bump 2019-03-30 13:54:51 -07:00
calebboyd de42dd94ad refactor: run download for src and prebuilt before plugins or patches 2019-03-30 13:29:58 -07:00
calebboyd 37192e4ca8 fix: ignore node cli for 11.6.0+ 2019-03-28 19:19:03 -07:00
calebboyd c5cdbd9380 chore: remove unused directories in appveyor 2019-03-09 15:27:25 -06:00
calebboyd b022cd525d fix: size on bundled stat 2019-02-21 22:33:44 -06:00
bruce-one 72d9dfebf6 fix(fs): stat file and directories for bundled files (#589)
Ensure Stat return has correct prototype.
In particular to fix `Reflect.apply(Stats.prototype.isFile, stat, [])`.
2019-02-21 18:48:49 -06:00
calebboyd 8849ac0c0c chore: don't try to upload an existing asset 2019-01-31 19:56:36 -06:00
calebboyd 8ad6641278 chore: update travis vm 2019-01-31 17:54:10 -06:00
calebboyd 1a75bdb058 fix: avoid syncronous errors in compile method 2019-01-23 20:31:11 -06:00
calebboyd 12c77a34fd chore: update resolve-dependencies 2019-01-05 11:25:36 -06:00
calebboyd c76edb7c0b chore: expose build on compiler 2018-12-31 08:51:53 -06:00
calebboyd 09d7093295 fix: ignore parsing errors in node_modules 2018-12-21 19:58:34 -06:00
calebboyd 3a091f45bd refactor(fs-patches): don't destroy arguments for non bundled files 2018-12-15 16:38:32 -06:00
bruce-one 9575c0811b refactor: avoid checking DEBUG every log, if set. (#562) 2018-12-05 19:51:04 -06:00
calebboyd ab6b540e23 fix: filter parser warnings 2018-11-29 09:47:35 -06:00
calebboyd dcea374922 fix: input detection and validity
closes #556, #548, #547, #533
2018-11-28 20:25:59 -06:00
calebboyd b3b6c6b936 chore: use npm i 2018-11-21 21:42:22 -06:00
calebboyd 058c34ee8a chore: update node-cli patches for 10-10.9 2018-11-21 18:31:06 -06:00
calebboyd 9750c70873 chore: update circle-ci caching 2018-11-21 10:58:03 -06:00
calebboyd 7bc3a83667 fix: node 10 cli args 2018-11-20 14:35:35 -06:00
calebboyd aa93ab6516 chore: update resolve-dependencies 2018-11-15 14:27:36 -06:00
calebboyd c681a62566 chore: log json error response 2018-10-25 10:06:05 -05:00
calebboyd 2ea0f897d5 chore: bump version 2018-10-23 10:23:34 -05:00
António Nuno Monteiro cfa3036694 feat: invalidate binary cache when snapshot file changes (#542) 2018-10-23 09:31:17 -05:00
calebboyd c3ba3ec872 chore: bump version 2018-10-21 12:25:35 -05:00
António Nuno Monteiro 142338ede6 fix: stringify compiler.entrypoint in shims (#541) 2018-10-21 12:00:06 -05:00
calebboyd e3f1d6fc74 fix: missing cwd on resolve 2018-10-20 11:31:05 -05:00
calebboyd 0db8122f8f fix: handle configure script in node 10.11+ 2018-10-20 11:23:12 -05:00
António Nuno Monteiro 129c2f86a0 fix: include snapshot in patches (#539) 2018-10-20 11:16:41 -05:00
calebboyd db766e2832 fix: use vcBuild and make based on current platform 2018-10-20 10:33:39 -05:00
calebboyd e6a416cc4f fix: add internalModuleReadJSON patch 2018-10-17 21:56:37 -05:00
calebboyd 35f4811826 fix: remove spread operator 2018-10-09 14:07:32 -05:00
calebboyd 891ac0ab2a chore: badges 2018-10-04 08:20:00 -05:00
calebboyd 18bbf24456 chore: bump version 2018-10-04 08:20:00 -05:00
calebboyd 689160e833 fix: handle empty content when adding an empty file 2018-10-04 08:20:00 -05:00
calebboyd bd3ae2e80a chore: disable stdin in docker build 2018-10-04 08:20:00 -05:00
calebboyd 0447d8004c chore: append build error to log file 2018-10-04 08:19:59 -05:00
Jon Tore Hafstad 11be5a2573 chore(docs): resource should always we a glob string (#528)
To avoid confusion regarding the resource glob string, all examples i the readme should be in strings.
2018-10-04 08:19:03 -05:00
calebboyd 664dc6a11b chore: update arm version generator 2018-09-26 09:57:08 -05:00
calebboyd 4a7bfd99b6 chore: fix platform check 2018-09-25 11:48:39 -05:00
calebboyd a3af96eae8 chore: update travis auth 2018-09-25 11:36:09 -05:00
37 changed files with 1647 additions and 864 deletions
-8
View File
@@ -1,8 +0,0 @@
#!/usr/bin/env bash
export NVS_HOME="$HOME/.nvs"
. "$NVS_HOME/nvs.sh" install && nvs link lts && nvs use default
set -e -x
yarn run asset-compile
-28
View File
@@ -1,28 +0,0 @@
version: 2
jobs:
build:
macos:
xcode: "9.0"
steps:
- checkout
- restore_cache:
keys:
- package-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
- package- # Fallback for the latest package.json cache
- run:
name: setup
command: .circleci/setup.sh
- save_cache:
key: package-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
paths:
- node_modules
- run:
name: build
command: .circleci/build.sh
workflows:
version: 2
test:
jobs:
- build
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -e
echo "setting up node to use latest lts"
export NVS_HOME="$HOME/.nvs"
git clone --depth=1 https://github.com/jasongin/nvs "$NVS_HOME"
. "$NVS_HOME/nvs.sh" install
nvs add lts && nvs link lts nvs use default
npm install -g yarn
yarn
+1 -2
View File
@@ -4,6 +4,5 @@ end_of_line = lf
charset = utf-8
indent_style = space
indent_size = 2
#https://github.com/Microsoft/vscode/issues/23983#event-1087502108
#trim_trailing_whitespace = true
trim_trailing_whitespace = true
insert_final_newline = true
+1
View File
@@ -1,6 +1,7 @@
.DS_Store
temp/
coverage
nexe
node_modules
*.log
*.exe
-14
View File
@@ -1,14 +0,0 @@
language: node_js
node_js:
- '6'
before_install:
- sudo apt-get -qq update
- sudo apt-get install -y g++-multilib
node_js:
- '8'
script: npm run asset-compile
env:
global:
- secure: LyC6djYqPfPKz5gHILES9JmSRxQ+ntKOcoaZNM5WID0+gogUoXZBhcmmtvq9RnMpFZN3pYujY6LB5iy7QS2seGjg+feyewNKI30yIK6N9ulJyZZKmrJVDdRx4wvl8YVTkttjcJFkanDGa6zRvFfFDKx/iIdcWLEpnqR/WO1ppf8=
notifications:
email: false
+1 -1
View File
@@ -1,3 +1,3 @@
{
"recommendations": [ "eg2.tslint" ]
"recommendations": [ "ms-vscode.vscode-typescript-tslint-plugin" ]
}
+20 -14
View File
@@ -1,11 +1,10 @@
<p align="center"><img src="https://cloud.githubusercontent.com/assets/2391349/23598327/a17bb68a-01ee-11e7-8f55-88a5fc96e997.png" /></p>
<p align="center">
<a href="https://circleci.com/gh/nexe/nexe"><img src="https://img.shields.io/circleci/project/nexe/nexe.svg" alt="Build Status"></a>
<a href="https://dev.azure.com/nexe-ci/Nexe/_build?definitionId=1"><img src="https://img.shields.io/azure-devops/build/nexe-ci/nexe/1/master.svg" alt="Build Status"></a>
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/dt/nexe.svg" alt="Downloads"></a>
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/v/nexe.svg" alt="Version"></a>
<a href="https://www.npmjs.com/package/nexe"><img src="https://img.shields.io/npm/l/nexe.svg" alt="License"></a>
<a href="https://gitter.im/nexe/nexe?utm_source=badge&utm_medium=badge"><img src="https://badges.gitter.im/nexe/nexe.svg"></a>
</p>
<p align="center"><code>npm i nexe -g</code></p>
@@ -40,8 +39,7 @@ For more CLI options see: `nexe --help`
### Examples
- `nexe server.js -r public/**/*.html`
- `nexe my-bundle.js --no-bundle -o app.exe`
- `nexe server.js -r "public/**/*.html"`
- `nexe --build`
- `nexe -t x86-8.0.0`
@@ -88,14 +86,14 @@ compile({
- Output executable file path
- default: same as `name` with an OS specific extension.
- #### `target: string | object`
- An object or string describing platform-arch-version. e.g. `'windows-ia32-6.10.3'`
- An object or string describing platform-arch-version. e.g. `'windows-ia32-10.13.0'`
- each segment is optional, and will be merged with the current environment
- Examples: ([full list](https://github.com/nexe/nexe/releases))
- `'win32-x86-8.6.0`
- `'win32-x86-10.13.0`
- `{ platform: 'alpine' }`
- `darwin-8.6.0`
- `darwin-10.13.0`
- `linux-x64`
- `macos-8.4.0`
- `macos-10.13.0`
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.
@@ -113,8 +111,15 @@ compile({
- #### `cwd: string`
- Directory nexe will operate on as though it is the cwd
- default: process.cwd()
- #### `mangle: boolean`
- If set to false, nexe will not include the virtual filesystem (your application and resources) on the output.
- This will cause the output to error as an "Invalid Binary" unless a userland patch alters the contents of lib/_third_party_main.js in the nodejs source.
- default: true
- #### `build: boolean`
- Build node from source, passing this flag tells nexe to download and build from source. Subsequently using this flag will cause nexe to use the previously built binary. To rebuild, first add [`--clean`](#clean-boolean)
- #### `asset: string`
- Provide a pre-built nexe binary asset, this can either be an http or https URL or a file path.
- default: `null`
- #### `python: string`
- On Linux this is the path pointing to your python2 executable
- On Windows this is the directory where `python` can be accessed
@@ -128,10 +133,11 @@ compile({
- Example: `['--with-dtrace', '--dest-cpu=x64']`
- default: `[]`
- #### `make: string[]`
- Array of arguments for the node build make step, on windows this step recieves options for vcBuild.bat
- default: `[]` or `['nosign', 'release']` for non windows systems
- Array of arguments for the node build make step
- default: `[]`
- #### `vcBuild: string[]`
- Alias for `make` option
- Options for windows build
- default: `['nosign', 'release']`
- #### `snapshot: string`
- path to a file to be used as the warmup snapshot for the build
- default: `null`
@@ -142,12 +148,12 @@ compile({
- #### `temp: string`
- Path to use for storing nexe's build files
- Override in the env with `NEXE_TEMP`
- default: `./.nexe` in the cwd
- 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:
- Example (keys may vary depending on the version. Reference the file linked above):
```javascript
{
CompanyName: "ACME Corp",
@@ -165,7 +171,7 @@ compile({
variable will still be processed. NODE_OPTIONS support can be disabled with the `--without-node-options` configure flag.
- default: `false`
- #### `fakeArgv: boolean`
- fake the entry point file name (`process.argv[1]`). If nexe was used with stdin this will be `'[stdin]'`.
- fake the entry point file name (`process.argv[1]`). If nexe was used with stdin this will be `'[stdin]'`.
- #### `ghToken: string`
- Provide a Github Token for accessing nexe releases
- This is usually needed in CI environments
-22
View File
@@ -1,22 +0,0 @@
image:
- Visual Studio 2017
- Visual Studio 2015
environment:
CIRCLE_TOKEN:
secure: TQKcepXmTzCNt4+tUBtYpwEQkdSPwFr9A8ugtoMhNbKu/SW1WM1EpjEu6kYXxpQ4
TRAVIS_TOKEN:
secure: tR7HXYv3x97q90uvPrjUxKZh7R1+uqKct0HdEcqTbsg=
GITHUB_TOKEN:
secure: 3wuEGJsppSKFvdwhp3jprUA9Zkc3WINlc/9oFXjhAR6J05lYMXP7Fl7lXTKpkGZx
install:
- ps: Install-Product node 8
- curl -L -o nasm.exe https://www.nasm.us/pub/nasm/releasebuilds/2.13.03/win64/nasm-2.13.03-installer-x64.exe
- start /wait nasm.exe /S
- ps: $env:path="C:\Program Files\nasm;$($env:path)"
- npm i
build: off
test: off
build_script:
- npm run asset-compile
artifacts:
- path: $(HOMEPATH)\.nexe\**\node.exe
+41
View File
@@ -0,0 +1,41 @@
jobs:
- job: BaseBuild
timeoutInMinutes: 0
strategy:
matrix:
linux:
imageName: 'ubuntu-16.04'
mac:
imageName: 'macos-10.13'
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: '10.x'
- script: sudo apt-get install -y libc6-dev-i386 gcc-multilib g++-multilib
condition: eq(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: startsWith(variables['Agent.JobName'], 'windows_2015')
displayName: Install Windows Build Dependencies
- script: choco install nasm -fy
displayName: Install Windows Assembler (NASM)
condition: startsWith(variables['Agent.JobName'], 'windows')
- script: npm install && npm run ci:build
displayName: Build Nexe
env:
GITHUB_TOKEN: $(PersonalGithubToken)
NEXE_VERBOSE: $(NEXE_VERBOSE)
+14 -1
View File
@@ -10,7 +10,20 @@ if (require.main === module) {
process.stderr.write(showHelp ? options.help : options.version + eol)
} else {
const nexe = require('./lib/nexe')
nexe.compile(argv).catch(() => {
nexe.compile(argv).catch((error) => {
const NexeError = require('./lib/compiler').NexeError
const chalk = require('chalk')
const isSilent = Boolean(argv.silent === true || argv.loglevel === 'silent')
if (!isSilent) {
if (error instanceof NexeError) {
process.stderr.write(eol + chalk.red('Error: ') + error.message + eol
+ eol + 'See nexe -h for usage..' + eol + eol
)
} else {
process.stderr.write(error.stack + eol)
}
}
process.exit(1)
})
}
+907 -340
View File
File diff suppressed because it is too large Load Diff
+25 -24
View File
@@ -2,14 +2,14 @@
"name": "nexe",
"description": "Create a single executable out of your Node.js application",
"license": "MIT",
"version": "3.0.0-beta.0",
"version": "3.2.1",
"contributors": [
"Craig Condon <craig.j.condon@gmail.com> (http://crcn.io)",
"Jared Allard <jaredallard@outlook.com>",
"Caleb Boyd <caleb.boyd@hotmail.com>"
],
"scripts": {
"asset-compile": "ts-node tasks/asset-compile",
"ci:build": "ts-node tasks/build",
"test": "mocha",
"lint": "tslint \"{src,plugins,tasks}/**/*.ts\" --fix",
"prepare": "npm run lint && npm run build && npm test",
@@ -36,40 +36,41 @@
"@calebboyd/semaphore": "^1.3.1",
"app-builder": "^5.2.0",
"caw": "^2.0.1",
"chalk": "^2.4.1",
"cherow": "^1.6.8",
"chalk": "^2.4.2",
"cherow": "1.6.9",
"download": "^7.1.0",
"globby": "^8.0.1",
"got": "^9.2.2",
"globby": "^9.2.0",
"got": "^9.6.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"multistream": "^2.1.1",
"ora": "^3.0.0",
"pify": "^4.0.0",
"resolve-dependencies": "^2.1.1",
"rimraf": "^2.6.2"
"ora": "^3.4.0",
"pify": "^4.0.1",
"resolve-dependencies": "^2.2.3",
"rimraf": "^2.6.3"
},
"devDependencies": {
"@types/chai": "^4.1.5",
"@types/chai": "^4.1.7",
"@types/download": "^6.2.4",
"@types/execa": "^0.9.0",
"@types/globby": "^8.0.0",
"@types/got": "^8.3.4",
"@types/globby": "^9.1.0",
"@types/got": "^9.4.4",
"@types/minimist": "^1.2.0",
"@types/mkdirp": "^0.5.2",
"@types/mocha": "^5.2.5",
"@types/mocha": "^5.2.6",
"@types/multistream": "^2.1.1",
"@types/ora": "^1.3.4",
"@types/ora": "^3.2.0",
"@types/pify": "3.0.2",
"@types/rimraf": "2.0.2",
"@types/semver": "^5.5.0",
"chai": "^4.1.2",
"@types/semver": "^6.0.0",
"chai": "^4.2.0",
"execa": "^1.0.0",
"mocha": "^5.2.0",
"prettier": "^1.14.3",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.15.0",
"tslint-plugin-prettier": "^2.0.0",
"typescript": "^3.0.3"
"mocha": "^6.1.4",
"prettier": "^1.17.1",
"ts-node": "^8.2.0",
"tslint": "^5.16.0",
"tslint-config-prettier": "^1.18.0",
"tslint-plugin-prettier": "^2.0.1",
"typescript": "^3.4.5"
}
}
+111 -52
View File
@@ -1,15 +1,20 @@
import { delimiter, dirname, normalize, join } from 'path'
import { delimiter, resolve, normalize, join } from 'path'
import { Buffer } from 'buffer'
import { createReadStream } from 'fs'
import { Readable, Stream } from 'stream'
import { createReadStream, ReadStream } from 'fs'
import { spawn } from 'child_process'
import { Logger, LogStep } from './logger'
import { readFileAsync, writeFileAsync, pathExistsAsync, dequote, isWindows, bound } from './util'
import {
readFileAsync,
writeFileAsync,
pathExistsAsync,
statAsync,
dequote,
isWindows,
bound,
semverGt
} from './util'
import { NexeOptions, version } from './options'
import { NexeTarget } from './target'
import download = require('download')
import { getLatestGitRelease } from './releases'
import { IncomingMessage } from 'http'
import combineStreams = require('multistream')
import { Bundle, toStream } from './fs/bundle'
@@ -17,6 +22,8 @@ const isBsd = Boolean(~process.platform.indexOf('bsd'))
const make = isWindows ? 'vcbuild.bat' : isBsd ? 'gmake' : 'make'
const configure = isWindows ? 'configure' : './configure'
type StringReplacer = (match: string) => string
export interface NexeFile {
filename: string
absPath: string
@@ -25,28 +32,90 @@ export interface NexeFile {
export { NexeOptions }
export class NexeError extends Error {
constructor(m: string) {
super(m)
Object.setPrototypeOf(this, NexeError.prototype)
}
}
export class NexeCompiler {
/**
* Epoch of when compilation started
*/
private start = Date.now()
private env = { ...process.env }
private bundle: Bundle
private compileStep: LogStep | undefined
public log = new Logger(this.options.loglevel)
/**
* Copy of process.env
*/
public env = { ...process.env }
/**
* Virtual FileSystem
*/
public bundle: Bundle
/**
* Root directory for the source of the current build
*/
public src: string
/**
* In memory files that are being manipulated by the compiler
*/
public files: NexeFile[] = []
/**
* Standalone pieces of code run before the application entrypoint
*/
public shims: string[] = []
/**
* The last shim (defaults to "require('module').runMain()")
*/
public startup: string = ''
/**
* The main entrypoint filename for your application - eg. node mainFile.js
*/
public entrypoint: string | undefined
public bundledInput?: string
/**
* Not used
*/
public targets: NexeTarget[]
/**
* Current target of the compiler
*/
public target: NexeTarget
/**
* Output filename (-o myapp.exe)
*/
public output = this.options.output
private nodeSrcBinPath: string
/**
* Flag to indicate whether or notstdin was used for input
*/
public stdinUsed = false
/**
* Path to the configure script
*/
public configureScript: string
/**
* The file path of node binary
*/
public nodeSrcBinPath: string
/**
* Remote asset path if available
*/
public remoteAsset: string
constructor(public options: NexeOptions) {
const { python } = (this.options = options)
//SOMEDAY iterate over multiple targets with `--outDir`
this.targets = options.targets as NexeTarget[]
this.target = this.targets[0]
if (options.asset && options.asset.startsWith('http')) {
this.remoteAsset = options.asset
} else {
this.remoteAsset =
'https://github.com/nexe/nexe/releases/download/v3.0.0/' + this.target.toString()
}
this.src = join(this.options.temp, this.target.version)
this.configureScript = configure + (semverGt(this.target.version, '10.10.0') ? '.py' : '')
this.nodeSrcBinPath = isWindows
? join(this.src, 'Release', 'node.exe')
: join(this.src, 'out', 'Release', 'node')
@@ -105,9 +174,9 @@ export class NexeCompiler {
}
@bound
async replaceInFileAsync(file: string, replace: string | RegExp, value: string) {
async replaceInFileAsync(file: string, replace: string | RegExp, value: string | StringReplacer) {
const entry = await this.readFileAsync(file)
entry.contents = entry.contents.replace(replace, value)
entry.contents = entry.contents.replace(replace, value as any)
}
@bound
@@ -116,19 +185,22 @@ export class NexeCompiler {
entry.contents = contents
}
quit() {
quit(error?: any) {
const time = Date.now() - this.start
this.log.write(`Finished in ${time / 1000}s`)
this.log.write(`Finished in ${time / 1000}s`, error ? 'red' : 'green')
return this.log.flush()
}
assertBuild() {
if (!this.options.build) {
throw new Error('This feature is only available with `--build`')
throw new NexeError('This feature is only available with `--build`')
}
}
public getNodeExecutableLocation(target?: NexeTarget) {
if (this.options.asset && !this.options.asset.startsWith('http')) {
return resolve(this.options.cwd, this.options.asset)
}
if (target) {
return join(this.options.temp, target.toString())
}
@@ -157,7 +229,7 @@ export class NexeCompiler {
}
if (code != 0) {
const error = `${command} ${args.join(' ')} exited with code: ${code}`
reject(new Error(error))
reject(new NexeError(error))
}
resolve()
})
@@ -165,13 +237,16 @@ export class NexeCompiler {
}
private _configureAsync() {
if (isWindows && semverGt(this.target.version, '11.999')) {
return Promise.resolve()
}
return this._runBuildCommandAsync(this.env.PYTHON || 'python', [
configure,
this.configureScript,
...this.options.configure
])
}
private async _buildAsync() {
public async build(): Promise<ReadStream> {
this.compileStep!.log(
`Configuring node build${
this.options.configure.length ? ': ' + this.options.configure : '...'
@@ -186,37 +261,24 @@ export class NexeCompiler {
return createReadStream(this.getNodeExecutableLocation())
}
private async _fetchPrebuiltBinaryAsync(target: NexeTarget) {
let downloadOptions = this.options.downloadOptions
private async _shouldCompileBinaryAsync(
binary: NodeJS.ReadableStream | null,
location: string | undefined
) {
//SOMEDAY combine make/configure/vcBuild/and modified times of included files
const { snapshot, build } = this.options
if (this.options.ghToken) {
downloadOptions = Object.assign({}, downloadOptions)
downloadOptions.headers = Object.assign({}, downloadOptions.headers, {
Authorization: 'token ' + this.options.ghToken
})
if (!binary) {
return true
}
const githubRelease = await getLatestGitRelease(downloadOptions)
const assetName = target.toString()
const asset = githubRelease.assets.find(x => x.name === assetName)
if (!asset) {
throw new Error(`${assetName} not available, create it using the --build flag`)
if (build && snapshot != null && (await pathExistsAsync(snapshot))) {
const snapshotLastModified = (await statAsync(snapshot)).mtimeMs
const binaryLastModified = (await statAsync(location)).mtimeMs
return snapshotLastModified > binaryLastModified
}
const filename = this.getNodeExecutableLocation(target)
await download(asset.browser_download_url, dirname(filename), this.options.downloadOptions).on(
'response',
(res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
this.compileStep!.modify(`Downloading...${((current / total) * 100).toFixed()}%`)
})
}
)
return createReadStream(filename)
return false
}
async compileAsync(target: NexeTarget) {
@@ -224,15 +286,12 @@ export class NexeCompiler {
const build = this.options.build
const location = this.getNodeExecutableLocation(build ? undefined : target)
let binary = (await pathExistsAsync(location)) ? createReadStream(location) : null
if (!build && !binary) {
step.modify('Fetching prebuilt binary')
binary = await this._fetchPrebuiltBinaryAsync(target)
}
if (!binary) {
binary = await this._buildAsync()
if (await this._shouldCompileBinaryAsync(binary, location)) {
binary = await this.build()
step.log('Node binary compiled')
}
return this._assembleDeliverable(binary)
return this._assembleDeliverable(binary!)
}
code() {
+18 -13
View File
@@ -1,15 +1,20 @@
const __nexe_patches = (process.nexe = { patches: {} }).patches
function __nexe_noop_patch(original, ...args) {
return original.call(this, ...args)
}
function __nexe_patch(obj, method, patch) {
const original = obj[method]
if (!original) return
__nexe_patches[method] = patch
obj[method] = function( ...args) {
return __nexe_patches[method].call(this, original, ...args)
if (true) {
const __nexe_patches = (process.nexe = { patches: {} }).patches
const slice = [].slice
const __nexe_noop_patch = function (original) {
const args = slice.call(arguments, 1)
return original.apply(this, args)
}
const __nexe_patch = function (obj, method, patch) {
const original = obj[method]
if (!original) return
__nexe_patches[method] = patch
obj[method] = function() {
const args = [original].concat(slice.call(arguments))
return __nexe_patches[method].apply(this, args)
}
}
__nexe_patch((process).binding('fs'), 'internalModuleReadFile', __nexe_noop_patch)
__nexe_patch((process).binding('fs'), 'internalModuleReadJSON', __nexe_noop_patch)
__nexe_patch((process).binding('fs'), 'internalModuleStat', __nexe_noop_patch)
}
__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)
+1 -1
View File
@@ -42,7 +42,7 @@ export class Bundle {
async addResource(absoluteFileName: string, content?: Buffer | string) {
let length = 0
if (content) {
if (content !== undefined) {
length = Buffer.byteLength(content)
} else {
const stats = await stat(absoluteFileName)
+100 -66
View File
@@ -13,52 +13,46 @@ export interface NexeBinary {
}
let originalFsMethods: any = null
let nexeBinary: NexeBinary | null = null
function restoreFs(fs: any = require('fs')): NexeBinary | false {
if (!nexeBinary) {
return false
}
const source = nexeBinary
Object.assign(fs, originalFsMethods)
nexeBinary = originalFsMethods = null
return source
}
let lazyRestoreFs = () => {}
function shimFs(binary: NexeBinary, fs: any = require('fs')) {
if (originalFsMethods !== null) {
return
}
originalFsMethods = Object.assign({}, fs)
nexeBinary = binary
const { blobPath, resources: manifest } = binary
const { resourceStart, stat } = binary.layout
const directories: { [key: string]: { [key: string]: boolean } } = {},
const { blobPath, resources: manifest } = binary,
{ resourceStart, stat } = binary.layout,
directories: { [key: string]: { [key: string]: boolean } } = {},
notAFile = '!@#$%^&*',
isWin = process.platform.startsWith('win'),
isString = (x: any): x is string => typeof x === 'string' || x instanceof String,
isNotFile = () => false,
isNotDirectory = isNotFile,
isFile = () => true,
noop = () => {},
isDirectory = isFile,
path = require('path')
path = require('path'),
baseDir = path.dirname(process.execPath)
let log = (text: string) => {
if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {
process.stderr.write('[nexe] - ' + text + '\n')
} else {
log = noop
}
let log = (_: string) => true
if ((process.env.DEBUG || '').toLowerCase().includes('nexe:require')) {
log = (text: string) => process.stderr.write('[nexe] - ' + text + '\n')
}
const getKey = process.platform.startsWith('win')
? function getKey(filepath: string): string {
let key = path.resolve(filepath)
if (key.substr(1, 2) === ':\\') {
key = key[0].toUpperCase() + key.substr(1)
}
return key
}
: path.resolve
const winPath = (key: string) => {
if (isWin && key.substr(1, 2) === ':\\') {
key = key[0].toUpperCase() + key.substr(1)
}
return key
}
const getKey = function getKey(filepath: string | Buffer | null): string {
if (Buffer.isBuffer(filepath)) {
filepath = filepath.toString()
}
if (!isString(filepath)) {
return notAFile
}
let key = path.resolve(baseDir, filepath)
return winPath(key)
}
const statTime = function() {
return {
@@ -81,22 +75,21 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
}
}
const createStat = function(directoryExtensions: any, fileExtensions?: any) {
if (!fileExtensions) {
return Object.assign({}, binary.layout.stat, directoryExtensions, { size: 0 }, statTime())
}
const size = directoryExtensions[1]
return Object.assign({}, binary.layout.stat, fileExtensions, { size }, statTime())
const createStat = function(extensions: any) {
return Object.assign(new fs.Stats(), binary.layout.stat, statTime(), extensions)
}
const ownStat = function(filepath: string) {
const ownStat = function(filepath: any) {
setupManifest()
const key = getKey(filepath)
if (directories[key]) {
return createStat({ isDirectory, isFile: isNotFile })
let mode = binary.layout.stat.mode
mode |= fs.constants.S_IFDIR
mode &= ~fs.constants.S_IFREG
return createStat({ mode, size: 0 })
}
if (manifest[key]) {
return createStat(manifest[key], { isFile, isDirectory: isNotDirectory })
return createStat({ size: manifest[key][1] })
}
}
@@ -113,7 +106,7 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
const entry = manifest[filepath]
const absolutePath = getKey(filepath)
const longPath = makeLong(absolutePath)
const normalizedPath = path.normalize(filepath)
const normalizedPath = winPath(path.normalize(filepath))
if (!manifest[absolutePath]) {
manifest[absolutePath] = entry
@@ -139,6 +132,8 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
currentDir = path.dirname(currentDir)
}
})
;(manifest[notAFile] as any) = false
;(directories[notAFile] as any) = false
setupManifest = noop
}
@@ -163,32 +158,26 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
realpathSync: function realpathSync(filepath: any, options: any) {
setupManifest()
const key = getKey(filepath)
if (isString(filepath) && (manifest[filepath] || manifest[key])) {
if (manifest[key]) {
return filepath
}
return originalFsMethods.realpathSync.call(fs, filepath, options)
},
readdir: function readdir(filepath: string | Buffer, options: any, callback: any) {
setupManifest()
filepath = filepath.toString()
if ('function' === typeof options) {
callback = options
options = { encoding: 'utf8' }
}
const dir = directories[getKey(filepath)]
if (dir) {
process.nextTick(() => {
//todo merge with original?
callback(null, Object.keys(dir))
})
if ('function' === typeof options) {
callback = options
options = { encoding: 'utf8' }
}
process.nextTick(() => callback(null, Object.keys(dir)))
} else {
return originalFsMethods.readdir.apply(fs, arguments)
}
},
readdirSync: function readdirSync(filepath: string | Buffer, options: any) {
setupManifest()
filepath = filepath.toString()
const dir = directories[getKey(filepath)]
if (dir) {
return Object.keys(dir)
@@ -198,8 +187,8 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
readFile: function readFile(filepath: any, options: any, callback: any) {
setupManifest()
const entry = manifest[filepath] || manifest[getKey(filepath)]
if (!entry || !isString(filepath)) {
const entry = manifest[getKey(filepath)]
if (!entry) {
return originalFsMethods.readFile.apply(fs, arguments)
}
const [offset, length] = entry
@@ -230,8 +219,8 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
},
createReadStream: function createReadStream(filepath: any, options: any) {
setupManifest()
const entry = manifest[filepath] || manifest[getKey(filepath)]
if (!entry || !isString(filepath)) {
const entry = manifest[getKey(filepath)]
if (!entry) {
return originalFsMethods.createReadStream.apply(fs, arguments)
}
const [offset, length] = entry
@@ -248,9 +237,8 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
},
readFileSync: function readFileSync(filepath: any, options: any) {
setupManifest()
const entry = manifest[filepath] || manifest[getKey(filepath)]
if (!entry || !isString(filepath)) {
const entry = manifest[getKey(filepath)]
if (!entry) {
return originalFsMethods.readFileSync.apply(fs, arguments)
}
const [offset, length] = entry
@@ -263,14 +251,14 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
return encoding ? result.toString(encoding) : result
},
statSync: function statSync(filepath: string | Buffer) {
const stat = isString(filepath) && ownStat(filepath)
const stat = ownStat(filepath)
if (stat) {
return stat
}
return originalFsMethods.statSync.apply(fs, arguments)
},
stat: function stat(filepath: string | Buffer, callback: any) {
const stat = isString(filepath) && ownStat(filepath)
const stat = ownStat(filepath)
if (stat) {
process.nextTick(() => {
callback(null, stat)
@@ -301,6 +289,7 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
log('read (miss) ' + filepath)
return original.call(this, ...args)
}
patches.internalModuleReadJSON = patches.internalModuleReadFile
patches.internalModuleStat = function(this: any, original: any, ...args: any[]) {
setupManifest()
const [filepath] = args
@@ -333,8 +322,53 @@ function shimFs(binary: NexeBinary, fs: any = require('fs')) {
process.nextTick(() => cb(exists))
}
}
if (typeof fs.copyFile === 'function') {
nfs.copyFile = function(filepath: string, dest: string, flags: number, callback: Function) {
setupManifest()
const entry = manifest[getKey(filepath)]
if (!entry) {
return originalFsMethods.copyFile.apply(fs, arguments)
}
if (typeof flags === 'function') {
callback = flags
flags = 0
}
nfs.readFile(filepath, (err: any, buffer: any) => {
if (err) {
return callback(err)
}
originalFsMethods.writeFile(dest, buffer, (err: any) => {
if (err) {
return callback(err)
}
callback(null)
})
})
}
nfs.copyFileSync = function(filepath: string, dest: string) {
setupManifest()
const entry = manifest[getKey(filepath)]
if (!entry) {
return originalFsMethods.copyFileSync.apply(fs, arguments)
}
return originalFsMethods.writeFileSync(dest, nfs.readFileSync(filepath))
}
}
Object.assign(fs, nfs)
lazyRestoreFs = () => {
Object.keys(nfs).forEach(key => {
fs[key] = originalFsMethods[key]
})
lazyRestoreFs = () => {}
}
return true
}
function restoreFs() {
lazyRestoreFs()
}
export { shimFs, restoreFs }
+2 -2
View File
@@ -1,5 +1,5 @@
import colors = require('chalk')
import ora = require('ora')
import colors from 'chalk'
import ora from 'ora'
const frameLength = 120
+17 -19
View File
@@ -1,6 +1,6 @@
import { EOL } from 'os'
import { compose } from 'app-builder'
import { NexeCompiler } from './compiler'
import { NexeCompiler, NexeError } from './compiler'
import { normalizeOptions, NexeOptions, NexePatch } from './options'
import resource from './steps/resource'
import clean from './steps/clean'
@@ -15,31 +15,29 @@ async function compile(
compilerOptions?: Partial<NexeOptions>,
callback?: (err: Error | null) => void
) {
const options = normalizeOptions(compilerOptions)
const compiler = new NexeCompiler(options)
let error = null,
options: NexeOptions | null = null,
compiler: NexeCompiler | null = null
const nexe = compose(
clean,
resource,
cli,
bundle,
shim,
options.build ? [download, artifacts, ...patches, ...(options.patches as NexePatch[])] : [],
options.plugins as NexePatch[]
)
let error = null
try {
await nexe(compiler)
options = normalizeOptions(compilerOptions)
compiler = new NexeCompiler(options)
await compose(
clean,
resource,
cli,
bundle,
shim,
download,
options.build ? [artifacts, ...patches, ...(options.patches as NexePatch[])] : [],
options.plugins as NexePatch[]
)(compiler)
} catch (e) {
error = e
}
if (error) {
if (compiler.options.loglevel !== 'silent' && error) {
process.stderr.write(EOL + error.message + EOL)
}
compiler.quit()
compiler && compiler.quit(error)
if (callback) return callback(error)
return Promise.reject(error)
}
+42 -34
View File
@@ -1,6 +1,6 @@
import * as parseArgv from 'minimist'
import { NexeCompiler } from './compiler'
import { isWindows } from './util'
import { NexeCompiler, NexeError } from './compiler'
import { isWindows, STDIN_FLAG } from './util'
import { basename, extname, join, isAbsolute, resolve } from 'path'
import { getTarget, NexeTarget } from './target'
import { EOL, homedir } from 'os'
@@ -21,7 +21,9 @@ export interface NexeOptions {
output: string
targets: (string | NexeTarget)[]
name: string
asset: string
cwd: string
fs: boolean | string[]
flags: string[]
configure: string[]
vcBuild: string[]
@@ -35,11 +37,6 @@ export interface NexeOptions {
patches: (string | NexePatch)[]
plugins: (string | NexePatch)[]
native: any
/**
* @deprecated
* Use mangle: false instead
*/
empty: boolean
mangle: boolean
ghToken: string
sourceUrl?: string
@@ -53,7 +50,6 @@ export interface NexeOptions {
ico?: string
debugBundle?: boolean
warmup?: string
compress?: boolean
clean?: boolean
/**
* Api Only
@@ -64,12 +60,13 @@ export interface NexeOptions {
const defaults = {
flags: [],
cwd: process.cwd(),
fs: true,
configure: [],
mangle: true,
make: [],
targets: [],
vcBuild: isWindows ? ['nosign', 'release'] : [],
enableNodeCli: false,
compress: false,
build: false,
bundle: true,
patches: [],
@@ -104,6 +101,7 @@ ${c.bold('nexe <entry-file> [options]')}
-t --target -- node version description
-n --name -- main app module name
-r --resource -- *embed files (glob) within the binary
-a --asset -- alternate asset path, file or url pointing to a base (nexe) binary
--plugin -- extend nexe runtime behavior
${c.underline.bold('Building from source:')}
@@ -113,7 +111,8 @@ ${c.bold('nexe <entry-file> [options]')}
-f --flag -- *v8 flags to include during compilation
-c --configure -- *arguments to the configure step
-m --make -- *arguments to the make/build step
--no-mangle -- used when generating base binaries, or when pathing _third_party_main manually.
--patch -- module with middleware default export for adding a build patch
--no-mangle -- used when generating base binaries, or when patching _third_party_main manually.
--snapshot -- path to a warmup snapshot
--ico -- file name for alternate icon file (windows)
--rc-* -- populate rc file options (windows)
@@ -165,7 +164,7 @@ function extractLogLevel(options: NexeOptions) {
}
function isName(name: string) {
return name && name !== 'index'
return name && name !== 'index' && name !== STDIN_FLAG
}
function extractName(options: NexeOptions) {
@@ -194,11 +193,16 @@ function isEntryFile(filename?: string): filename is string {
return Boolean(filename && !isAbsolute(filename))
}
export function resolveEntry(input: string, cwd: string, maybeEntry?: string) {
if (input === '-' || maybeEntry === '-') {
return ''
}
export function resolveEntry(
input: string,
cwd: string,
maybeEntry: string | undefined,
bundle: boolean | string
) {
let result = null
if (input === '-' || maybeEntry === '-') {
return STDIN_FLAG
}
if (input && isAbsolute(input)) {
return input
}
@@ -206,19 +210,19 @@ export function resolveEntry(input: string, cwd: string, maybeEntry?: string) {
const inputPath = padRelative(input)
result = resolveFileNameSync(cwd, inputPath)
}
if (isEntryFile(maybeEntry) && (!result || !result.absPath)) {
const inputPath = padRelative(maybeEntry)
result = resolveFileNameSync(cwd, inputPath)
}
if (!process.stdin.isTTY) {
return ''
if (!process.stdin.isTTY && (!result || !result.absPath) && bundle === defaults.bundle) {
return STDIN_FLAG
}
if (!result || !result.absPath) {
result = resolveFileNameSync(cwd, '.')
}
if (!result.absPath) throw new Error(`Entry file "${input}" not found!`)
if (!result.absPath) {
throw new NexeError(`Entry file "${input || ''}" not found!`)
}
return result.absPath
}
@@ -234,8 +238,8 @@ function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
? resolve(cwd, options.temp)
: process.env.NEXE_TEMP || join(homedir(), '.nexe')
const maybeEntry = isCli(input) ? argv._[argv._.length - 1] : undefined
options.input = resolveEntry(options.input, cwd, maybeEntry)
options.enableStdIn = isCli(input) && options.input === ''
options.input = resolveEntry(options.input, cwd, maybeEntry, options.bundle)
options.enableStdIn = isCli(input) && options.input === STDIN_FLAG
options.name = extractName(options)
options.loglevel = extractLogLevel(options)
options.flags = flatten(opts.flag, options.flags)
@@ -244,7 +248,7 @@ function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
options.targets.push(getTarget())
}
options.ghToken = options.ghToken || process.env.GITHUB_TOKEN || ''
options.make = flatten(options.vcBuild, options.make)
options.make = flatten(isWindows ? options.vcBuild : options.make)
options.configure = flatten(options.configure)
options.resources = flatten(opts.resource, options.resources)
@@ -263,6 +267,21 @@ function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
: `${options.output || options.name}`
options.output = resolve(cwd, options.output)
const requireDefault = (x: string) => {
if (typeof x === 'string') {
return require(x).default
}
return x
}
options.mangle = 'mangle' in opts ? opts.mangle : true
options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault)
options.patches = flatten(opts.patch, options.patches).map(requireDefault)
if ((!options.mangle && !options.bundle) || options.patches.length) {
options.build = true
}
if (options.build) {
const { arch } = options.targets[0] as NexeTarget
if (isWindows) {
@@ -272,17 +291,6 @@ function normalizeOptions(input?: Partial<NexeOptions>): NexeOptions {
}
}
const requireDefault = (x: string) => {
if (typeof x === 'string') {
return require(x).default
}
return x
}
options.mangle = 'mangle' in opts ? opts.mangle : !opts.empty
options.plugins = flatten(opts.plugin, options.plugins).map(requireDefault)
options.patches = flatten(opts.patch, options.patches).map(requireDefault)
Object.keys(alias)
.filter(k => k !== 'rc')
.forEach(x => delete opts[x])
+8 -5
View File
@@ -1,17 +1,20 @@
const fs = require('fs'),
fd = fs.openSync(process.execPath, 'r'),
stat = fs.statSync(process.execPath),
footer = Buffer.alloc(32, 0)
tailSize = Math.min(stat.size, 16000),
tailWindow = Buffer.from(Array(tailSize))
fs.readSync(fd, footer, 0, 32, stat.size - 32)
fs.readSync(fd, tailWindow, 0, tailSize, stat.size - tailSize)
if (!footer.slice(0, 16).equals(Buffer.from('<nexe~~sentinel>'))) {
const footerPosition = tailWindow.indexOf('<nexe~~sentinel>')
if (footerPosition == -1) {
throw 'Invalid Nexe binary'
}
const contentSize = footer.readDoubleLE(16),
const footer = tailWindow.slice(footerPosition, footerPosition + 32),
contentSize = footer.readDoubleLE(16),
resourceSize = footer.readDoubleLE(24),
contentStart = stat.size - 32 - resourceSize - contentSize,
contentStart = stat.size - tailSize + footerPosition - resourceSize - contentSize,
resourceStart = contentStart + contentSize
Object.defineProperty(
+33 -9
View File
@@ -1,20 +1,44 @@
import { NexeCompiler } from '../compiler'
import { semverGt } from '../util'
export default async function disableNodeCli(compiler: NexeCompiler, next: () => Promise<void>) {
if (compiler.options.enableNodeCli) {
return next()
}
const nodeccMarker = 'argv[index][0] =='
if (semverGt(compiler.target.version, '11.6.0')) {
await compiler.replaceInFileAsync(
'src/node.cc',
/(?<!int )ProcessGlobalArgs\(/g,
'0;//ProcessGlobalArgs('
)
} else if (semverGt(compiler.target.version, '10.9')) {
await compiler.replaceInFileAsync('src/node.cc', /(?<!void )ProcessArgv\(/g, '//ProcessArgv(')
} 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(
'src/node.cc',
`${nodeccMarker} '-'`,
// allow NODE_OPTIONS, introduced in 8.0
parseInt(compiler.target.version.split('.')[0]) >= 8
? `(${nodeccMarker} (is_env ? '-' : ']'))`
: `(${nodeccMarker} ']')`
)
await compiler.replaceInFileAsync(
'src/node.cc',
`${nodeccMarker} '-'`,
// allow NODE_OPTIONS, introduced in 8.0
semverGt(compiler.target.version, '7.99')
? `(${nodeccMarker} (is_env ? '-' : ']'))`
: `(${nodeccMarker} ']')`
)
}
return next()
}
+2 -1
View File
@@ -5,7 +5,8 @@ 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]
const patches = [gyp, bootNexe, buildFixes, cli, flags, ico, rc, snapshot]
export default patches
+15 -7
View File
@@ -1,18 +1,26 @@
import { resolve } from 'path'
import { NexeCompiler } from '../compiler'
import { semverGt } from '../util'
export default async function snapshot(compiler: NexeCompiler, next: () => Promise<void>) {
const snapshotFile = compiler.options.snapshot
const warmupScript = compiler.options.warmup
export default async function(compiler: NexeCompiler, next: () => Promise<void>) {
const { snapshot, warmup, cwd } = compiler.options
if (!snapshotFile) {
if (!snapshot) {
return next()
}
const variablePrefix = semverGt(compiler.target.version, '11.0.0') ? 'v8_' : ''
await compiler.replaceInFileAsync(
'configure',
compiler.configureScript,
'def configure_v8(o):',
`def configure_v8(o):\n o['variables']['embed_script'] = '${snapshotFile}'\n o['variables']['warmup_script'] = '${warmupScript ||
snapshotFile}'`
`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()
+1 -11
View File
@@ -1,16 +1,6 @@
import { NexeCompiler } from '../compiler'
import { parse } from 'cherow'
function semverGt(version: string, operand: string) {
const [cMajor, cMinor, cPatch] = version.split('.').map(Number)
const [major, minor, patch] = operand.split('.').map(Number)
return (
cMajor > major ||
(cMajor === major && cMinor > minor) ||
(cMajor === major && cMinor === minor && cPatch > patch)
)
}
import { semverGt } from '../util'
function walkSome(node: any, visit: Function) {
if (!node || typeof node.type !== 'string' || node._visited) {
+3 -3
View File
@@ -25,8 +25,8 @@ async function getJson<T>(url: string, options?: any) {
}
function isBuildableVersion(version: string) {
const major = +version.split('.')[0]
return !~versionsToSkip.indexOf(major) || version === '4.8.4'
const major = Number(version.split('.')[0])
return !versionsToSkip.includes(major) || version === '4.8.4'
}
export function getLatestGitRelease(options?: any) {
@@ -50,7 +50,7 @@ export async function getUnBuiltReleases(options?: any) {
platforms.forEach(platform => {
architectures.forEach(arch => {
if (arch === 'x86' && platform === 'mac') return
if (arch === 'arm7l' && platform !== 'linux') return
if (arch.includes('arm')) return
versions.push(getTarget({ platform, arch, version }))
})
})
+60 -6
View File
@@ -1,20 +1,74 @@
import { NexeCompiler } from '../compiler'
import { resolve } from 'path'
import { NexeCompiler, NexeError } from '../compiler'
import { resolve, relative } from 'path'
import { each } from '@calebboyd/semaphore'
import resolveFiles from 'resolve-dependencies'
import resolveFiles, { resolveFileNameSync } from 'resolve-dependencies'
import { dequote, STDIN_FLAG } from '../util'
import { Readable } from 'stream'
function getStdIn(stdin: Readable): Promise<string> {
let out = ''
return new Promise(resolve => {
stdin
.setEncoding('utf8')
.on('readable', () => {
let current
while ((current = stdin.read())) {
out += current
}
})
.on('end', () => resolve(out.trim()))
setTimeout(() => {
if (!out.trim()) {
resolve(out.trim())
}
}, 1000)
})
}
export default async function bundle(compiler: NexeCompiler, next: any) {
const { bundle, cwd, input } = compiler.options
const { bundle, cwd, input: inputPath } = compiler.options
let input = inputPath
compiler.entrypoint = './' + relative(cwd, input)
compiler.startup = ';require("module").runMain();'
if (!bundle) {
await compiler.addResource(resolve(cwd, input))
return next()
}
if (!input) {
let code = ''
if (typeof bundle === 'string') {
code = await require(bundle).createBundle(compiler.options)
}
if (input === STDIN_FLAG && (code = code || dequote(await getStdIn(process.stdin)))) {
compiler.stdinUsed = true
compiler.entrypoint = './__nexe_stdin.js'
await compiler.addResource(resolve(cwd, compiler.entrypoint), code)
return next()
}
const { files } = await resolveFiles(input, { cwd, expand: true, loadContent: false })
if (input === STDIN_FLAG) {
const maybeInput = resolveFileNameSync(cwd, '.')
if (!maybeInput || !maybeInput.absPath) {
throw new NexeError('No valid input detected')
}
input = maybeInput.absPath
compiler.entrypoint = './' + relative(cwd, input)
}
const { files, warnings } = await resolveFiles(
input,
...Object.keys(compiler.bundle.index).filter(x => x.endsWith('.js')),
{ cwd, expand: true, loadContent: false }
)
if (
warnings.filter(x => x.startsWith('Error parsing file') && !x.includes('node_modules')).length
) {
throw new NexeError('Parsing Error:\n' + warnings.join('\n'))
}
await each(Object.keys(files), (filename: string) => compiler.addResource(filename), {
concurrency: 10
})
+28 -46
View File
@@ -1,24 +1,9 @@
import { normalize, relative, resolve } from 'path'
import { dirname, normalize, relative, resolve } from 'path'
import { createWriteStream, chmodSync, statSync } from 'fs'
import { dequote } from '../util'
import { Readable } from 'stream'
import { NexeCompiler } from '../compiler'
import { NexeTarget } from '../target'
function getStdIn(stdin: Readable): Promise<string> {
return new Promise(resolve => {
let out = ''
stdin
.setEncoding('utf8')
.on('readable', () => {
let current
while ((current = stdin.read())) {
out += current
}
})
.on('end', () => resolve(out))
})
}
import { STDIN_FLAG } from '../util'
import mkdirp = require('mkdirp')
/**
* The "cli" step detects the appropriate input. If no input options are passed,
@@ -26,51 +11,48 @@ function getStdIn(stdin: Readable): Promise<string> {
* After all the build steps have run, the output (the executable) is written to a file or piped to stdout.
*
* Configuration:
* - compiler.options.input - file path to the input bundle.
* - fallbacks: stdin, package.json#main
* - compiler.options.output - file path to the output executable.
* - fallbacks: stdout, nexe_ + epoch + ext
*
* @param {*} compiler
* @param {*} next
*/
export default async function cli(compiler: NexeCompiler, next: () => Promise<void>) {
const { log } = compiler
let stdInUsed = false
if (!process.stdin.isTTY && compiler.options.enableStdIn) {
stdInUsed = true
compiler.entrypoint = './__nexe_stdin.js'
const code = dequote(await getStdIn(process.stdin))
await compiler.addResource(resolve(compiler.options.cwd, compiler.entrypoint), code)
} else {
compiler.entrypoint = './' + relative(compiler.options.cwd, compiler.options.input)
}
compiler.startup = ';require("module").runMain();'
await next()
const { log } = compiler,
target = compiler.options.targets.shift() as NexeTarget,
deliverable = await compiler.compileAsync(target),
output = normalize(compiler.output!)
const target = compiler.options.targets.shift() as NexeTarget
const deliverable = await compiler.compileAsync(target)
mkdirp.sync(dirname(output))
return new Promise((resolve, reject) => {
return new Promise((res, rej) => {
const step = log.step('Writing result to file')
deliverable
.pipe(createWriteStream(normalize(compiler.output!)))
.on('error', reject)
.pipe(createWriteStream(output))
.on('error', rej)
.once('close', (e: Error) => {
if (e) {
reject(e)
rej(e)
} else if (compiler.output) {
const output = compiler.output
const mode = statSync(output).mode | 0o111
const output = compiler.output,
mode = statSync(output).mode | 0o111,
inputFileLogOutput = relative(
process.cwd(),
resolve(compiler.options.cwd, compiler.entrypoint || compiler.options.input)
),
outputFileLogOutput = relative(process.cwd(), output)
chmodSync(output, mode.toString(8).slice(-3))
const inputFile = relative(process.cwd(), compiler.options.input)
const outputFile = relative(process.cwd(), output)
step.log(
`Entry: '${
stdInUsed ? (compiler.options.mangle ? '[stdin]' : '[none]') : inputFile
}' written to: ${outputFile}`
compiler.stdinUsed
? compiler.options.mangle
? STDIN_FLAG
: '[none]'
: inputFileLogOutput
}' written to: ${outputFileLogOutput}`
)
resolve(compiler.quit())
compiler.quit()
res(output)
}
})
})
+42 -12
View File
@@ -2,7 +2,8 @@ import download = require('download')
import { pathExistsAsync } from '../util'
import { LogStep } from '../logger'
import { IncomingMessage } from 'http'
import { NexeCompiler } from '../compiler'
import { NexeCompiler, NexeError } from '../compiler'
import { dirname } from 'path'
function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options = {}) {
const setText = (p: number) => step.modify(`Downloading Node: ${p.toFixed()}%...`)
@@ -21,27 +22,56 @@ function fetchNodeSourceAsync(dest: string, url: string, step: LogStep, options
.then(() => step.log(`Node source extracted to: ${dest}`))
}
async function fetchPrebuiltBinary(compiler: NexeCompiler, step: any) {
const { target, remoteAsset } = compiler,
filename = compiler.getNodeExecutableLocation(target)
try {
await download(remoteAsset, dirname(filename), compiler.options.downloadOptions).on(
'response',
(res: IncomingMessage) => {
const total = +res.headers['content-length']!
let current = 0
res.on('data', data => {
current += data.length
step!.modify(`Downloading...${((current / total) * 100).toFixed()}%`)
})
}
)
} catch (e) {
if (e.statusCode === 404) {
throw new NexeError(`${remoteAsset} is not available, create it using the --build flag`)
} else {
throw new NexeError('Error downloading prebuilt binary: ' + e)
}
}
}
/**
* Downloads the node source to the configured temporary directory
* @param {*} compiler
* @param {*} next
*/
export default async function downloadNode(compiler: NexeCompiler, next: () => Promise<void>) {
const {
src,
log,
targets: [{ version }]
} = compiler
const { sourceUrl, downloadOptions } = compiler.options
const url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`
const step = log.step(`Downloading Node.js source from: ${url}`)
if (await pathExistsAsync(src)) {
step.log('Source already downloaded')
const { src, log, target } = compiler,
{ version } = target,
{ sourceUrl, downloadOptions, build } = compiler.options,
url = sourceUrl || `https://nodejs.org/dist/v${version}/node-v${version}.tar.gz`,
step = log.step(
`Downloading ${build ? '' : 'pre-built '}Node.js${build ? `source from: ${url}` : ''}`
),
exeLocation = compiler.getNodeExecutableLocation(build ? undefined : target),
downloadExists = await pathExistsAsync(build ? src : exeLocation)
if (downloadExists) {
step.log('Already downloaded...')
return next()
}
if (compiler.options.build) {
if (build) {
await fetchNodeSourceAsync(src, url, step, downloadOptions)
} else {
await fetchPrebuiltBinary(compiler, step)
}
return next()
+1 -1
View File
@@ -1,5 +1,5 @@
import { isDirectoryAsync, each } from '../util'
import * as globs from 'globby'
import globs from 'globby'
import { NexeCompiler } from '../compiler'
export default async function resource(compiler: NexeCompiler, next: () => Promise<any>) {
+8 -2
View File
@@ -6,7 +6,9 @@ export default function(compiler: NexeCompiler, next: () => Promise<void>) {
wrap(
`process.__nexe = ${JSON.stringify(compiler.binaryConfiguration)};\n` +
'{{replace:lib/fs/patch.js}}' +
'\nshimFs(process.__nexe)'
'\nshimFs(process.__nexe)' +
`\n${compiler.options.fs ? '' : 'restoreFs()'}`
//TODO support only restoring specific methods
)
)
compiler.shims.push(
@@ -22,7 +24,11 @@ export default function(compiler: NexeCompiler, next: () => Promise<void>) {
compiler.shims.push(
wrap(`
if (!process.send) {
process.argv.splice(1,0, require.resolve("${compiler.entrypoint}"))
const path = require('path')
const entry = path.resolve(path.dirname(process.execPath),${JSON.stringify(
compiler.entrypoint
)})
process.argv.splice(1,0, entry)
}
`)
)
+17
View File
@@ -4,6 +4,7 @@ import pify = require('pify')
import rimraf = require('rimraf')
const rimrafAsync = pify(rimraf)
export const STDIN_FLAG = '[stdin]'
export async function each<T>(
list: T[] | Promise<T[]>,
@@ -80,10 +81,26 @@ function isDirectoryAsync(path: string) {
.then(x => x.isDirectory())
.catch(falseOnEnoent)
}
/**
* @param version See if this version is greather than the second one
* @param operand Version to compare against
*/
function semverGt(version: string, operand: string) {
const [cMajor, cMinor, cPatch] = version.split('.').map(Number)
let [major, minor, patch] = operand.split('.').map(Number)
if (!minor) minor = 0
if (!patch) patch = 0
return (
cMajor > major ||
(cMajor === major && cMinor > minor) ||
(cMajor === major && cMinor === minor && cPatch > patch)
)
}
export {
dequote,
padRight,
semverGt,
bound,
isWindows,
rimrafAsync,
-99
View File
@@ -1,99 +0,0 @@
import * as nexe from '../lib/nexe'
import { getUnBuiltReleases, getLatestGitRelease } from '../lib/releases'
import * as ci from './ci'
import { runDockerBuild } from './docker'
import { getTarget } from '../lib/target'
import { pathExistsAsync, readFileAsync, execFileAsync } from '../lib/util'
import got = require('got')
const env = process.env,
branchName = env.CIRCLE_BRANCH || env.APPVEYOR_REPO_BRANCH || env.TRAVIS_BRANCH || '',
isScheduled = Boolean(env.APPVEYOR_SCHEDULED_BUILD || env.NEXE_TRIGGERED),
isWindows = Boolean(env.APPVEYOR),
isPullRequest =
Boolean(env.CIRCLE_PR_NUMBER) ||
Boolean(env.APPVEYOR_PULL_REQUEST_NUMBER) ||
Boolean(env.TRAVIS_PULL_REQUEST_BRANCH),
headers = {
Authorization: 'token ' + env.GITHUB_TOKEN,
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
if (require.main === module) {
if (!isPullRequest) {
build().catch(x => {
console.error(x)
process.exit(1)
})
}
}
async function build() {
if (isScheduled) {
const releases = await getUnBuiltReleases({ headers })
if (!releases.length) {
return
}
const windowsBuild = releases.find(x => x.platform === 'windows')
const macBuild = releases.find(x => x.platform === 'mac')
const linuxOrAlpine = releases.find(x => x.platform === 'linux' || x.platform === 'alpine')
if (linuxOrAlpine) {
await ci.triggerDockerBuild(linuxOrAlpine, branchName)
}
if (macBuild) {
await ci.triggerMacBuild(macBuild, branchName)
}
if (windowsBuild) {
await ci.triggerWindowsBuild(windowsBuild)
}
}
if (env.NEXE_VERSION) {
const target = getTarget(env.NEXE_VERSION),
output = isWindows ? './out.exe' : './out',
options = {
empty: true,
build: true,
target,
output
}
console.log('Building: ', target)
const stop = keepalive()
if (['arm7l', 'arm6l', 'arm64', 'alpine'].indexOf(target.platform)) {
await runDockerBuild(target)
} else {
await nexe.compile(options)
}
stop()
if (await pathExistsAsync(output)) {
await assertNexeBinary(output)
const gitRelease = await getLatestGitRelease({ headers })
await got(gitRelease.upload_url.split('{')[0], {
query: { name: target.toString() },
body: await readFileAsync(output),
headers: {
Authorization: 'token ' + env.GITHUB_TOKEN,
'Content-Type': 'application/octet-stream',
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
})
console.log(target + ' uploaded.')
}
}
}
function keepalive() {
const keepalive = setInterval(() => console.log('Building...'), 300 * 1000)
return () => clearInterval(keepalive)
}
function assertNexeBinary(file: string) {
return execFileAsync(file).catch(e => {
if (e && e.stack && e.stack.includes('Invalid Nexe binary')) {
return
}
throw e
})
}
+113
View File
@@ -0,0 +1,113 @@
import * as nexe from '../lib/nexe'
import { getUnBuiltReleases, getLatestGitRelease } from '../lib/releases'
import { runDockerBuild } from './docker'
import { getTarget, targetsEqual, NexeTarget } from '../lib/target'
import { pathExistsAsync, readFileAsync, execFileAsync, semverGt } from '../lib/util'
import got = require('got')
const env = process.env,
isPullRequest = env.BUILD_REASON === 'PullRequest',
isWindows = process.platform === 'win32',
isLinux = process.platform === 'linux',
buildHost = env.AGENT_JOBNAME || (isWindows && 'windows_2017_2015') || '',
isMac = process.platform === 'darwin',
headers = {
Authorization: 'token ' + env.GITHUB_TOKEN,
'User-Agent': 'nexe (https://www.npmjs.com/package/nexe)'
}
if (require.main === module) {
if (!isPullRequest) {
build().catch(x => {
console.error(x)
process.exit(1)
})
}
}
async function build() {
const releases = await getUnBuiltReleases({ headers })
if (!releases.length) {
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
if (env.NEXE_VERSION) target = getTarget(env.NEXE_VERSION)
else if (isWindows) target = windowsBuild
else if (isMac) target = macBuild
else if (isLinux) target = linux
if (buildHost === '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,
output
}
console.log('Building: ' + target + ' on ' + buildHost)
const stop = keepalive()
if (
[/*'arm7l', 'arm6l', 'arm64', */ 'alpine'].includes(target.platform) &&
buildHost === '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.`)
return
}
await got(gitRelease.upload_url.split('{')[0], {
query: { 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() {
const keepalive = setInterval(() => console.log('Building...'), 300 * 1000)
return () => clearInterval(keepalive)
}
function assertNexeBinary(file: string) {
return execFileAsync(file).catch(e => {
if (e && e.stack && e.stack.includes('Invalid Nexe binary')) {
return
}
throw e
})
}
+8 -7
View File
@@ -1,6 +1,5 @@
import { NexeTarget, architectures } from '../lib/target'
import { writeFileAsync, readFileAsync } from '../lib/util'
import { spawn } from 'child_process'
import got = require('got')
import execa = require('execa')
import { appendFileSync } from 'fs'
@@ -10,7 +9,7 @@ function alpine(target: NexeTarget) {
FROM ${target.arch === 'x64' ? '' : 'i386/'}alpine:3.4
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=beta
ENV NEXE_VERSION=latest
WORKDIR /
RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.tar.gz | tar -xz && \
@@ -21,19 +20,20 @@ RUN curl -sSL https://nodejs.org/dist/v\${NODE_VERSION}/node-v\${NODE_VERSION}.t
paxctl -cm /usr/bin/node
RUN rm /nexe_temp/\${NODE_VERSION}/out/Release/node && \
npm install -g nexe@\${NEXE_VERSION} && \
nexe --build --empty --temp /nexe_temp -c="--fully-static" -o out
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) {
return `
FROM hypriot/rpi-node
ENV NEXE_VERSION=beta
ENV NEXE_VERSION=latest
WORKDIR /
RUN yarn global add nexe@\${NEXE_VERSION} && \
nexe --build --empty -o out -t ${target.version}
nexe --build --no-mangle -o out -t ${target.version}
`.trim()
}
@@ -51,7 +51,8 @@ export async function runDockerBuild(target: NexeTarget) {
output.push(await execa.shell(`docker cp nexe:/out out`))
output.push(await execa.shell(`docker rm nexe`))
} catch (e) {
console.log('Error running docker', e)
console.log('Error running docker')
appendFileSync(outFilename, e.message)
} finally {
output.forEach((x: any) => {
appendFileSync(outFilename, x.stderr)
+6 -2
View File
@@ -10,9 +10,13 @@ describe('options', () => {
const options = normalizeOptions()
expect(options.cwd).to.equal(process.cwd())
})
it('should use the main module in a package directory', () => {
it('should use the main module in a package directory (if not in a TTY)', () => {
const options = normalizeOptions()
expect(options.input).to.equal(path.resolve('./index.js'))
if (!process.stdin.isTTY) {
expect(options.input).to.equal('[stdin]')
} else {
expect(options.input).to.equal(path.resolve('./index.js'))
}
})
it('should resolve relative paths for input', () => {
const options = normalizeOptions({ input: 'test/fixture/entry.js' })
+1 -1
View File
@@ -3,7 +3,7 @@
"target":"es5",
"skipLibCheck": true,
"experimentalDecorators": true,
"lib": ["es2017"],
"lib": ["es2018"],
"module": "commonjs",
"outDir": "./lib",
"strict": true