Skip to content

feat: add support for appimage #1399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@ nwbuild({
});
```

### Package mode

Package application as AppImage for Linux:

```js
nwbuild({
mode: "package",
platform: "linux",
appimage: true,
});
```

#### Managed Manifest

You can let `nw-builder` manage your node modules. The `managedManifest` options accepts a `boolean`, `string` or `object` type. It will then remove `devDependencies`, autodetect and download `dependencies` via the relevant `packageManager`. If none is specified, it uses `npm` as default.
Expand Down Expand Up @@ -209,7 +221,7 @@ Options

| Name | Type | Default | Description |
| ---- | ------- | --------- | ----------- |
| mode | `"get" \| "run" \| "build"` | `"build"` | Choose between get, run or build mode |
| mode | `"get" \| "run" \| "build" \| "package"` | `"build"` | Choose between get, run, build or package mode |
| version | `string \| "latest" \| "stable"` | `"latest"` | Runtime version |
| flavor | `"normal" \| "sdk"` | `"normal"` | Runtime flavor |
| platform | `"linux" \| "osx" \| "win"` | | Host platform |
Expand All @@ -228,6 +240,7 @@ Options
| managedManifest | `boolean \| string \| object` | `false` | Managed manifest |
| nodeAddon | `false \| "gyp"` | `false` | Rebuild Node native addons |
| zip | `boolean \| "zip" \| "tar" \| "tgz"` | `false`| If true, "zip", "tar" or "tgz" the `outDir` directory is compressed. |
| appimage | `boolean` | `false` | Package the application as an AppImage for Linux |
| app | `LinuxRc \| WinRc \| OsxRc` | Additional options for each platform. (See below.)

### `app` configuration object
Expand Down Expand Up @@ -388,7 +401,7 @@ nwbuild({

- feat(get): support canary releases
- feat(pkg): add `AppImage` installer
- feat(pkg): add `NSIS` installer
- feat(pkg): add `MSIX` installer
- feat(pkg): add `DMG` installer
- feat(get): add Linux ARM unofficial support
- feat(bld): add source code protection
Expand Down
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"demo:bld:linux": "node ./tests/fixtures/demo.linux.js",
"demo:bld:osx": "node ./tests/fixtures/demo.osx.js",
"demo:bld:win": "node ./tests/fixtures/demo.win.js",
"demo:exe:linux": "./tests/fixtures/out/linux/Demo",
"demo:exe:linux": "./tests/fixtures/out/nw.AppImage",
"demo:exe:osx": "./tests/fixtures/out/osx/Demo.app/Contents/MacOS/Demo",
"demo:exe:win": "./tests/fixtures/out/win/Demo.exe",
"demo:cli": "nwbuild --mode=build --flavor=sdk --glob=false --cacheDir=./node_modules/nw --logLevel=debug --outDir=./tests/fixtures/out/linux --app.name=Demo --app.icon=./tests/fixtures/app/icon.png ./tests/fixtures/app"
Expand All @@ -66,6 +66,7 @@
"vitest": "^3.0.7"
},
"dependencies": {
"appimage": "^0.3.1",
"archiver": "^7.0.1",
"axios": "^1.11.0",
"commander": "^14.0.0",
Expand Down
5 changes: 3 additions & 2 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import util from './util.js';

program
.argument('<string>', 'File path(s) to project')
.option('--mode <string>', 'get, run or build mode', 'build')
.option('--mode <string>', 'get, run, build or package mode', 'build')
.option('--version <string>', 'NW.js version', 'latest')
.option('--flavor <string>', 'NW.js build flavor', 'normal')
.option('--platform <string>', 'NW.js supported platform', util.PLATFORM_KV[process.platform])
Expand All @@ -25,7 +25,8 @@ program
.option('--shaSum <string>', 'Enable/disable shasum', true)
.option('--zip <string>', 'Enable/disable compression', false)
.option('--managedManifest <string>', 'Managed manifest mode', false)
.option('--nodeAddon <boolean>', 'Download NW.js Node headers', false);
.option('--nodeAddon <boolean>', 'Download NW.js Node headers', false)
.option('--appimage <boolean>', 'Package the application as an AppImage for Linux', false);

// Handle unknown --app.* arguments
const unknownArgs = program.parseOptions(process.argv).unknown;
Expand Down
16 changes: 14 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import path from 'node:path';
import bld from './bld.js';
import get from './get/index.js';
import run from './run.js';
import pkg from './pkg/index.js';
import util from './util.js';

/**
* @typedef {object} Options Configuration options
* @property {"get" | "run" | "build"} [mode="build"] Choose between get, run or build mode
* @property {"get" | "run" | "build" | "package"} [mode="build"] Choose between get, run or build package mode
* @property {"latest" | "stable" | string} [version="latest"] Runtime version
* @property {"normal" | "sdk"} [flavor="normal"] Runtime flavor
* @property {"linux" | "osx" | "win"} platform Host platform
Expand All @@ -29,6 +30,7 @@ import util from './util.js';
* @property {boolean | "zip" | "tar" | "tgz"} [zip=false] If true, "zip", "tar" or "tgz" the outDir directory is compressed.
* @property {boolean | string | object} [managedManifest = false] Managed manifest mode
* @property {false | "gyp"} [nodeAddon = false] Rebuild Node native addons
* @property {boolean} [appimage = false] Package the application as an AppImage for Linux
* @property {boolean} [cli=false] If true the CLI is used to parse options. This option is used internally.
*/

Expand Down Expand Up @@ -125,7 +127,7 @@ async function nwbuild(options) {
argv: options.argv,
});
return nwProcess;
} else if (options.mode === 'build') {
} else if (options.mode === 'build' || options.mode === 'package') {
util.log('info', options.logLevel, `Build a NW.js application for ${options.platform} ${options.arch}...`);
await bld({
version: options.version,
Expand All @@ -144,6 +146,16 @@ async function nwbuild(options) {
releaseInfo: releaseInfo,
});
util.log('info', options.logLevel, `Appliction is available at ${path.resolve(options.outDir)}`);

if (options.mode === 'package') {
util.log('info', options.logLevel, `Packages a NW.js application for ${options.platform} ${options.arch}...`);
await pkg({
outDir: options.outDir,
pkgDir: path.dirname(options.outDir),
appimage: options.appimage,
app: options.app,
});
}
}
} catch (error) {
console.error(error);
Expand Down
49 changes: 49 additions & 0 deletions src/pkg/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import path from 'node:path';

import createAppImage from 'appimage';

/**
*
* @param {object} options

Check warning on line 7 in src/pkg/index.js

View workflow job for this annotation

GitHub Actions / tests (macos-15)

Missing JSDoc @param "options" description

Check warning on line 7 in src/pkg/index.js

View workflow job for this annotation

GitHub Actions / tests (ubuntu-24.04)

Missing JSDoc @param "options" description

Check warning on line 7 in src/pkg/index.js

View workflow job for this annotation

GitHub Actions / tests (windows-2025)

Missing JSDoc @param "options" description
* @param {string} options.outDir - Output directory for the built application
* @param {string} options.pkgDir - Output directory for the packaged application
* @param {boolean} options.appimage - Package the application as an AppImage for Linux
* @param {object} options.app - Application specific options
* @param {string} options.app.name - Application name

Check warning on line 12 in src/pkg/index.js

View workflow job for this annotation

GitHub Actions / tests (macos-15)

@param "options.app.name" does not exist on options

Check warning on line 12 in src/pkg/index.js

View workflow job for this annotation

GitHub Actions / tests (ubuntu-24.04)

@param "options.app.name" does not exist on options

Check warning on line 12 in src/pkg/index.js

View workflow job for this annotation

GitHub Actions / tests (windows-2025)

@param "options.app.name" does not exist on options
* @returns {Promise<void>}
*/
export default async function pkg({
outDir,
pkgDir,
appimage = false,
app,
}) {

if (appimage === true) {

await createAppImage({
appName: app.name,
outDir: pkgDir,
appImagePath: './node_modules/nw/appimagetool.AppImage',
srcMap: {
'/AppRun': path.resolve('tests', 'fixtures', 'AppRun'),
'/nw.desktop': path.resolve(outDir, `${app.name}.desktop`),
'/usr/bin/demo/lib': path.resolve(outDir, 'lib'),
'/usr/bin/demo/locales': path.resolve(outDir, 'locales'),
'/usr/bin/demo/package.nw': path.resolve(outDir, 'package.nw'),
'/usr/bin/demo/swiftshader': path.resolve(outDir, 'swiftshader'),
'/usr/bin/demo/chrome_crashpad_handler': path.resolve(outDir, 'chrome_crashpad_handler'),
'/usr/bin/demo/chromedriver': path.resolve(outDir, 'chromedriver'),
'/usr/bin/demo/nw': path.resolve(outDir, app.name),
'/usr/bin/demo/icudtl.dat': path.resolve(outDir, 'icudtl.dat'),
'/usr/bin/demo/minidump_stackwalk': path.resolve(outDir, 'minidump_stackwalk'),
'/usr/bin/demo/nw_100_percent.pak': path.resolve(outDir, 'nw_100_percent.pak'),
'/usr/bin/demo/nw_200_percent.pak': path.resolve(outDir, 'nw_200_percent.pak'),
'/usr/bin/demo/nwjc': path.resolve(outDir, 'nwjc'),
'/usr/bin/demo/resources.pak': path.resolve(outDir, 'resources.pak'),
'/usr/bin/demo/v8_context_snapshot.bin': path.resolve(outDir, 'v8_context_snapshot.bin'),
}
});
}

}
15 changes: 12 additions & 3 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ export const parse = async (options, pkg) => {

options.managedManifest = str2Bool(options.managedManifest) ?? false;
options.nativeAddon = str2Bool(options.nativeAddon) ?? false;
options.appimage = str2Bool(options.appimage) ?? false;

options.app = options.app ?? {};
options.app.name = options.app.name ?? pkg.name;
Expand Down Expand Up @@ -324,10 +325,11 @@ export const validate = async (options, releaseInfo) => {
if (
options.mode !== 'get' &&
options.mode !== 'run' &&
options.mode !== 'build'
) {
options.mode !== 'build' &&
options.mode !== 'package'
) {
throw new Error(
`Unknown mode ${options.mode}. Expected "get", "run" or "build".`,
`Unknown mode ${options.mode}. Expected "get", "run", "build" or "package".`,
);
}
if (typeof releaseInfo === 'undefined') {
Expand Down Expand Up @@ -450,6 +452,13 @@ export const validate = async (options, releaseInfo) => {
}
}

if (typeof options.appimage !== 'boolean') {
throw new Error('Expected options.appimage to be a boolean. Got ' + typeof options.appimage);
}
if (options.appimage === true && (options.platform !== 'linux' || options.mode !== 'package')) {
throw new Error('Expected options.appimage to be true iff platform is `linux` and mode is `package`.');
}

if (options.platform === 'linux') {
if (options.app.name && typeof options.app.name !== 'string') {
throw new Error('Expected options.app.name to be a string. Got ' + options.app.name);
Expand Down
4 changes: 4 additions & 0 deletions tests/fixtures/AppRun
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
HERE="$(dirname "$(readlink -f "${0}")")"
export PATH="${HERE}/usr/bin:${PATH}"
exec nw "$@"
5 changes: 3 additions & 2 deletions tests/fixtures/app/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{
"name": "Demo",
"name": "demo",
"main": "index.html",
"version": "0.0.0",
"product_string": "Demo",
"chromium-args": "--disable-gpu --disable-software-rasterize --use-gl=egl",
"product_string": "demo",
"window": {
"icon": "./icon.png"
}
Expand Down
18 changes: 6 additions & 12 deletions tests/fixtures/demo.linux.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import nwbuild from '../../src/index.js';

await nwbuild({
mode: 'build',
mode: 'package',
flavor: 'sdk',
platform: 'linux',
srcDir: './tests/fixtures/app',
cacheDir: './node_modules/nw',
outDir: './tests/fixtures/out/linux',
glob: false,
appimage: true,
logLevel: 'debug',
app: {
name: 'Demo',
genericName: 'Demo',
name: 'nw',
genericName: 'nw',
noDisplay: false,
comment: 'Tooltip information',
categories: ['Utility'],
/* File path of icon from where it is copied. */
icon: './tests/fixtures/app/icon.png',
hidden: false,
// TODO: test in different Linux desktop environments
// onlyShowIn: [],
// notShowIn: [],
dBusActivatable: true,
// TODO: test in Linux environment
// tryExec: '/path/to/exe?'
exec: './tests/fixtures/out/linux/Demo',
icon: '/usr/bin/demo/package.nw/icon',
}
});

Expand Down
Loading