├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── e2e.yml │ ├── npm.yml │ └── rel.yml ├── .gitignore ├── .npmrc ├── .release-please-manifest.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── eslint.config.js ├── package-lock.json ├── package.json ├── release-please-config.json ├── src ├── bld.js ├── bld │ └── osx.js ├── cli.js ├── get │ ├── decompress.js │ ├── ffmpeg.js │ ├── index.js │ ├── node.js │ ├── nw.js │ ├── request.js │ └── verify.js ├── index.d.ts ├── index.js ├── postinstall.js ├── run.js ├── util.js └── util │ └── osx.arm.versions.json ├── tests ├── fixtures │ ├── app │ │ ├── icon.icns │ │ ├── icon.ico │ │ ├── icon.png │ │ ├── index.html │ │ └── package.json │ ├── demo.linux.js │ ├── demo.osx.js │ └── demo.win.js └── specs │ ├── bld.test.js │ ├── decompress.test.js │ ├── ffmpeg.test.js │ ├── node.test.js │ ├── nw.test.js │ ├── osx.test.js │ ├── request.test.js │ ├── run.test.js │ ├── util.test.js │ └── verify.test.js └── vitest.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # Top-most EditorConfig file 4 | root = true 5 | 6 | # defaults for all files 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | # Markdown files uses two trailing spaces to indicate a
16 | [*.{md,snap}] 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Issue Type 2 | 3 | - [ ] Bug Report 4 | - [ ] Feature Request 5 | - [ ] Other 6 | 7 | ### Current/Missing Behaviour 8 | 9 | ### Expected/Proposed Behaviour 10 | 11 | ### Additional Info 12 | 13 | - Package version: 14 | - Operating System: 15 | - Node version: 16 | - NW.js version: 17 | - Repro link: 18 | - ... 19 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | * 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "." 5 | schedule: 6 | interval: "daily" 7 | versioning-strategy: increase 8 | groups: 9 | npm: 10 | patterns: 11 | - "*" 12 | - package-ecosystem: "github-actions" 13 | directory: ".github/workflows" 14 | schedule: 15 | interval: "daily" 16 | groups: 17 | gha: 18 | patterns: 19 | - "*" 20 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: e2e 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | pull-requests: write 11 | 12 | concurrency: 13 | group: ${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | tests: 18 | strategy: 19 | matrix: 20 | os: 21 | - macos-15 22 | - ubuntu-24.04 23 | - windows-2025 24 | fail-fast: false 25 | runs-on: ${{ matrix.os }} 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4.2.2 29 | - name: Setup Volta 30 | uses: volta-cli/action@v4.2.1 31 | - name: Node.js version 32 | run: node -v 33 | - name: npm version 34 | run: npm -v 35 | - name: Install dependencies 36 | run: npm ci 37 | - name: Check for linting errors 38 | run: npm run lint 39 | - name: Run tests 40 | run: npm run test:cov 41 | - name: Report Coverage 42 | if: always() 43 | uses: davelosert/vitest-coverage-report-action@v2.8.2 44 | -------------------------------------------------------------------------------- /.github/workflows/npm.yml: -------------------------------------------------------------------------------- 1 | name: cd 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: ${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | permissions: 13 | contents: read 14 | pull-requests: write 15 | 16 | jobs: 17 | env: 18 | runs-on: ubuntu-24.04 19 | steps: 20 | - name: Check if NPM_TOKEN is set 21 | run: | 22 | if [ -z "${{ secrets.NPM_TOKEN }}" ]; then 23 | echo "NPM_TOKEN is not set, skipping job." 24 | exit 1 25 | fi 26 | npm: 27 | needs: [ env ] 28 | runs-on: ubuntu-24.04 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v4.2.2 32 | - name: Setup Node 33 | uses: actions/setup-node@v4.4.0 34 | with: 35 | node-version: 23 36 | - name: Install dependencies 37 | run: npm ci 38 | - name: Publish to npm 39 | uses: JS-DevTools/npm-publish@v3.1.1 40 | with: 41 | token: ${{ secrets.NPM_TOKEN }} 42 | -------------------------------------------------------------------------------- /.github/workflows/rel.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | name: release-please 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-24.04 15 | steps: 16 | - name: Release Please 17 | uses: googleapis/release-please-action@v4.2.0 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .idea 4 | coverage 5 | node_modules 6 | 7 | tests/fixtures/cache 8 | tests/fixtures/out 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | nwjs_build_type=sdk 2 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "4.13.14" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/) using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). 7 | 8 | Since `v4.6.0`, we have switched to automated releases and this file does not need to be manually updated. 9 | 10 | ## [4.13.14](https://github.com/nwutils/nw-builder/compare/v4.13.13...v4.13.14) (2025-05-14) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **bld:** disable rebuilding Node addons ([464dca2](https://github.com/nwutils/nw-builder/commit/464dca25759c1ae6960977b1e83bdf03b57d2bb2)) 16 | 17 | 18 | ### Chores 19 | 20 | * **ci:** check for NPM_TOKEN in separate job ([74ca486](https://github.com/nwutils/nw-builder/commit/74ca486a8f8660636e5d17fec9c5ce40e36f9cae)) 21 | * **deps:** bump the npm group across 1 directory with 4 updates ([#1394](https://github.com/nwutils/nw-builder/issues/1394)) ([560dd73](https://github.com/nwutils/nw-builder/commit/560dd738162b10555a68109cf146725f821151eb)) 22 | * **docs:** clarify addon rebuilding disabled ([bb910e1](https://github.com/nwutils/nw-builder/commit/bb910e193759b6551f1681a5b2da084f9c28be27)) 23 | * **docs:** remove mention of rebuilding node addons ([e3ae5b3](https://github.com/nwutils/nw-builder/commit/e3ae5b379e302baa9bbf532d9666ea52dc2639e7)) 24 | 25 | ## [4.13.13](https://github.com/nwutils/nw-builder/compare/v4.13.12...v4.13.13) (2025-05-07) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * **security:** use execFileSync instead of execSync ([48193f0](https://github.com/nwutils/nw-builder/commit/48193f0e14cae4f02a8217df736d056204f446d9)) 31 | 32 | ## [4.13.12](https://github.com/nwutils/nw-builder/compare/v4.13.11...v4.13.12) (2025-05-06) 33 | 34 | 35 | ### Bug Fixes 36 | 37 | * **util:** specify default values in CLI invocation ([3b3799d](https://github.com/nwutils/nw-builder/commit/3b3799d6923c3b2a2c361f26978f35dd35d089ac)) 38 | 39 | 40 | ### Chores 41 | 42 | * **ci:** check if NPM_TOKEN exists as action step ([6b7e64e](https://github.com/nwutils/nw-builder/commit/6b7e64ed100789c314f97cc786ad59fbdff5bca1)) 43 | * **ci:** fail if NPM_TOKEN not set ([84484a1](https://github.com/nwutils/nw-builder/commit/84484a1fcd9e307d6225a4b453f778e3f44fd542)) 44 | * **ci:** move NPM_TOKEN conditional inside action steps ([ea6ea4c](https://github.com/nwutils/nw-builder/commit/ea6ea4c0d63184822a69ec2627a361892a6121d7)) 45 | * **ci:** set continue-on-error to false ([3b9a8a2](https://github.com/nwutils/nw-builder/commit/3b9a8a29030a27fbc4792b367e2c91158b3e60b3)) 46 | * **deps:** bump davelosert/vitest-coverage-report-action from 2.8.1 to 2.8.2 in /.github/workflows in the gha group ([#1388](https://github.com/nwutils/nw-builder/issues/1388)) ([7a51dd4](https://github.com/nwutils/nw-builder/commit/7a51dd4fac0c70b8c23f2a345eb130e9e007dc97)) 47 | * **deps:** bump the gha group across 1 directory with 2 updates ([#1385](https://github.com/nwutils/nw-builder/issues/1385)) ([2a98e27](https://github.com/nwutils/nw-builder/commit/2a98e27868d149323fa2ef39dd1eeaaad77e464f)) 48 | * **deps:** bump the npm group across 1 directory with 6 updates ([#1378](https://github.com/nwutils/nw-builder/issues/1378)) ([5df5c51](https://github.com/nwutils/nw-builder/commit/5df5c51a173d65863544751abda2a1097d9a8225)) 49 | * **deps:** bump the npm group across 1 directory with 8 updates ([#1390](https://github.com/nwutils/nw-builder/issues/1390)) ([a5ce5be](https://github.com/nwutils/nw-builder/commit/a5ce5be72cb7ed9c05837ca1f2e40dfc1b81d3af)) 50 | * **deps:** bump the npm group across 1 directory with 9 updates ([#1386](https://github.com/nwutils/nw-builder/issues/1386)) ([1f55773](https://github.com/nwutils/nw-builder/commit/1f557731367fa3c3189f3f9a2ac74f19fb378c34)) 51 | * **deps:** bump vite from 6.2.2 to 6.2.3 in the npm_and_yarn group ([#1377](https://github.com/nwutils/nw-builder/issues/1377)) ([4f4379d](https://github.com/nwutils/nw-builder/commit/4f4379dd25348cd834df078a970564a114ba5e89)) 52 | * **deps:** bump vite from 6.2.3 to 6.2.4 in the npm_and_yarn group ([#1379](https://github.com/nwutils/nw-builder/issues/1379)) ([e425d56](https://github.com/nwutils/nw-builder/commit/e425d563ae37ec2ca6419763e53508337db57d1a)) 53 | 54 | ## [4.13.11](https://github.com/nwutils/nw-builder/compare/v4.13.10...v4.13.11) (2025-03-19) 55 | 56 | 57 | ### Bug Fixes 58 | 59 | * **util:** use strict boolean check for glob flag ([63c931d](https://github.com/nwutils/nw-builder/commit/63c931dad34c2b5449c74b540924a91d8ed51685)) 60 | 61 | 62 | ### Chores 63 | 64 | * **ci:** add concurrency ([41494f5](https://github.com/nwutils/nw-builder/commit/41494f535a583ba2a69daa651b8d1582726a4ad7)) 65 | * **ci:** do not run npm publish job if NPM_TOKEN is not available ([e21051c](https://github.com/nwutils/nw-builder/commit/e21051cc8098f12d1006e184108755a88f435517)) 66 | * **deps:** bump @babel/runtime from 7.26.0 to 7.26.10 in the npm_and_yarn group ([#1373](https://github.com/nwutils/nw-builder/issues/1373)) ([0b2d34e](https://github.com/nwutils/nw-builder/commit/0b2d34e11d9bf939a9e608bb6ca6415a929457e0)) 67 | * **deps:** bump actions/setup-node from 4.2.0 to 4.3.0 in /.github/workflows in the gha group ([#1370](https://github.com/nwutils/nw-builder/issues/1370)) ([5509ca9](https://github.com/nwutils/nw-builder/commit/5509ca93acd5fa68f9e860f1ff3c30d96ea739b1)) 68 | * **deps:** bump the npm group across 1 directory with 5 updates ([#1371](https://github.com/nwutils/nw-builder/issues/1371)) ([ba554d2](https://github.com/nwutils/nw-builder/commit/ba554d241e7f3d33c8492b658f4d45f723834fd0)) 69 | 70 | ## [4.13.10](https://github.com/nwutils/nw-builder/compare/v4.13.9...v4.13.10) (2025-03-11) 71 | 72 | 73 | ### Bug Fixes 74 | 75 | * **ci:** give release please write permissions ([920e4a0](https://github.com/nwutils/nw-builder/commit/920e4a02863d71d0acac374678d3c017070a1e9f)) 76 | * **codeql:** address code scanning alerts ([#1360](https://github.com/nwutils/nw-builder/issues/1360)) ([10cb3ba](https://github.com/nwutils/nw-builder/commit/10cb3baa94803bcf0e119333a0c253e14f8bf00f)) 77 | 78 | 79 | ### Chores 80 | 81 | * **deps:** bump googleapis/release-please-action from 4.1.4 to 4.2.0 in /.github/workflows in the gha group across 1 directory ([#1364](https://github.com/nwutils/nw-builder/issues/1364)) ([0f28592](https://github.com/nwutils/nw-builder/commit/0f28592f543b62af07b9b4f8aa291712e4e45432)) 82 | * **deps:** bump the npm group across 1 directory with 6 updates ([#1368](https://github.com/nwutils/nw-builder/issues/1368)) ([1aaffa5](https://github.com/nwutils/nw-builder/commit/1aaffa5803454e5df17c9d57f4461823f979ea88)) 83 | 84 | ## [4.13.9](https://github.com/nwutils/nw-builder/compare/v4.13.8...v4.13.9) (2025-03-06) 85 | 86 | 87 | ### Bug Fixes 88 | 89 | * **get/verify:** use crypto.timingSafeEqual to verify shasums ([3dd449a](https://github.com/nwutils/nw-builder/commit/3dd449aaa55a8f646027f7f454afbe0dc74db35b)) 90 | 91 | 92 | ### Chores 93 | 94 | * **deps:** bump the gha group across 1 directory with 2 updates ([#1358](https://github.com/nwutils/nw-builder/issues/1358)) ([48e4947](https://github.com/nwutils/nw-builder/commit/48e49474c5aa0c13add585489ae74b6d9adef541)) 95 | * **deps:** bump the npm group across 1 directory with 13 updates ([#1357](https://github.com/nwutils/nw-builder/issues/1357)) ([9d3fe0c](https://github.com/nwutils/nw-builder/commit/9d3fe0c95a8209d68055ac25a5f3581c1f6db48a)) 96 | * reverse undeprecation of get and run mode ([e139740](https://github.com/nwutils/nw-builder/commit/e1397405929302f07d5bd1bcec913550050c3aa0)) 97 | 98 | ## [4.13.8](https://github.com/nwutils/nw-builder/compare/v4.13.7...v4.13.8) (2025-01-01) 99 | 100 | 101 | ### Bug Fixes 102 | 103 | * **bld:** await archiver.finalize ([#1333](https://github.com/nwutils/nw-builder/issues/1333)) ([580668f](https://github.com/nwutils/nw-builder/commit/580668f7a56d050d43078c7e0014eacdc3ee7dfc)), closes [#1328](https://github.com/nwutils/nw-builder/issues/1328) 104 | 105 | 106 | ### Chores 107 | 108 | * **deps:** bump the npm group across 1 directory with 11 updates ([#1332](https://github.com/nwutils/nw-builder/issues/1332)) ([b9b96ff](https://github.com/nwutils/nw-builder/commit/b9b96ff565921a518893770996568bb30cee179b)) 109 | * **docs:** clarify CJS usage ([d673459](https://github.com/nwutils/nw-builder/commit/d673459141e68a190964c1d4f02203e32296a8b9)), closes [#1331](https://github.com/nwutils/nw-builder/issues/1331) 110 | * **docs:** clarify non-usage of srcDir in CLi interface ([1b61bd1](https://github.com/nwutils/nw-builder/commit/1b61bd1acb58a1ea966bd3606c7d7d7140cfb1d5)), closes [#1330](https://github.com/nwutils/nw-builder/issues/1330) 111 | * **docs:** fix CJS import usage example ([5f323df](https://github.com/nwutils/nw-builder/commit/5f323df441212d495de75a2dceda5031ddce56d5)), closes [#1331](https://github.com/nwutils/nw-builder/issues/1331) 112 | * **test:** download latest NW.js version for Linux demo app ([8c09908](https://github.com/nwutils/nw-builder/commit/8c09908943eba0414b03a7e79c2a87a5f076bfff)), closes [#1324](https://github.com/nwutils/nw-builder/issues/1324) 113 | 114 | ## [4.13.7](https://github.com/nwutils/nw-builder/compare/v4.13.6...v4.13.7) (2024-11-28) 115 | 116 | 117 | ### Bug Fixes 118 | 119 | * **get:** use \s+ to split lines containing shasum info ([64f5709](https://github.com/nwutils/nw-builder/commit/64f5709f230199e3d6a6e837e95ec4e0f74dc806)), closes [#1317](https://github.com/nwutils/nw-builder/issues/1317) 120 | 121 | ## [4.13.6](https://github.com/nwutils/nw-builder/compare/v4.13.5...v4.13.6) (2024-11-26) 122 | 123 | 124 | ### Bug Fixes 125 | 126 | * **bld:** fs.promises.copyFile -> fs.promises.cp ([c1909c7](https://github.com/nwutils/nw-builder/commit/c1909c7d84e1513339625667fc6cab1525677f3b)) 127 | 128 | ## [4.13.5](https://github.com/nwutils/nw-builder/compare/v4.13.4...v4.13.5) (2024-11-26) 129 | 130 | 131 | ### Bug Fixes 132 | 133 | * **util:** validate windows options.app.* iff defined ([8a65a6d](https://github.com/nwutils/nw-builder/commit/8a65a6dfc6e3bb16431b81b751140ec7a4056496)) 134 | 135 | ## [4.13.4](https://github.com/nwutils/nw-builder/compare/v4.13.3...v4.13.4) (2024-11-26) 136 | 137 | 138 | ### Bug Fixes 139 | 140 | * **util:** validate options.app.company iff defined ([a273e23](https://github.com/nwutils/nw-builder/commit/a273e2335c29922e662b7cc69aeeb0ffe40fba33)) 141 | 142 | ## [4.13.3](https://github.com/nwutils/nw-builder/compare/v4.13.2...v4.13.3) (2024-11-25) 143 | 144 | 145 | ### Bug Fixes 146 | 147 | * **bld:** correct fs.promises.copyFile function call ([63fd422](https://github.com/nwutils/nw-builder/commit/63fd422575828dd6be43455e4274abadb4240fbe)) 148 | 149 | ## [4.13.2](https://github.com/nwutils/nw-builder/compare/v4.13.1...v4.13.2) (2024-11-25) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * **bld:** parse options.app.icon correctly during build mode ([bd0ef96](https://github.com/nwutils/nw-builder/commit/bd0ef96f50660be90398e1075434ef003112bbc5)) 155 | * **bld:** use fs.promises.copyFile to copy app files in build mode with glob enabled ([e1843f0](https://github.com/nwutils/nw-builder/commit/e1843f00c6f2ec389933565ba0b3975c2c93bc23)) 156 | 157 | ## [4.13.1](https://github.com/nwutils/nw-builder/compare/v4.13.0...v4.13.1) (2024-11-24) 158 | 159 | 160 | ### Bug Fixes 161 | 162 | * **run:** return NW.js Node process during run mode from nwbuild function ([fa94df2](https://github.com/nwutils/nw-builder/commit/fa94df284c3e6d23e0efd44d363b71b564cf1f26)) 163 | * **types:** correct nwbuild function return type ([b274d27](https://github.com/nwutils/nw-builder/commit/b274d27dbb843b76e26be751249b994f233ac696)) 164 | 165 | ## [4.13.0](https://github.com/nwutils/nw-builder/compare/v4.12.1...v4.13.0) (2024-11-24) 166 | 167 | 168 | ### Features 169 | 170 | * **get:** add options.shaSum to enable/disable shasum checks ([#1307](https://github.com/nwutils/nw-builder/issues/1307)) ([98abcaf](https://github.com/nwutils/nw-builder/commit/98abcafeb884a42c34208a6a83f37eb7d518122c)) 171 | 172 | ## [4.12.1](https://github.com/nwutils/nw-builder/compare/v4.12.0...v4.12.1) (2024-11-24) 173 | 174 | 175 | ### Bug Fixes 176 | 177 | * **util:** correct Array.isArray usage ([31c4132](https://github.com/nwutils/nw-builder/commit/31c4132bc3313c687f85e7b9ecf2562c483b6639)) 178 | 179 | ## [4.12.0](https://github.com/nwutils/nw-builder/compare/v4.11.6...v4.12.0) (2024-11-21) 180 | 181 | 182 | ### Features 183 | 184 | * **run:** return NW.js process reference ([#1304](https://github.com/nwutils/nw-builder/issues/1304)) ([bd2f926](https://github.com/nwutils/nw-builder/commit/bd2f9263d6bf61a98db2e0ec14c5d0ca68aa4b0f)) 185 | 186 | 187 | ### Bug Fixes 188 | 189 | * miscellaneous quality of life improvements ([#1296](https://github.com/nwutils/nw-builder/issues/1296)) ([a82c140](https://github.com/nwutils/nw-builder/commit/a82c140bd6ebd234dcfe5bb0bff668cfb18d60bc)) 190 | * **util:** validate options.* correctly ([#1298](https://github.com/nwutils/nw-builder/issues/1298)) ([3034f5c](https://github.com/nwutils/nw-builder/commit/3034f5cd4214f9b1e4ee5d459a20463eb4d0a50d)) 191 | * **util:** validate options.app.* values ([#1302](https://github.com/nwutils/nw-builder/issues/1302)) ([4f388a9](https://github.com/nwutils/nw-builder/commit/4f388a95b3ad634330290ddbc9afca9ab1cda576)), closes [#1279](https://github.com/nwutils/nw-builder/issues/1279) [#1293](https://github.com/nwutils/nw-builder/issues/1293) 192 | 193 | 194 | ### Chores 195 | 196 | * **deps-dev:** bump the npm group across 1 directory with 6 updates ([#1301](https://github.com/nwutils/nw-builder/issues/1301)) ([56c1192](https://github.com/nwutils/nw-builder/commit/56c11929d1ae0bce83f4f12ba6fd315d70fd43f3)) 197 | * **deps:** bump cross-spawn from 7.0.3 to 7.0.6 ([#1305](https://github.com/nwutils/nw-builder/issues/1305)) ([2803af3](https://github.com/nwutils/nw-builder/commit/2803af3d46ff49bb87487ce3ce59de764ee57cbd)) 198 | * **deps:** bump davelosert/vitest-coverage-report-action from 2.6.0 to 2.7.0 in /.github/workflows in the gha group ([#1295](https://github.com/nwutils/nw-builder/issues/1295)) ([23aaad8](https://github.com/nwutils/nw-builder/commit/23aaad85322ac5eb2a3ccb8546a43884c4d89b04)) 199 | * **deps:** bump davelosert/vitest-coverage-report-action from 2.7.0 to 2.8.0 in /.github/workflows in the gha group ([#1303](https://github.com/nwutils/nw-builder/issues/1303)) ([ceaf348](https://github.com/nwutils/nw-builder/commit/ceaf348f2d62243576f0c8b6ff57aab1ea1848dc)) 200 | 201 | ## [4.11.6](https://github.com/nwutils/nw-builder/compare/v4.11.5...v4.11.6) (2024-11-01) 202 | 203 | 204 | ### Bug Fixes 205 | 206 | * **bld:** set product_string property in manifest to rename MacOS Helper apps ([#1290](https://github.com/nwutils/nw-builder/issues/1290)) ([b1caad7](https://github.com/nwutils/nw-builder/commit/b1caad7343c95f8f37251f3b3132f86adc31f38a)), closes [#1286](https://github.com/nwutils/nw-builder/issues/1286) 207 | 208 | ## [4.11.5](https://github.com/nwutils/nw-builder/compare/v4.11.4...v4.11.5) (2024-11-01) 209 | 210 | 211 | ### Bug Fixes 212 | 213 | * **cli:** handle boolean type options correctly ([#1255](https://github.com/nwutils/nw-builder/issues/1255)) ([524fe34](https://github.com/nwutils/nw-builder/commit/524fe34438493d3aa0f4236741d91462cfb068e3)), closes [#1277](https://github.com/nwutils/nw-builder/issues/1277) 214 | 215 | 216 | ### Chores 217 | 218 | * **deps-dev:** bump nw from 0.92.0 to 0.93.0 in the npm group across 1 directory ([#1289](https://github.com/nwutils/nw-builder/issues/1289)) ([ed275ad](https://github.com/nwutils/nw-builder/commit/ed275ad120105d8b5d8f324e97b311c791a92d1a)) 219 | * **deps:** bump the gha group across 1 directory with 2 updates ([#1285](https://github.com/nwutils/nw-builder/issues/1285)) ([13081c9](https://github.com/nwutils/nw-builder/commit/13081c918ce2b76cf39a31b8b25ef26e3903f91c)) 220 | * **docs:** clarify priority for defined options ([#1281](https://github.com/nwutils/nw-builder/issues/1281)) ([632db41](https://github.com/nwutils/nw-builder/commit/632db4105dd3b205d448c6fe02b26e46167d6549)), closes [#1261](https://github.com/nwutils/nw-builder/issues/1261) 221 | * **docs:** improve terminology ([84fa2a4](https://github.com/nwutils/nw-builder/commit/84fa2a4e55e88e490f46e91dade60e2ab285480b)) 222 | 223 | ## [4.11.4](https://github.com/nwutils/nw-builder/compare/v4.11.3...v4.11.4) (2024-10-13) 224 | 225 | 226 | ### Bug Fixes 227 | 228 | * **util:** strip special and control characters from app.name ([#1259](https://github.com/nwutils/nw-builder/issues/1259)) ([b035bc3](https://github.com/nwutils/nw-builder/commit/b035bc3d7393b7ae15c3996e62eb24afbf691945)) 229 | 230 | 231 | ### Chores 232 | 233 | * **deps:** bump actions/checkout from 4.2.0 to 4.2.1 in /.github/workflows in the gha group ([#1273](https://github.com/nwutils/nw-builder/issues/1273)) ([3165f2b](https://github.com/nwutils/nw-builder/commit/3165f2b0a6da286295ddc94b4178b7567d394043)) 234 | * **deps:** bump the npm group across 1 directory with 7 updates ([#1275](https://github.com/nwutils/nw-builder/issues/1275)) ([5f26f21](https://github.com/nwutils/nw-builder/commit/5f26f21df78997aa5add40f5c049ec7fd2917474)) 235 | * **deps:** drop Dependabot support for v3 branch ([19cf479](https://github.com/nwutils/nw-builder/commit/19cf47973057c420dc0bb70ddb5e50df1aa0de4c)) 236 | 237 | ## [4.11.3](https://github.com/nwutils/nw-builder/compare/v4.11.2...v4.11.3) (2024-10-02) 238 | 239 | 240 | ### Bug Fixes 241 | 242 | * **get:** do not check integrity of community ffmpeg but give a warning ([#1253](https://github.com/nwutils/nw-builder/issues/1253)) ([0c05a34](https://github.com/nwutils/nw-builder/commit/0c05a3493c25a172c018e0d788a563c80592c2ef)), closes [#1209](https://github.com/nwutils/nw-builder/issues/1209) 243 | 244 | ## [4.11.2](https://github.com/nwutils/nw-builder/compare/v4.11.1...v4.11.2) (2024-10-02) 245 | 246 | 247 | ### Bug Fixes 248 | 249 | * **get:** verify shasum for official ffmpeg not community ffmpeg ([#1251](https://github.com/nwutils/nw-builder/issues/1251)) ([9385836](https://github.com/nwutils/nw-builder/commit/938583602788f55f1e050013e78890118e19ed7a)), closes [#1209](https://github.com/nwutils/nw-builder/issues/1209) 250 | 251 | ## [4.11.1](https://github.com/nwutils/nw-builder/compare/v4.11.0...v4.11.1) (2024-10-01) 252 | 253 | 254 | ### Bug Fixes 255 | 256 | * **types:** add managedManifest parameter in options ([#1249](https://github.com/nwutils/nw-builder/issues/1249)) ([8b3b407](https://github.com/nwutils/nw-builder/commit/8b3b407ab96e8f4162abf92b6e8158923f71687f)), closes [#1248](https://github.com/nwutils/nw-builder/issues/1248) 257 | 258 | 259 | ### Chores 260 | 261 | * **deps-dev:** bump the npm group across 1 directory with 6 updates ([#1250](https://github.com/nwutils/nw-builder/issues/1250)) ([94da963](https://github.com/nwutils/nw-builder/commit/94da963848ef2f311d13da49a1d78e6b8f117b22)) 262 | * **deps:** bump rollup from 4.22.1 to 4.22.4 ([#1241](https://github.com/nwutils/nw-builder/issues/1241)) ([6601ebc](https://github.com/nwutils/nw-builder/commit/6601ebc3db46e28964e0aa05de37a2f68745ba46)) 263 | * **deps:** bump the gha group across 1 directory with 2 updates ([#1247](https://github.com/nwutils/nw-builder/issues/1247)) ([526454e](https://github.com/nwutils/nw-builder/commit/526454e276c67bac435c02699109ae563fe1c4d7)) 264 | 265 | ## [4.11.0](https://github.com/nwutils/nw-builder/compare/v4.10.0...v4.11.0) (2024-09-20) 266 | 267 | 268 | ### Features 269 | 270 | * **bld:** add NSLocalNetworkUsageDescription option for MacOS ([#1236](https://github.com/nwutils/nw-builder/issues/1236)) ([eea3f69](https://github.com/nwutils/nw-builder/commit/eea3f69ee1951e24f4fbc8d1550b0bee1e43951f)), closes [#1235](https://github.com/nwutils/nw-builder/issues/1235) 271 | 272 | 273 | ### Chores 274 | 275 | * **bld:** remove repetitive code for updating MacOS Helper apps ([#1214](https://github.com/nwutils/nw-builder/issues/1214)) ([e1edc05](https://github.com/nwutils/nw-builder/commit/e1edc051c1660ef474643b903ac4e9a0f2968a05)) 276 | * **cli:** migrate from yargs to commander ([#1216](https://github.com/nwutils/nw-builder/issues/1216)) ([7ca5a28](https://github.com/nwutils/nw-builder/commit/7ca5a28f40078ab179aab72cf0f3260ef5f118f2)) 277 | * **deps:** bump actions/setup-node from 4.0.3 to 4.0.4 in /.github/workflows in the gha group ([#1232](https://github.com/nwutils/nw-builder/issues/1232)) ([8473fc6](https://github.com/nwutils/nw-builder/commit/8473fc63811fd08066b7801c019d00565918770b)) 278 | * **deps:** bump the npm group across 1 directory with 7 updates ([#1233](https://github.com/nwutils/nw-builder/issues/1233)) ([9efa2f4](https://github.com/nwutils/nw-builder/commit/9efa2f439e46aaadf55c79d9c3bd404cea1144aa)) 279 | * **deps:** bump vite from 5.3.5 to 5.4.6 ([#1231](https://github.com/nwutils/nw-builder/issues/1231)) ([6135682](https://github.com/nwutils/nw-builder/commit/6135682dc01f8a1dad12660957ddd637e6a20864)) 280 | 281 | ## [4.10.0](https://github.com/nwutils/nw-builder/compare/v4.9.0...v4.10.0) (2024-08-24) 282 | 283 | 284 | ### Features 285 | 286 | * **bld:** rename MacOS Helper apps ([#1206](https://github.com/nwutils/nw-builder/issues/1206)) ([9f3b30f](https://github.com/nwutils/nw-builder/commit/9f3b30f9e04c2d2d103f5ec19078c0a14fbf12f7)) 287 | 288 | 289 | ### Chores 290 | 291 | * **deps:** bump davelosert/vitest-coverage-report-action from 2.5.0 to 2.5.1 in /.github/workflows in the gha group ([#1210](https://github.com/nwutils/nw-builder/issues/1210)) ([6d69ae3](https://github.com/nwutils/nw-builder/commit/6d69ae36bc1edcf1867aa25e464e4a9b1aa28aa4)) 292 | * **deps:** bump the npm group across 1 directory with 3 updates ([#1212](https://github.com/nwutils/nw-builder/issues/1212)) ([20b7e81](https://github.com/nwutils/nw-builder/commit/20b7e8190f022bc8a50776176bdcc70d3bdcd08f)) 293 | * **get:** improve error message when comparing shasums ([#1213](https://github.com/nwutils/nw-builder/issues/1213)) ([b37068f](https://github.com/nwutils/nw-builder/commit/b37068f9228c1f00877d51c5fe607be7096b6564)), closes [#1209](https://github.com/nwutils/nw-builder/issues/1209) 294 | * **test:** move tests to seperate dir ([#1205](https://github.com/nwutils/nw-builder/issues/1205)) ([da0e353](https://github.com/nwutils/nw-builder/commit/da0e353e805b8e4b249f298c74a352a114b2737d)) 295 | * **test:** rename fixtures dir ([c6193bb](https://github.com/nwutils/nw-builder/commit/c6193bb67fa32d750b42f9a8a7d44db507126519)) 296 | 297 | ## [4.9.0](https://github.com/nwutils/nw-builder/compare/v4.8.1...v4.9.0) (2024-08-15) 298 | 299 | 300 | ### Features 301 | 302 | * **get:** verify sha256 checksum ([#1201](https://github.com/nwutils/nw-builder/issues/1201)) ([810944d](https://github.com/nwutils/nw-builder/commit/810944da14ffc97d8fb2b5d78d7c8d09ad514226)) 303 | 304 | 305 | ### Chores 306 | 307 | * **deps:** bump the npm group across 1 directory with 4 updates ([#1199](https://github.com/nwutils/nw-builder/issues/1199)) ([b07d1fc](https://github.com/nwutils/nw-builder/commit/b07d1fc09d4fcc8282b04c369725594d120af12a)) 308 | * **deps:** bump volta-cli/action from 4.1.1 to 4.2.1 in /.github/workflows in the gha group across 1 directory ([#1189](https://github.com/nwutils/nw-builder/issues/1189)) ([282ea7a](https://github.com/nwutils/nw-builder/commit/282ea7ade9d93dbbf8d97c9c0770df0876c3fd8a)) 309 | * **deps:** remove license check logic ([9dde7b2](https://github.com/nwutils/nw-builder/commit/9dde7b2b36a1ccd0effb546a89584d2075e59a17)) 310 | 311 | ## [4.8.1](https://github.com/nwutils/nw-builder/compare/v4.8.0...v4.8.1) (2024-08-05) 312 | 313 | 314 | ### Bug Fixes 315 | 316 | * **bld:** maintain cwd when using managedManifest or nativeAddon ([#1187](https://github.com/nwutils/nw-builder/issues/1187)) ([40223db](https://github.com/nwutils/nw-builder/commit/40223db6af75651618df7f3099b99191faa11f24)), closes [#1186](https://github.com/nwutils/nw-builder/issues/1186) 317 | 318 | 319 | ### Chores 320 | 321 | * **deps:** bump the npm group across 1 directory with 6 updates ([#1185](https://github.com/nwutils/nw-builder/issues/1185)) ([f4c0822](https://github.com/nwutils/nw-builder/commit/f4c08224c7651b3eecf2353a7a50fd7cf240c2f0)) 322 | * **deps:** remove unused cli-progress package ([8f4e07d](https://github.com/nwutils/nw-builder/commit/8f4e07dfbda348fcc832694d346be7616bfb1f4b)) 323 | 324 | ## [4.8.0](https://github.com/nwutils/nw-builder/compare/v4.7.8...v4.8.0) (2024-07-27) 325 | 326 | 327 | ### Features 328 | 329 | * **bld:** add languageCode option for Windows ([#1175](https://github.com/nwutils/nw-builder/issues/1175)) ([96ad585](https://github.com/nwutils/nw-builder/commit/96ad585ec170416d31248ccf3503191831e802b0)) 330 | 331 | 332 | ### Bug Fixes 333 | 334 | * **bld:** pass nw manifest correctly when managedManifest is true ([#1176](https://github.com/nwutils/nw-builder/issues/1176)) ([949c4b7](https://github.com/nwutils/nw-builder/commit/949c4b70b89cc96ee98d88b910caa833eb99d385)) 335 | 336 | 337 | ### Chores 338 | 339 | * **ci:** check for valid licenses ([#1150](https://github.com/nwutils/nw-builder/issues/1150)) ([ab99731](https://github.com/nwutils/nw-builder/commit/ab997311046b1e75aee24397a3b9bb20d19c4d2f)) 340 | * **deps:** bump actions/setup-node from 4.0.2 to 4.0.3 in /.github/workflows in the gha group ([#1151](https://github.com/nwutils/nw-builder/issues/1151)) ([7130930](https://github.com/nwutils/nw-builder/commit/7130930c3d7ce28c1e07c510045576830e266c77)) 341 | * **deps:** bump the npm group across 1 directory with 8 updates ([#1177](https://github.com/nwutils/nw-builder/issues/1177)) ([9410455](https://github.com/nwutils/nw-builder/commit/94104551d8b8f8c1ece595e34caa91b3c34dc0a3)) 342 | * **deps:** migrate from compressing to tar and archiver ([7c73903](https://github.com/nwutils/nw-builder/commit/7c73903d6c3723814df64d9241976b457cb2d149)) 343 | * **docs:** improve install/quick start ([a87f44b](https://github.com/nwutils/nw-builder/commit/a87f44b5f6e0bdc0e09569a560b10a032766da02)) 344 | * fsm -> fs.promises ([08d79bf](https://github.com/nwutils/nw-builder/commit/08d79bf07c0b590a376af4cc53a37db4d75094cf)) 345 | 346 | ## [4.7.8](https://github.com/nwutils/nw-builder/compare/v4.7.7...v4.7.8) (2024-07-02) 347 | 348 | 349 | ### Chores 350 | 351 | * **ci:** add Vitest Coverage Action ([#1136](https://github.com/nwutils/nw-builder/issues/1136)) ([4d32845](https://github.com/nwutils/nw-builder/commit/4d32845877ee7a64b61e8226b5593f6c8137524a)), closes [#1041](https://github.com/nwutils/nw-builder/issues/1041) 352 | * **deps-dev:** bump eslint-plugin-jsdoc from 48.2.13 to 48.4.0 in the npm group ([#1138](https://github.com/nwutils/nw-builder/issues/1138)) ([61189de](https://github.com/nwutils/nw-builder/commit/61189de946bf5bafd2acdd7b37b05e5a68e6b9a3)) 353 | * **deps-dev:** bump the npm group across 1 directory with 4 updates ([#1142](https://github.com/nwutils/nw-builder/issues/1142)) ([761963d](https://github.com/nwutils/nw-builder/commit/761963d60db32aa53fd585204ea83868164986ae)) 354 | * **deps:** bump davelosert/vitest-coverage-report-action from 2.4.0 to 2.5.0 in /.github/workflows in the gha group ([#1140](https://github.com/nwutils/nw-builder/issues/1140)) ([fddaf9f](https://github.com/nwutils/nw-builder/commit/fddaf9f1a2f53b8c9b8314b4043d5a0523be2e35)) 355 | * **test:** enable vitest json reporter ([e44aadb](https://github.com/nwutils/nw-builder/commit/e44aadb880b794b99516069d2c40473a72f18dc5)) 356 | 357 | ## [4.7.7](https://github.com/nwutils/nw-builder/compare/v4.7.6...v4.7.7) (2024-06-22) 358 | 359 | 360 | ### Bug Fixes 361 | 362 | * execute postinstall script iff in development mode ([#1132](https://github.com/nwutils/nw-builder/issues/1132)) ([3c68216](https://github.com/nwutils/nw-builder/commit/3c682167369248e96ed48ea06163169805fcedda)) 363 | 364 | 365 | ### Chores 366 | 367 | * **deps:** bump the npm group across 1 directory with 6 updates ([#1135](https://github.com/nwutils/nw-builder/issues/1135)) ([905478a](https://github.com/nwutils/nw-builder/commit/905478a0156a9047aa10f629cb53200ee6f90e65)) 368 | 369 | ## [4.7.6](https://github.com/nwutils/nw-builder/compare/v4.7.5...v4.7.6) (2024-06-19) 370 | 371 | 372 | ### Bug Fixes 373 | 374 | * **bld:** resolve undefined reference ([#1127](https://github.com/nwutils/nw-builder/issues/1127)) ([006b517](https://github.com/nwutils/nw-builder/commit/006b517be0420e08134b5670e5d62a91f8b9107c)), closes [#1125](https://github.com/nwutils/nw-builder/issues/1125) 375 | 376 | 377 | ### Chores 378 | 379 | * **deps-dev:** bump @stylistic/eslint-plugin-js from 2.1.0 to 2.2.1 in the npm group ([#1123](https://github.com/nwutils/nw-builder/issues/1123)) ([fcd83a9](https://github.com/nwutils/nw-builder/commit/fcd83a9875068ed9e86870cb4bbdabcb86b74bef)) 380 | * **deps-dev:** bump ws from 8.17.0 to 8.17.1 ([#1126](https://github.com/nwutils/nw-builder/issues/1126)) ([7aa6f08](https://github.com/nwutils/nw-builder/commit/7aa6f08927869c8ceb5da686bb53e48f537f7a40)) 381 | * **deps:** bump actions/checkout from 4.1.6 to 4.1.7 in /.github/workflows in the gha group ([#1116](https://github.com/nwutils/nw-builder/issues/1116)) ([eefde68](https://github.com/nwutils/nw-builder/commit/eefde686e344ae3e51191caf66a5b6b0b1b736fe)) 382 | * **test:** enable e2e tests ([#1120](https://github.com/nwutils/nw-builder/issues/1120)) ([f802947](https://github.com/nwutils/nw-builder/commit/f80294712240caf8ccf225684eaec20ecb5f80a8)) 383 | 384 | ## [4.7.5](https://github.com/nwutils/nw-builder/compare/v4.7.4...v4.7.5) (2024-06-11) 385 | 386 | 387 | ### Bug Fixes 388 | 389 | * **run:** set stdio behaviour to inherit ([a3d181a](https://github.com/nwutils/nw-builder/commit/a3d181a5b9b6f967c11e7082fea57db96078bf7e)) 390 | 391 | 392 | ### Chores 393 | 394 | * **bld:** migrate from rcedit with resedit ([#1094](https://github.com/nwutils/nw-builder/issues/1094)) ([03a55b9](https://github.com/nwutils/nw-builder/commit/03a55b919a7e5dfcc1d9fa3f06baa327804d67c4)) 395 | * **deps:** bump actions/checkout from 4.1.5 to 4.1.6 in /.github/workflows in the gha group ([#1095](https://github.com/nwutils/nw-builder/issues/1095)) ([0f1b126](https://github.com/nwutils/nw-builder/commit/0f1b1260d3a36939c111313ec1ab121fe8f12955)) 396 | * **deps:** bump google-github-actions/release-please-action from 4.1.0 to 4.1.1 in /.github/workflows in the gha group ([#1091](https://github.com/nwutils/nw-builder/issues/1091)) ([316741b](https://github.com/nwutils/nw-builder/commit/316741ba699fcd0f8a7dd1176cbd14ca05c571be)) 397 | * **deps:** bump googleapis/release-please-action from 4.1.1 to 4.1.3 in /.github/workflows in the gha group ([#1114](https://github.com/nwutils/nw-builder/issues/1114)) ([e284f5b](https://github.com/nwutils/nw-builder/commit/e284f5b61c0df05b63388ea5d31311f5daacd858)) 398 | * **deps:** bump the npm group across 1 directory with 3 updates ([#1112](https://github.com/nwutils/nw-builder/issues/1112)) ([fde3491](https://github.com/nwutils/nw-builder/commit/fde34914d920245f535184ae545a56d939c59b8d)) 399 | * **deps:** bump the npm group across 1 directory with 6 updates ([#1105](https://github.com/nwutils/nw-builder/issues/1105)) ([eb63ded](https://github.com/nwutils/nw-builder/commit/eb63dedabbf2ae525fa9f1ab6aa57d9b11c63fe0)) 400 | * **deps:** upgrade to eslint v9 ([ffe6dd0](https://github.com/nwutils/nw-builder/commit/ffe6dd0238a8401fab46beed06c2812b0fa89abd)) 401 | * **docs:** add missing platform-specific app options info ([#1093](https://github.com/nwutils/nw-builder/issues/1093)) ([715097f](https://github.com/nwutils/nw-builder/commit/715097f53dfe69e7895634fd85e4043b6a3242e1)) 402 | 403 | ## [4.7.4](https://github.com/nwutils/nw-builder/compare/v4.7.3...v4.7.4) (2024-05-12) 404 | 405 | 406 | ### Bug Fixes 407 | 408 | * **cli:** add missing options managedManifest and nodeAddon ([#1084](https://github.com/nwutils/nw-builder/issues/1084)) ([f6ced81](https://github.com/nwutils/nw-builder/commit/f6ced81d1e9b2b862fd916fcb7432a0d53881039)) 409 | 410 | 411 | ### Chores 412 | 413 | * **deps:** bump the npm group with 4 updates ([#1082](https://github.com/nwutils/nw-builder/issues/1082)) ([26cbf88](https://github.com/nwutils/nw-builder/commit/26cbf881cc7460f088ad9b5fdd84d10b5c7589ab)) 414 | 415 | ## [4.7.3](https://github.com/nwutils/nw-builder/compare/v4.7.2...v4.7.3) (2024-05-09) 416 | 417 | 418 | ### Bug Fixes 419 | 420 | * **cli:** disallow unknown options ([#1079](https://github.com/nwutils/nw-builder/issues/1079)) ([5d54d8c](https://github.com/nwutils/nw-builder/commit/5d54d8c20927c5dfabacc128d1cee036a7cf6fb5)) 421 | 422 | ## [4.7.2](https://github.com/nwutils/nw-builder/compare/v4.7.1...v4.7.2) (2024-05-09) 423 | 424 | 425 | ### Bug Fixes 426 | 427 | * **get:** close file after reading all entries ([#1077](https://github.com/nwutils/nw-builder/issues/1077)) ([a6b090f](https://github.com/nwutils/nw-builder/commit/a6b090fd3ebeb4ed4d45d04c711d00eddf79dc9e)) 428 | 429 | 430 | ### Chores 431 | 432 | * **ci:** correct config hopefully ([0bad4d2](https://github.com/nwutils/nw-builder/commit/0bad4d2893389a76f7292bfaffaa77cdb8094571)) 433 | * **deps:** bump the gha group in /.github/workflows with 2 updates ([#1074](https://github.com/nwutils/nw-builder/issues/1074)) ([fd8a633](https://github.com/nwutils/nw-builder/commit/fd8a6335a26a01f187171e607f289485b0d4865f)) 434 | * **deps:** bump the npm group across 1 directory with 11 updates ([#1078](https://github.com/nwutils/nw-builder/issues/1078)) ([7158c7b](https://github.com/nwutils/nw-builder/commit/7158c7b8881d333995d1cda32812776500d7d96b)) 435 | * **deps:** bump vite from 5.1.6 to 5.2.8 ([#1060](https://github.com/nwutils/nw-builder/issues/1060)) ([c52dbb6](https://github.com/nwutils/nw-builder/commit/c52dbb690f46d8929dbd9ba553eb9967b7683988)) 436 | * **docs:** update contributing guidelines ([efdbdca](https://github.com/nwutils/nw-builder/commit/efdbdca0f73a91129dc279d2f7346084f8a0cefe)) 437 | 438 | ## [4.7.1](https://github.com/nwutils/nw-builder/compare/v4.7.0...v4.7.1) (2024-03-30) 439 | 440 | 441 | ### Bug Fixes 442 | 443 | * **get:** missing import ([bd2273a](https://github.com/nwutils/nw-builder/commit/bd2273a84199de3b804e96c92da88e66aa7fce32)) 444 | 445 | 446 | ### Chores 447 | 448 | * **deps:** reconfigure dependabot ([698ecd5](https://github.com/nwutils/nw-builder/commit/698ecd5817a9f4ee7e17271410ebce78157644fa)) 449 | 450 | ## [4.7.0](https://github.com/nwutils/nw-builder/compare/v4.6.4...v4.7.0) (2024-03-26) 451 | 452 | 453 | ### Features 454 | 455 | * **get:** support `file://` for `options.downloadUrl` ([094567c](https://github.com/nwutils/nw-builder/commit/094567c1192c66465fb8ed43d6e5b6f0ed7cfdec)) 456 | 457 | 458 | ### Chores 459 | 460 | * **deps:** bump follow-redirects from 1.15.5 to 1.15.6 ([#1052](https://github.com/nwutils/nw-builder/issues/1052)) ([8258de9](https://github.com/nwutils/nw-builder/commit/8258de9773fe5b9e497abbb9cea6978329ee6707)) 461 | * **deps:** bump the gha group in /.github/workflows with 1 update ([#1054](https://github.com/nwutils/nw-builder/issues/1054)) ([1935800](https://github.com/nwutils/nw-builder/commit/1935800ce560ad5c59cf422276916e3960a60a0d)) 462 | * **deps:** bump the gha group in /.github/workflows with 2 updates ([#1051](https://github.com/nwutils/nw-builder/issues/1051)) ([0362403](https://github.com/nwutils/nw-builder/commit/0362403b9501465258b974caefe623eebfb640f1)) 463 | * **deps:** bump the npm group with 1 update ([#1055](https://github.com/nwutils/nw-builder/issues/1055)) ([5a7bc71](https://github.com/nwutils/nw-builder/commit/5a7bc718a82b0907b89035412fda25cef54f32f6)) 464 | * **deps:** bump the npm group with 3 updates ([#1050](https://github.com/nwutils/nw-builder/issues/1050)) ([a70aabc](https://github.com/nwutils/nw-builder/commit/a70aabc14b5614a32d4fbfa995e35af123cef65f)) 465 | * **deps:** bump the npm group with 8 updates ([#1048](https://github.com/nwutils/nw-builder/issues/1048)) ([0ca3c34](https://github.com/nwutils/nw-builder/commit/0ca3c341f312b584b37628a9d3726eca2b4584be)) 466 | * **get:** deprecate get mode ([#1053](https://github.com/nwutils/nw-builder/issues/1053)) ([386fc18](https://github.com/nwutils/nw-builder/commit/386fc18efc4779438591cbc3ab39c72f65473215)) 467 | 468 | ## [4.6.4](https://github.com/nwutils/nw-builder/compare/v4.6.3...v4.6.4) (2024-02-24) 469 | 470 | 471 | ### Bug Fixes 472 | 473 | * **get:** copy ffmpeg to correct location on windows ([#1044](https://github.com/nwutils/nw-builder/issues/1044)) ([71fa4ab](https://github.com/nwutils/nw-builder/commit/71fa4ab471c77b2853dd822e4ea5e97cbd9daeb9)) 474 | 475 | ## [4.6.3](https://github.com/nwutils/nw-builder/compare/v4.6.2...v4.6.3) (2024-02-22) 476 | 477 | 478 | ### Bug Fixes 479 | 480 | * **get:** correct ffmpeg path ([#1042](https://github.com/nwutils/nw-builder/issues/1042)) ([391a6e1](https://github.com/nwutils/nw-builder/commit/391a6e1a715407ba45ea4b12dda812caea709535)) 481 | 482 | 483 | ### Chores 484 | 485 | * **docs:** update contributing guidelines ([ca594df](https://github.com/nwutils/nw-builder/commit/ca594df7beaa49a95d2fa33c14ebcc120a0b6d3c)) 486 | 487 | ## [4.6.2](https://github.com/nwutils/nw-builder/compare/v4.6.1...v4.6.2) (2024-02-22) 488 | 489 | 490 | ### Bug Fixes 491 | 492 | * **get:** ffmpeg and symlinks ([d5c1bf5](https://github.com/nwutils/nw-builder/commit/d5c1bf53cc66afdf79c3f9653ff6b9e77a7de4b6)) 493 | 494 | 495 | ### Chores 496 | 497 | * **deps:** bump ip from 2.0.0 to 2.0.1 ([2284d52](https://github.com/nwutils/nw-builder/commit/2284d52ba0c4d1bb9a86a91e138ba99ce2af8d6c)) 498 | 499 | ## [4.6.1](https://github.com/nwutils/nw-builder/compare/v4.6.0...v4.6.1) (2024-02-15) 500 | 501 | 502 | ### Chores 503 | 504 | * **ci:** add chores section to changelog ([#1028](https://github.com/nwutils/nw-builder/issues/1028)) ([d630720](https://github.com/nwutils/nw-builder/commit/d630720039ba81563aa0e00995aa004c8d5edc79)) 505 | * **ci:** fixup release please action ([#1032](https://github.com/nwutils/nw-builder/issues/1032)) ([f9ae7cd](https://github.com/nwutils/nw-builder/commit/f9ae7cd170bba17849ff66ac4612df9d3a0716de)) 506 | * **ci:** remove `schema` prop ([33238b1](https://github.com/nwutils/nw-builder/commit/33238b14fb1bff16bd351826bd2d891c7e6d136c)) 507 | * **deps:** bump the gha group in /.github/workflows with 1 update ([70030df](https://github.com/nwutils/nw-builder/commit/70030df94d55e5563775df16b7f07b2537198f69)) 508 | * **docs:** update PR template to simplify commit descriptions ([#1029](https://github.com/nwutils/nw-builder/issues/1029)) ([6da9b89](https://github.com/nwutils/nw-builder/commit/6da9b898f74309dde6ca120dddbeaa32e7bdbcfc)) 509 | * **docs:** update readme and changelog ([63fd50b](https://github.com/nwutils/nw-builder/commit/63fd50bdbfed52de4be4332601944e058b11d793)) 510 | * fix remaining lint errors ([334ae74](https://github.com/nwutils/nw-builder/commit/334ae744d2d1d56d973145e407a987107675eb04)) 511 | * **get:** refactor implementation ([#1025](https://github.com/nwutils/nw-builder/issues/1025)) ([72f65e1](https://github.com/nwutils/nw-builder/commit/72f65e134b3f5dfd543aba9d292c016da8b6d7f3)) 512 | * **get:** refactor unzip symlink implementation ([#1030](https://github.com/nwutils/nw-builder/issues/1030)) ([69661c3](https://github.com/nwutils/nw-builder/commit/69661c301278f2f4071f41fa71e909733698c680)) 513 | * **get:** simplify symlink logic ([#1035](https://github.com/nwutils/nw-builder/issues/1035)) ([4f64307](https://github.com/nwutils/nw-builder/commit/4f643077fb259fb1374e41c88abc801486f8467c)), closes [#1030](https://github.com/nwutils/nw-builder/issues/1030) 514 | * **release-please-action:** do not point to manifest file ([96eeec8](https://github.com/nwutils/nw-builder/commit/96eeec806e1aba0b62acd899d5b9e98070a32b64)) 515 | * **release-please-action:** point to config and manifest ([0a6a44d](https://github.com/nwutils/nw-builder/commit/0a6a44db44ae19c246221f7f05450f6ef1e9f646)) 516 | * **release-please:** correct path to package ([3719cee](https://github.com/nwutils/nw-builder/commit/3719cee4571b46b6b4c2c4d2b6806864e3c90c16)) 517 | * **release-please:** remove manifest ([23a16fb](https://github.com/nwutils/nw-builder/commit/23a16fbd5e93c3ad1221dad6106299079f81ea7c)) 518 | * **run:** mark run mode as deprecated ([#1027](https://github.com/nwutils/nw-builder/issues/1027)) ([1115728](https://github.com/nwutils/nw-builder/commit/1115728d433cba123a7e2dd54a52abaaed4710a6)) 519 | * **test:** try adding chores to release notes ([#1031](https://github.com/nwutils/nw-builder/issues/1031)) ([5cabc20](https://github.com/nwutils/nw-builder/commit/5cabc20e79bb0fe0d77687f97ac4fae8fc3e95a9)) 520 | 521 | ## [4.6.0](https://github.com/nwutils/nw-builder/compare/v4.5.4...v4.6.0) (2024-02-01) 522 | 523 | 524 | ### Features 525 | 526 | * **get:** set `cacheDir` on another volume ([#1023](https://github.com/nwutils/nw-builder/issues/1023)) ([7c0f711](https://github.com/nwutils/nw-builder/commit/7c0f711d407c4f992e7897e9d590f03d3105db4e)), closes [#1017](https://github.com/nwutils/nw-builder/issues/1017) 527 | 528 | ## [Unreleased] 529 | 530 | ## [4.5.4] - 2024-01-23 531 | 532 | ### Changed 533 | 534 | - Migrate from `unzipper` to `yauzl-promise` to prevent corrupting files. 535 | 536 | ## [4.5.3] - 2023-12-20 537 | 538 | ### Changed 539 | 540 | - Wrap `unzipper` call inside Promise to prevent race condition. 541 | 542 | ## [4.5.2] - 2023-12-19 543 | 544 | ### Changed 545 | 546 | - Fix `yargs/helpers` import for cli usage. 547 | 548 | ## [4.5.1] - 2023-12-19 549 | 550 | ### Changed 551 | 552 | - Manually create symbolic links for MacOS builds. 553 | 554 | ## [4.5.0] - 2023-12-18 555 | 556 | ## Added 557 | 558 | - Use `unzipper` to decompress ZIP files. 559 | 560 | ## Changed 561 | 562 | - Use `tar` to extract tarballs. 563 | - Disable `options.nativeAddon`. 564 | 565 | ### Removed 566 | 567 | - Remove `yauzl-promise` since it does not preserve symlinks on MacOS. 568 | 569 | ## [4.4.2-beta.4] - 2023-11-03 570 | 571 | ### Changed 572 | 573 | - Use `yauzl-promise` to decompress MacOS build on MacOS platform. 574 | 575 | ### Removed 576 | 577 | - Native package `unzip` usage. 578 | 579 | ## [4.4.2-beta.3] - 2023-10-23 580 | 581 | ### Added 582 | 583 | - Align cache implementation with `nwjs/npm-installer` 584 | - `nw` module can [use the `options.cacheDir`](https://github.com/nwjs/npm-installer#retrieve-binaries-from-custom-download-location-or-file-path) to get cached NW.js binaries. 585 | 586 | ## [4.4.2-beta.2] - 2023-10-20 587 | 588 | ### Added 589 | 590 | - Node Native Addon support using GYP. To enable, set `options.nativeAddon` to `gyp`. 591 | 592 | ## [4.4.2-beta.1] - 2023-10-16 593 | 594 | ### Added 595 | 596 | - Managed Manifest mode. `options.ManagedManifest` defaults to `false`. 597 | - If `true`, then first `package.json` globbed is parsed as manifest. 598 | - If JSON type, object is parsed as manifest. 599 | - If string type, then resolve as file path to manifest. 600 | 601 | ## [4.4.2] - 2023-10-16 602 | 603 | ### Changed 604 | 605 | - Fix FFmpeg decompression. 606 | - Auto generate docs from JSDoc comments. 607 | - Improve TypeScript type definitions. 608 | - Fix get mode. 609 | - Refactor build mode. 610 | - Generate markdown docs from JSDocs. 611 | 612 | ## [4.4.1] - 2023-09-06 613 | 614 | ### Changed 615 | 616 | - Improve debug logging. 617 | - Fixed handling of argv. 618 | 619 | ## [4.4.0] - 2023-09-05 620 | 621 | ### Added 622 | 623 | - Cache community FFmpeg. 624 | - Move FFmpeg decompress function to relevant location 625 | 626 | ## [4.3.11] - 2023-09-05 627 | 628 | ### Changed 629 | 630 | - Separate download logic for NW.js and FFmpeg. 631 | 632 | ## [4.3.10] - 2023-08-21 633 | 634 | ### Removed 635 | 636 | - Do not copy the first `package.json` encountered to the root of `options.outDir` when `options.glob` is enabled. This may seem like a breaking change but it is actually reverting incorrect behaviour. 637 | 638 | ## [4.3.9] - 2023-08-15 639 | 640 | ### Changed 641 | 642 | - Some mac environments don't restore symlinks when using compressing lib. Now we will use system `unzip` command to extract zip files 643 | 644 | ## [4.3.8] - 2023-08-14 645 | 646 | ### Changed 647 | 648 | - Handle error during ffmpeg copy on some mac environments 649 | 650 | ## [4.3.7] - 2023-08-11 651 | 652 | ### Changed 653 | 654 | - Move community `ffmpeg` in the correct folder. 655 | 656 | ## [4.3.6] - 2023-08-11 657 | 658 | ### Changed 659 | 660 | - Move community `ffmpeg` in the correct folder. 661 | 662 | ## [4.3.5] - 2023-08-03 663 | 664 | ### Changed 665 | 666 | - Return promise in get mode to await it correctly. 667 | 668 | ## [4.3.4] - 2023-08-02 669 | 670 | ### Changed 671 | 672 | - Conditonally set Icon for Windows build. 673 | - Refactor `get` mode into a single file. 674 | 675 | ## [4.3.3] - 2023-07-25 676 | 677 | ### Changed 678 | 679 | - Set `NSHumanReadableCopyright` property in `*.app/Resources/en.lproj/InfoPlist.strings` to update copyright 680 | 681 | ### Removed 682 | 683 | - `NSHumanReadableCopyright` from `Info.plist` 684 | 685 | ## [4.3.2] - 2023-07-11 686 | 687 | ### Added 688 | 689 | - Descriptions and argument types for remaining cli arguments. 690 | 691 | ## [4.3.1] - 2023-07-07 692 | 693 | ### Changed 694 | 695 | - Replace the icon at `nwjs.app/Contents/Resources/app.icns` with the icon at `options.app.icon` file path. 696 | 697 | ### Removed 698 | 699 | - `xattr` package. The `com.apple.quarantine` flag should be handled by the developer. 700 | 701 | ## [4.3.0] - 2023-07-03 702 | 703 | ### Added 704 | 705 | - Compress `outDir` to `zip`, `tar` and `tgz` formats. 706 | - Specify log level via `options.logLevel`. 707 | - Add platform, arch, Node and NW.js info in debug log. 708 | - Add MacOS name, version, description and legal metadata 709 | - Removed redundant `options.app.icon` property (Refer to NW.js docs on how to set icon) 710 | 711 | ## [4.2.8] - 2023-06-29 712 | 713 | ### Changed 714 | 715 | - Refactor `zip` implementation. Use `compressing` instead of `archiver` package. 716 | - If `zip` is `true` or `"zip"`, then remove outDir after compression. (This was supposed to be the intented behavior all along). 717 | 718 | ## [4.2.7] - 2023-06-27 719 | 720 | ### Changed 721 | 722 | - Redownload `manifest.json` every time the `nwbuild` function is executed. 723 | - If `manifest.json` already exists and we are unable to connect to the `nwjs.io` domain, then use the existing manifest. 724 | - If no `manifest.json` exists in the cache dir, then the `validate` function will cache this and throw an error - preventing the build. 725 | 726 | ## [4.2.6] - 2023-06-20 727 | 728 | ### Changed 729 | 730 | - Preserve relative symbolic links of NW.js files during build mode 731 | 732 | ## [4.2.5] - 2023-06-20 733 | 734 | ### Changed 735 | 736 | - Rename executable using `options.app.name` value. 737 | 738 | ## [4.2.4] - 2023-06-18 739 | 740 | ### Changed 741 | 742 | - Migrate from `decompress` to `compressing` 743 | 744 | ## [4.2.3-beta.2] - 2023-06-16 745 | 746 | ### Changed 747 | 748 | - Preserve relative symbolic links during build mode 749 | 750 | ## [4.2.3-beta.1] - 2023-06-15 751 | 752 | ### Changed 753 | 754 | - Do not resolve `options.srcDir` when parsing `options` object. 755 | 756 | ## [4.2.3] - 2023-04-19 757 | 758 | ### Changed 759 | 760 | - Fix module imports which broke in [04ccd51](https://github.com/nwutils/nw-builder/commit/04ccd514f264f5590e5f86c42288904fe027901f) 761 | 762 | ## [4.2.2] - 2023-04-14 763 | 764 | ### Added 765 | 766 | - Validation for `options.version`. 767 | - Type definition file for `nwbuild` function. 768 | 769 | ## [4.2.1] - 2023-03-28 770 | 771 | ### Changed 772 | 773 | - Set `files` to `options.srcDir` if glob disabled preventing a `package.json` not found error. 774 | 775 | ## [4.2.0] - 2023-03-27 776 | 777 | ## Added 778 | 779 | - Glob flag defaulting to true. Currently file globbing is broken and it is recommended to set `glob` to false. 780 | 781 | ### Changed 782 | 783 | - Fixed `get` mode 784 | - Fixed `run` mode 785 | - Fixed `build` mode 786 | - Updated `get` mode docs 787 | 788 | ## [4.1.1-beta.2] - 2023-03-15 789 | 790 | ### Changed 791 | 792 | - Parse the first `package.json` file and treat it as the NW.js manifest. Copy it to `outDir/package.nw/package.json` for Linux and Windows and `outDir/nwjs.app/Contents/Resources/app.nw/package.json` for MacOS. 793 | 794 | To simplify your workflow, you can pass the path to the `package.json` first: 795 | 796 | ```shell 797 | nwbuild ./path/to/package.json ./app/**/* ./node_modules/**/* 798 | ``` 799 | 800 | Make sure your manifest file's `main` property points to a valid file path. In this case it might be: 801 | 802 | ```json 803 | { 804 | "main": "app/index.html" 805 | } 806 | ``` 807 | 808 | ## [4.1.1-beta.1] - 2023-03-14 809 | 810 | ### Added 811 | 812 | - `get` mode to only download NW.js binaries. Example use case: download during Node's `postinstall` hook: 813 | 814 | ```json 815 | { 816 | "scripts": { 817 | "postinstall": "nwbuild --mode=get --flavor=sdk" 818 | } 819 | } 820 | ``` 821 | 822 | - Check if NW.js's Node version matches host's Node version 823 | 824 | ## Changed 825 | 826 | - Fix undefined import for Windows configuration 827 | 828 | ## [4.1.1-beta.0] - 2023-03-12 829 | 830 | ### Changed 831 | 832 | - Remove false test for run mode. 833 | 834 | ## [4.1.1] - 2023-03-12 835 | 836 | ### Changed 837 | 838 | - Glob file and directory differently. 839 | - MacOS ARM build is no longer behind `beta` version. 840 | 841 | ## [4.1.0-beta.3] - 2023-03-01 842 | 843 | ### Added 844 | 845 | - Allow list `https://npmmirror.com/mirrors/nwjs/` and `https://npm.taobao.org/mirrors/nwjs/` mirrors. 846 | 847 | ## [4.1.0-beta.2] - 2023-02-29 848 | 849 | ### Changed 850 | 851 | - Do not convert srcDir files to absolute paths. 852 | - Copy files to correct location. 853 | 854 | ## [4.1.0-beta.1] - 2023-02-28 855 | 856 | ### Changed 857 | 858 | - Resolve path iff file path type is valid. 859 | 860 | ## [4.1.0-beta.0] - 2023-02-25 861 | 862 | ### Added 863 | 864 | - MacOS ARM support 865 | 866 | ## [4.1.0] - 2023-02-23 867 | 868 | ## Added 869 | 870 | - Use (community) prebuilt version of `ffmpeg` if the `ffmpeg` flag is `true` (defaults to `false`). 871 | 872 | ### Changed 873 | 874 | - `await` platform specific config steps 875 | 876 | ## [4.0.11] - 2023-02-5 877 | 878 | ### Changed 879 | 880 | - Security update `http-cache-semantics` to `v4.1.1`. 881 | 882 | ## [4.0.10] - 2023-02-05 883 | 884 | ### Added 885 | 886 | - `options.cli` flag to prevent `node-glob` from globbing already globbed files and erroring out with a `package.json not found in srcDir file glob patterns` message. 887 | 888 | ### Changed 889 | 890 | - Copy subdirectories of `options.srDir` in the correct location. 891 | 892 | ## [4.0.9] - 2023-02-03 893 | 894 | ### Added 895 | 896 | - Run and build demo app in CI. 897 | 898 | ### Changed 899 | 900 | - Fixed false positives in CI 901 | - Throw errors instead of returning them 902 | - Reject error object instead of exit code 903 | 904 | ## [4.0.8] - 2023-01-15 905 | 906 | ### Added 907 | 908 | - Flag to check if specific `nw` release is cached. [#772](https://github.com/nwutils/nw-builder/pull/772) 909 | 910 | ### Changed 911 | 912 | - Create `cacheDir`, `outDir` before getting release info. [#772](https://github.com/nwutils/nw-builder/pull/772) 913 | 914 | ## [4.0.7] - 2023-01-14 915 | 916 | ### Changed 917 | 918 | - Do not throw error if `nwbuild` is of `object` type. [#769](https://github.com/nwutils/nw-builder/pull/769) 919 | 920 | - Fix `package.json` path for `updateNotifier`. [#767](https://github.com/nwutils/nw-builder/pull/767) 921 | 922 | ## [4.0.6] - 2023-01-09 923 | 924 | ### Added 925 | 926 | - Warn about loss of permissions if building Linux or MacOS on Windows. [8793d4b](https://github.com/nwutils/nw-builder/commit/8793d4bf06288199fcaff340fda43c1e2fbcacbc) 927 | 928 | ### Changed 929 | 930 | - Fix error when `options.version` is `latest`. [33ef184](https://github.com/nwutils/nw-builder/commit/33ef184467f78fed94541e876da042b4ed99d443) 931 | 932 | ### Removed 933 | 934 | - CJS support [968f798](https://github.com/nwutils/nw-builder/commit/968f7980de59fea72ddac8e1d64628e561de1f9a) 935 | 936 | ## [4.0.5] - 2023-01-06 937 | 938 | ### Changed 939 | 940 | - Prevent duplicate globbing of `srcDir` files. [07901c9](https://github.com/nwutils/nw-builder/commit/07901c9e3930481ead0977b9be731765df28c087) 941 | 942 | ## [4.0.4] - 2023-01-06 943 | 944 | ### Changed 945 | 946 | - Convert `srcDir` type from `string[]` to `string`. [1a699af](https://github.com/nwutils/nw-builder/commit/1a699af300782e0847333bb7ad945dbde8940350) 947 | 948 | ## [4.0.3] - 2023-01-06 949 | 950 | ### Added 951 | 952 | - File globing. [#749](https://github.com/nwutils/nw-builder/pull/749) 953 | 954 | - Linux and Windows configuration options. [#729](https://github.com/nwutils/nw-builder/pull/729) 955 | 956 | ### Changed 957 | 958 | - Skip modification of Windows executable if platform is not Windows or Wine is not installed. [#739](https://github.com/nwutils/nw-builder/pull/739) 959 | 960 | - Run mode should only require `srcDir`, `version` and `flavor`. [#718](https://github.com/nwutils/nw-builder/pull/718) 961 | 962 | ## [4.0.2] - 2022-11-30 963 | 964 | ### Added 965 | 966 | - Allow user to rename NW executable. [#712](https://github.com/nwutils/nw-builder/pull/712) 967 | 968 | ### Changed 969 | 970 | - Fix MacOS build. [#717](https://github.com/nwutils/nw-builder/pull/717) 971 | 972 | - CJS support via `esbuild`. [#713](https://github.com/nwutils/nw-builder/pull/713) 973 | 974 | ## [4.0.1] - 2022-11-20 975 | 976 | ### Added 977 | 978 | - Support for Desktop Entry file. [#690](https://github.com/nwutils/nw-builder/pull/690) 979 | 980 | ### Changed 981 | 982 | - Resolve promise in `close` event with respect to compression. [#698](https://github.com/nwutils/nw-builder/pull/698) 983 | 984 | - Check for release info after downloading NW binaries in `cacheDir`. [#697](https://github.com/nwutils/nw-builder/pull/697) 985 | 986 | ## [4.0.0] - 2022-11-16 987 | 988 | ### Added 989 | 990 | - Rename Helper apps. [#687](https://github.com/nwutils/nw-builder/pull/687) 991 | 992 | - MacOS support. [#685](https://github.com/nwutils/nw-builder/pull/685) 993 | 994 | - Check for `nwbuild` object in `package.json`. [#684](https://github.com/nwutils/nw-builder/pull/684) 995 | 996 | ## [3.8.6] - 2022-09-22 997 | 998 | - Fix mac and windows build 999 | 1000 | ## [3.8.5] - 2022-09-20 1001 | 1002 | ### Added 1003 | 1004 | - `nwbuild` function which accidently got removed. 1005 | 1006 | ### Changed 1007 | 1008 | - Update usage docs for `nwbuild` 1009 | 1010 | ## [3.8.4] - 2022-09-20 1011 | 1012 | ### Changed 1013 | 1014 | - Refactor download function 1015 | 1016 | ## [3.8.3-beta.1] 1017 | 1018 | ### Changed 1019 | 1020 | - Check for first instance of `package.json` 1021 | 1022 | ## [3.8.3] - 2022-08-26 1023 | 1024 | ### Changed 1025 | 1026 | - `platforms` argument also accepts comma separated (without spaces) values 1027 | 1028 | ## [3.8.2] - 2022-08-08 1029 | 1030 | ### Added 1031 | 1032 | - Support for multiple file paths 1033 | 1034 | ## [3.8.1] - 2022-07-18 1035 | 1036 | ### Changed 1037 | 1038 | - Fix regex to match `package.json` _files_ only 1039 | 1040 | ## [3.8.0] - 2022-07-11 1041 | 1042 | ## Added 1043 | 1044 | - `mode` option which defaults to run 1045 | - `nwbuild` function 1046 | - `quiet` option to documentation 1047 | 1048 | ## Changed 1049 | 1050 | - CLI options by matching them to the API 1051 | 1052 | ## [3.7.4] - 2022-06-06 1053 | 1054 | ## Removed 1055 | 1056 | - Remove `Version` from `CFBundleShortVersionString` [#576](https://github.com/nwjs-community/nw-builder/pull/576) 1057 | 1058 | ## [3.7.2] - 2022-06-02 1059 | 1060 | ## Added 1061 | 1062 | - Added options `buildType`, `macCredits`, `macPlist`, `zip`, `zipOptions` to CLI [#575](https://github.com/nwjs-community/nw-builder/pull/575) 1063 | 1064 | ## Changed 1065 | 1066 | - Update lint command [#575](https://github.com/nwjs-community/nw-builder/pull/575) 1067 | 1068 | ## [3.7.1] - 2022-06-02 1069 | 1070 | ## Changed 1071 | 1072 | - Add `EditorConfig` [#574](https://github.com/nwjs-community/nw-builder/pull/574) 1073 | - Fix build step for Windows x64 platform [#572](https://github.com/nwjs-community/nw-builder/pull/572) 1074 | - Refactor `platforms` object [#571](https://github.com/nwjs-community/nw-builder/pull/571) 1075 | 1076 | ## [3.7.0] - 2022-05-30 1077 | 1078 | ## Added 1079 | 1080 | - Optional zip file merging for Windows and Linux [#567](https://github.com/nwjs-community/nw-builder/pull/567) 1081 | - Add code of conduct [#560](https://github.com/nwjs-community/nw-builder/pull/560) 1082 | 1083 | ## Changed 1084 | 1085 | - Update contributing guide [#569](https://github.com/nwjs-community/nw-builder/pull/569) 1086 | - Switch from TypeScript to JSDocs [#568](https://github.com/nwjs-community/nw-builder/pull/568) 1087 | - Set window icon with `rcedit` [#566](https://github.com/nwjs-community/nw-builder/pull/566) 1088 | - Refactor `checkCache` [#565](https://github.com/nwjs-community/nw-builder/pull/565) 1089 | - Simplify demo 1090 | - Refactor `detectCurrentPlatform` [#564](https://github.com/nwjs-community/nw-builder/pull/564) 1091 | - Update dependencies [#561](https://github.com/nwjs-community/nw-builder/pull/561) [#532](https://github.com/nwjs-community/nw-builder/pull/532) 1092 | 1093 | ## Removed 1094 | 1095 | ## [3.6.0] - 2022-05-18 1096 | 1097 | ### Added 1098 | 1099 | - GitHub Actions for CICD [#552](https://github.com/nwjs-community/nw-builder/pull/552) 1100 | - Support multiple locales on OSX [#389](https://github.com/nwjs-community/nw-builder/pull/389) 1101 | - Pull request and issue template [#553](https://github.com/nwjs-community/nw-builder/pull/553) 1102 | 1103 | ### Changed 1104 | 1105 | - Dependencies [#550](https://github.com/nwjs-community/nw-builder/pull/550) 1106 | - Documentation [#540](https://github.com/nwjs-community/nw-builder/pull/540) [#553](https://github.com/nwjs-community/nw-builder/pull/553) [#555](https://github.com/nwjs-community/nw-builder/pull/555) 1107 | - Improve run mode by detecting current platform to prevent downloading additional binaries 1108 | 1109 | ### Removed 1110 | 1111 | - Travis 1112 | - AppVeyor 1113 | - JSHint 1114 | - EditorConfig 1115 | 1116 | ## [3.5.7] 1117 | 1118 | ### Security 1119 | 1120 | - Source platform overrides module into the project instead of it being an extenal dependency. This helped us get rid of a vulnerable lodash version. See https://github.com/nwjs-community/nw-builder/issues/500 1121 | 1122 | ## [3.5.1] - 2017-10-19 1123 | 1124 | ### Added 1125 | 1126 | - Add option.winVersionString for accurate process name. See https://github.com/nwjs-community/nw-builder/pull/459. 1127 | 1128 | ### Fixed 1129 | 1130 | - Small platform overrides fix. See https://github.com/nwjs-community/nw-builder/pull/477/files. 1131 | 1132 | ## [3.4.1] - 2017-06-05 1133 | 1134 | ### Removed 1135 | 1136 | - The `bluebird` dependency. We're now using native promises instead. 1137 | 1138 | ## [3.4.0] - 2017-05-28 1139 | 1140 | ### Added 1141 | 1142 | - If using the package programmatically and it's out of date, a message will be shown (this was always the case for the CLI). 1143 | - There is now a README in every directory (with at least a single sentence summarizing the directory) to help with onboarding contributors. 1144 | 1145 | ### Changed 1146 | 1147 | - Some dependencies are updated. 1148 | 1149 | ### Removed 1150 | 1151 | - `osx32` is removed from the default list of platforms. Thanks to [@preaction](https://github.compreaction) (PR [#439](https://github.com/nwjs-community/nw-builder/pull/439)). 1152 | - An unnecessary `rcedit` dependency is removed. 1153 | 1154 | ### Fixed 1155 | 1156 | - For Node 7+ users, you won't see a `os.tmpDir` deprecation warning anymore. 1157 | 1158 | --- 1159 | 1160 | ## Old format 1161 | 1162 | - 2017-05-22 `3.2.3` Fix for caching when a version is specified (thanks @piwonesien for the help). 1163 | - 2017-05-20 `3.2.2` Fix: when using the `nwbuild` in run mode, the `-p` option was ignored and the current platform was always used. 1164 | - 2017-05-16 `3.2.1` Fix: NW.js 0.22.0+ apps didn't open. 1165 | - 2017-02-12 `3.2.0` Defaults to HTTPS now, added `manifestUrl` option, and bumped some dependencies. 1166 | - 2016-10-09 `3.1.2` Fix for passing array as files option when running app (plus some security fixes). 1167 | - 2016-10-09 `3.1.1` Fix for flavor feature when using CLI. 1168 | - 2016-09-14 `3.1.0` Ability to select any flavor of NW.js, not just `sdk`. 1169 | - 2016-08-28 `3.0.0` bumping graceful-fs-extra dependency to 2.0.0. 1170 | - 2016-08-14 `2.2.7` fix for macIcns option when using NW.js 0.12.3 1171 | - 2016-07-31 `2.2.6` fix for OS X caching 1172 | - 2016-07-03 `2.2.5` fix for update-notifier usage in bin 1173 | - 2016-07-03 `2.2.4` fix for syntax error in CLI 1174 | - 2016-07-02 `2.2.3` a few small fixes for the run option and more 1175 | - 2016-07-02 `2.2.2` fix for cache check of some legacy versions 1176 | - 2016-07-02 `2.2.1` supports newer NW.js versions (via http://nwjs.io/versions.json), plus other fixes. 1177 | - 2015-12-18 `2.2.0` added `zip` option. 1178 | - 2015-12-06 `2.1.0` added `cacheDir` command-line option, fix for no info being passed back, etc. 1179 | - 2015-06-28 `2.0.2` put upper bound to semver check for windows. 1180 | - 2015-06-14 `2.0.1` safer validation of versions. 1181 | - 2015-06-14 `2.0.0` changed to nw-builder, etc. 1182 | - 2015-05-05 `1.0.12` when using latest NW.js version, it's first validated that it's not an alpha version (fixes [#222](https://github.com/nwjs/nw-builder/issues/222)). Plus a fix for the `winIco` & `macIcns` command line options 1183 | - 2015-01-29 `1.0.8` fixed EMFILE errors (see [#147](https://github.com/nwjs/nw-builder/issues/147) [#148](https://github.com/nwjs/nw-builder/pull/148)) 1184 | - 2015-01-21 `1.0.7` fixed about screen when copyright is not supplied 1185 | - 2015-01-15 `1.0.6` fixed downloads for nw.js version 0.12.0-alpha1 1186 | - 2015-01-15 `1.0.5` fixed downloads for NW.js versions < 0.12.0-alpha 1187 | - 2014-12-12 `1.0.0` 64-bit support, improved platform-overrides and no more EMFILE errors. 1188 | - 2014-12-07 `0.4.0` macPlist CFBundleIdentifier is generated from `package.json` (see [#131](https://github.com/nwjs/nw-builder/pull/131)) 1189 | - 2014-11-14 `0.3.0` macPlist option improvements (see [#96](https://github.com/nwjs/nw-builder/pull/96)) 1190 | - 2014-10-30 `0.2.0` adds support for platform-specific manifest overrides (see [#94](https://github.com/nwjs/nw-builder/pull/94)) 1191 | - 2014-08-19 `0.1.2` adds a progress bar to downloads, fixes downloading through a proxy, fixed winIco, bug fixes 1192 | - 2014-08-01 `0.1.0` use app filename for generated executables, optimized version checking, (known issue: `winIco` on windows) 1193 | - 2014-07-31 `0.0.4` fixed compatibility with nodewebkit 0.10.0 1194 | - 2014-04-20 Added run option, bug fixes 1195 | - 2014-04-13 Preview Release 1196 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # "No Ideologies" Code of Conduct 2 | 3 | The following are the guidelines we expect our community members and maintainers to follow. 4 | 5 | --- 6 | 7 | ## Terminology and Scope 8 | 9 | **What defines a "maintainer"?** 10 | 11 | - A maintainer is anyone that interacts with the community on behalf of this project. Amount of code written is not a qualifier. A maintainer may include those who solely help in support roles such as in resolving issues, improving documentation, administrating or moderating forums/chatrooms, or any other non-coding specific roles. Maintainers also include those that are responsible for the building and upkeep of the project. 12 | 13 | **What defines a "community member"?** 14 | 15 | - Anyone interacting with this project directly, including maintainers. 16 | 17 | **What is the scope of these guidelines?** 18 | 19 | - These guidelines apply only to this project and forms of communication directly related to it, such as issue trackers, forums, chatrooms, and in person events specific to this project. If a member is violating these guidelines outside of this project or on other platforms, that is beyond our scope and any grievances should be handled on those platforms. 20 | 21 | **Discussing the guidelines:** 22 | 23 | - Discussions around these guidelines, improving, updating, or altering them, is permitted so long as the discussions do not violate any existing guidelines. 24 | 25 | --- 26 | 27 | ## Guidelines 28 | 29 | ### Guidelines for community members 30 | 31 | This project is technical in nature and not based around any particular non-technical ideology. As such, communication that is based primarily around ideologies unrelated to the technologies used by this repository are not permitted. 32 | 33 | Any discussion or communication that is primarily focused around an ideology, be it about race, gender, politics, religion, or anything else non-technical, is not allowed. Everyone has their own ideological preferences, beliefs, and opinions. We do not seek to marginalize, exclude, or judge anyone for their ideologies. To prevent conflict between those with differing or opposing ideologies, all communication on these subjects are prohibited. Some discussions around these topics may be important, however this project is not the proper channel for these discussions. 34 | 35 | ### Guidelines for maintainers 36 | 37 | - Maintainers must abide by the same rules as all other community members mentioned above. However, in addition, maintainers are held to a higher standard, explained below. 38 | - Maintainers should answer all questions politely. 39 | - If someone is upset or angry about something, it's probably because it's difficult to use, so thank them for bringing it to your attention and address ways to solve the problem. Maintainers should focus on the content of the message, and not on how it was delivered. 40 | - A maintainer should seek to update members when an issue they brought up is resolved. 41 | 42 | --- 43 | 44 | ## Appropriate response to violations 45 | 46 | How to respond to a community member or maintainer violating a guideline. 47 | 48 | 1. If an issue is created that violates a guideline a maintainer should close and lock the issue, explaining "This issue is in violation of our code of conduct. Please review it before posting again." with a link to this document. 49 | 1. If a member repeatedly violates the guidelines established in this document, they should be politely warned that continuing to violate the rules may result in being banned from the community. This means revoking access and support to interactions relating directly to the project (issue trackers, chatrooms, forums, in person events, etc.). However, they may continue to use the technology in accordance with its license. 50 | 1. If a maintainer is in violation of a guideline, they should be informed of such with a link to this document. If additional actions are required of the maintainer but not taken, then other maintainers should be informed of these inactions. 51 | 1. If a maintainer repeatedly violates the guidelines established in this document, they should be politely warned that continuing to violate the rules may result in being banned from the community. This means revoking access and support to interactions relating directly to the project (issue trackers, chatrooms, forums, in person events, etc.). However, they may continue to use the technology in accordance with its license. In addition, future contributions to this project may be ignored as well. 52 | 53 | --- 54 | 55 | Based on version 1.0.3 from https://github.com/CodifiedConduct/coc-no-ideologies 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-2024 NW.js Utilities 4 | Copyright (c) 2014-2021 Steffen Müller 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nw-builder 2 | 3 | [![npm](https://img.shields.io/npm/v/nw-builder/latest)](https://www.npmjs.com/package/nw-builder/v/latest) 4 | [![Join the chat at https://gitter.im/nwjs/nw-builder](https://badges.gitter.im/repo.svg)](https://app.gitter.im/#/room/#nwjs_nw-builder:gitter.im) 5 | 6 | Build [NW.js](https://github.com/nwjs/nw.js) applications for Mac, Windows and Linux. 7 | 8 | ## Major Features 9 | 10 | - Get, run or build applications. 11 | - Integrate [FFmpeg community builds](https://github.com/nwjs-ffmpeg-prebuilt/nwjs-ffmpeg-prebuilt) 12 | - Configure executable fields, icons and rename Helper apps 13 | - Downloading from mirrors 14 | 15 | ## Table of Contents 16 | 17 | - [Installation](https://github.com/nwutils/nw-builder#install) 18 | - [Usage](https://github.com/nwutils/nw-builder#usage) 19 | - [Concepts](https://github.com/nwutils/nw-builder#concepts) 20 | - [API Reference](https://github.com/nwutils/nw-builder#api-reference) 21 | - [Guides](https://github.com/nwutils/nw-builder#guides) 22 | - [Contributing](https://github.com/nwutils/nw-builder#contributing) 23 | - [Roadmap](https://github.com/nwutils/nw-builder#roadmap) 24 | - [FAQ](https://github.com/nwutils/nw-builder#faq) 25 | - [License](https://github.com/nwutils/nw-builder#license) 26 | 27 | ## Install 28 | 29 | ```shell 30 | npm i -D nw-builder 31 | ``` 32 | 33 | Every NW.js release includes a modified Node.js binary at a specific version. It is recommended to [install](https://nodejs.org/en/download/package-manager) exactly that version on the host system. Not doing so may download ABI incompatible Node modules. Consult the NW.js [versions manifest](https://nwjs.io/versions) for what Node.js version to install. It is recommended to use a Node version manager (such as [volta](https://volta.sh), n, nvm, or nvm-windows) to be able to easily install and switch between Node versions. 34 | 35 | ## Usage 36 | 37 | This package can be used via a command line interface, be imported as a JavaScript module, or configured via the Node manifest as a JSON object. If options are defined in Node manifest, then they will be used over options defined in CLI or JavaScript API. 38 | 39 | CLI interface: 40 | 41 | ```shell 42 | nwbuild --mode=build --glob=false --flavor=sdk --cacheDir=./node_modules/nw /path/to/project 43 | ``` 44 | 45 | > Note: While using the CLI interface, `/path/to/project` refers to `options.srcDir` in the JavaScript API or JSON object. 46 | 47 | ESM import: 48 | 49 | ```javascript 50 | import nwbuild from "nw-builder"; 51 | ``` 52 | 53 | CJS import: 54 | 55 | ```javascript 56 | let nwbuild; 57 | import("nw-builder") 58 | .then((moduleObject) => { 59 | nwbuild = moduleObject; 60 | }) 61 | .catch((error) => { 62 | console.error(error); 63 | }); 64 | 65 | nwbuild({ 66 | mode: "build", 67 | glob: false, 68 | flavor: "sdk", 69 | cacheDir: "./node_modules/nw", 70 | srcDir: "/path/to/project", 71 | }); 72 | ``` 73 | 74 | Node manifest usage: 75 | 76 | ```json 77 | { 78 | "nwbuild": { 79 | "mode": "build", 80 | "glob": false, 81 | "flavor": "sdk", 82 | "cacheDir": "./node_modules/nw", 83 | "srcDir": "/path/to/project" 84 | } 85 | } 86 | ``` 87 | 88 | See `nw-builder` in action by building the demo application. 89 | 90 | 1. `git clone https://github.com/nwutils/nw-builder` 91 | 1. Run `npm run demo:bld:linux && npm run demo:exe:linux` to build and execute a Linux application. 92 | 1. Run `npm run demo:bld:osx && npm run demo:exe:osx` to build and execute a MacOS application. 93 | 1. Run `npm run demo:bld:win && npm run demo:exe:win` to build and execute a Windows application. 94 | 95 | > From here on we will show `nw-builder` functionality by using the JavaScript module. Please note that the same functionality applies when using a command line or manifest file. 96 | 97 | ## Concepts 98 | 99 | `nw-builder` can get, run and build NW.js applications. We refer to them as get, run and build modes. 100 | 101 | ### Get Mode 102 | 103 | By default you get the normal build of the latest NW.js release for your specific platform and arch. For more information, please refer to the API reference. 104 | 105 | ```javascript 106 | nwbuild({ 107 | mode: "get" 108 | }); 109 | ``` 110 | 111 | Get the community built FFmeg which contains proprietary codecs. This options is disabled by default. Please read the [license's constraints](https://nwjs.readthedocs.io/en/latest/For%20Developers/Enable%20Proprietary%20Codecs/#get-ffmpeg-binaries-from-the-community) before enabling this option. 112 | 113 | ```javascript 114 | nwbuild({ 115 | mode: "get", 116 | ffmpeg: true 117 | }); 118 | ``` 119 | 120 | Get Node headers if you have to rebuild Node addons. 121 | 122 | ```javascript 123 | nwbuild({ 124 | mode: "get", 125 | nativeAddon: "gyp" 126 | }); 127 | ``` 128 | 129 | ### Run Mode 130 | 131 | ```javascript 132 | const nwProcess = await nwbuild({ 133 | mode: "run", 134 | srcDir: "./app", 135 | glob: false, 136 | }); 137 | ``` 138 | 139 | Note: The `nwProcess` is a [Node.js process](https://nodejs.org/api/process.html#process) 140 | 141 | ### Build Mode 142 | 143 | Build with defaults: 144 | 145 | ```javascript 146 | nwbuild({ 147 | mode: "build", 148 | }); 149 | ``` 150 | 151 | #### Managed Manifest 152 | 153 | 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. 154 | 155 | Setting it to `true` will parse the first Node manifest it encounters as the NW manifest. 156 | 157 | ```javascript 158 | nwbuild({ 159 | mode: "build", 160 | managedManifest: true, 161 | }); 162 | ``` 163 | 164 | Setting it to a `string` implies that you are passing the file path to the NW manifest. 165 | 166 | ```javascript 167 | nwbuild({ 168 | mode: "build", 169 | managedManifest: "./nw.js", 170 | }); 171 | ``` 172 | 173 | Setting it to a `object` implies you are directly passing the NW manifest as a JavaScript object. 174 | 175 | ```javascript 176 | nwbuild({ 177 | mode: "build", 178 | managedManifest: { 179 | name: "nwdemo", 180 | main: "index.html" 181 | }, 182 | }); 183 | ``` 184 | 185 | #### Rebuild Node addons 186 | 187 | > Currently this feature is disabled and it may be removed in the future. 188 | 189 | It only builds node addons which have a `binding.gyp` file in the `srcDir`. There are plans to support nan, cmake, ffi and gn and auto rebuild native addons which are installed as node modules. 190 | 191 | ```javascript 192 | nwbuild({ 193 | mode: "build", 194 | nodeAddon: "gyp", 195 | }); 196 | ``` 197 | 198 | We recommend rebuilding Node addons for NW.js via `node-gyp` if you are using NW.js v0.83.0 or above. 199 | 200 | ```shell 201 | node-gyp rebuild --target=22.2.0 --nodedir=/path/to/nw/node/headers 202 | ``` 203 | 204 | NW.js's Node version should match the Node version on the host machine due to [ABI differences in V8](https://github.com/nwjs/nw.js/issues/5025). 205 | 206 | ## API Reference 207 | 208 | Options 209 | 210 | | Name | Type | Default | Description | 211 | | ---- | ------- | --------- | ----------- | 212 | | mode | `"get" \| "run" \| "build"` | `"build"` | Choose between get, run or build mode | 213 | | version | `string \| "latest" \| "stable"` | `"latest"` | Runtime version | 214 | | flavor | `"normal" \| "sdk"` | `"normal"` | Runtime flavor | 215 | | platform | `"linux" \| "osx" \| "win"` | | Host platform | 216 | | arch | `"ia32" \| "x64" \| "arm64"` | | Host architecture | 217 | | downloadUrl | `"https://dl.nwjs.io" \| "https://npm.taobao.org/mirrors/nwjs" \| https://npmmirror.com/mirrors/nwjs \| "https://github.com/corwin-of-amber/nw.js/releases/"` | `"https://dl.nwjs.io"` | Download server. Supports file systems too (for example `file:///home/localghost/nwjs_mirror`) | 218 | | manifestUrl | `"https://nwjs.io/versions" \| "https://raw.githubusercontent.com/nwutils/nw-builder/main/src/util/osx.arm.versions.json"` | `"https://nwjs.io/versions"` | Versions manifest | 219 | | cacheDir | `string` | `"./cache"` | Directory to cache NW binaries | 220 | | cache | `boolean` | `true`| If true the existing cache is used. Otherwise it removes and redownloads it. | 221 | | ffmpeg | `boolean` | `false`| If true the chromium ffmpeg is replaced by community version with proprietary codecs. | 222 | | logLevel | `"error" \| "warn" \| "info" \| "debug"` | `"info"`| Specify level of logging. | 223 | | shaSum | `boolean` | `true` | Flag to enable/disable shasum checks. | 224 | | srcDir | `string` | `"./"` | File paths to application code | 225 | | argv | `string[]` | `[]` | Command line arguments to pass to NW executable in run mode. You can also define these in `chromium-args` in NW.js manifest. | 226 | | glob | `boolean` | `true`| If true file globbing is enabled when parsing `srcDir`. | 227 | | outDir | `string` | `"./out"` | Directory to store build artifacts | 228 | | managedManifest | `boolean \| string \| object` | `false` | Managed manifest | 229 | | nodeAddon | `false \| "gyp"` | `false` | Rebuild Node native addons | 230 | | zip | `boolean \| "zip" \| "tar" \| "tgz"` | `false`| If true, "zip", "tar" or "tgz" the `outDir` directory is compressed. | 231 | | app | `LinuxRc \| WinRc \| OsxRc` | Additional options for each platform. (See below.) 232 | 233 | ### `app` configuration object 234 | 235 | This object defines additional properties used for building for a specific platform. For each platform, pass an object with appropriate values: 236 | 237 | #### Windows-specific options (`WinRc`) 238 | 239 | | Name | Type | Default | Description | 240 | | ---- | ------- | --------- | ----------- | 241 | | `icon` | `string` | `undefined` | The path to the icon file. It should be a .ico file. (**WARNING**: Please define the icon in the NW.js manifest instead) | 242 | | `name` | `string` | Value of `name` in NW.js manifest | The name of the application | 243 | | `version` | `string` | Value of `version` in NW.js manifest | The version of the application | 244 | | `comments` | `string` | `undefined` | Additional information that should be displayed for diagnostic purposes. | 245 | | `company` | `string` | Value of `author` in NW.js manifest | Company that produced the file—for example, Microsoft Corporation or Standard Microsystems Corporation, Inc. This string is required. | 246 | | `fileDescription` | `string` | Value of `description` in NW.js manifest | File description to be presented to users. This string may be displayed in a list box when the user is choosing files to install. For example, Keyboard Driver for AT-Style Keyboards. This string is required. | 247 | | `fileVersion` | `string` | Value of `version` or value of `version` in NW.js manifest | Version number of the file. For example, 3.10 or 5.00.RC2. This string is required. | 248 | | `internalName` | `string` | Value of `name` in NW.js manifest |Internal name of the file, if one exists—for example, a module name if the file is a dynamic-link library. If the file has no internal name, this string should be the original filename, without extension. This string is required. | 249 | | `legalCopyright` | `string` | `undefined` | Copyright notices that apply to the file. This should include the full text of all notices, legal symbols, copyright dates, and so on. This string is optional. | 250 | | `legalTrademark` | `string` | `undefined` | Trademarks and registered trademarks that apply to the file. This should include the full text of all notices, legal symbols, trademark numbers, and so on. This string is optional. | 251 | | `originalFilename` | `string` | Value of `name` option | Original name of the file, not including a path. This information enables an application to determine whether a file has been renamed by a user. The format of the name depends on the file system for which the file was created. This string is required. | 252 | | `privateBuild` | `string` | `undefined` | Information about a private version of the file—for example, Built by TESTER1 on \\TESTBED. | 253 | | `productName` | `string` | `name` in NW.js manifest | Name of the product with which the file is distributed. This string is required. | 254 | | `productVersion` | `string` | Value of `version` in NW.js manifest | Version of the product with which the file is distributed—for example, 3.10 or 5.00.RC2. | 255 | | `specialBuild` | `string` | `undefined` | Text that specifies how this version of the file differs from the standard version—for example, Private build for TESTER1 solving mouse problems on M250 and M250E computers. | 256 | | `languageCode` | `number` | `1033` | Language of the file, defined by Microsoft, see: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a | 257 | 258 | #### Linux-specific options (`LinuxRc`) 259 | 260 | | Name | Type | Description | 261 | | ---- | ------- | ----------- | 262 | | name | `string` | Name of the application | 263 | | genericName | `string` | Generic name of the application | 264 | | noDisplay | `boolean` | If true the application is not displayed | 265 | | comment | `string` | Tooltip for the entry, for example "View sites on the Internet". | 266 | | icon | `string` | Icon to display in file manager, menus, etc. (**WARNING**: Please define the icon in the NW.js manifest instead) | 267 | | hidden | `boolean` | TBD | 268 | | onlyShowIn | `string[]` | A list of strings identifying the desktop environments that should display a given desktop entry | 269 | | notShowIn | `string[]` | A list of strings identifying the desktop environments that should not display a given desktop entry | 270 | | dBusActivatable | `boolean` | A boolean value specifying if D-Bus activation is supported for this application | 271 | | tryExec | `string` | Path to an executable file on disk used to determine if the program is actually installed | 272 | | exec | `string` | Program to execute, possibly with arguments. | 273 | | path | `string` | If entry is of type Application, the working directory to run the program in. | 274 | | terminal | `boolean` | Whether the program runs in a terminal window. | 275 | | actions | `string[]` | Identifiers for application actions. | 276 | | mimeType | `string[]` | The MIME type(s) supported by this application. | 277 | | categories | `string[]` | Categories in which the entry should be shown in a menu | 278 | | implements | `string[]` | A list of interfaces that this application implements. | 279 | | keywords | `string[]` | A list of strings which may be used in addition to other metadata to describe this entry. | 280 | | startupNotify | `boolean` | If true, it is KNOWN that the application will send a "remove" message when started with the DESKTOP_STARTUP_ID environment variable set. If false, it is KNOWN that the application does not work with startup notification at all. | 281 | | startupWMClass | `string` | If specified, it is known that the application will map at least one window with the given string as its WM class or WM name hin | 282 | | prefersNonDefaultGPU | `boolean` | If true, the application prefers to be run on a more powerful discrete GPU if available. | 283 | | singleMainWindow | `string` | If true, the application has a single main window, and does not support having an additional one opened. | 284 | 285 | #### MacOS-specific options (`OsxRc`) 286 | 287 | | Name | Type | Description | 288 | | ---- | ------- | ----------- | 289 | | name | `string` | The name of the application | 290 | | icon | `string` | The path to the icon file. It should be a .icns file. (**WARNING**: Please define the icon in the NW.js manifest instead) | 291 | | LSApplicationCategoryType | `string` | The category that best describes your app for the App Store. | 292 | | CFBundleIdentifier | `string` | A unique identifier for a bundle usually in reverse DNS format. | 293 | | CFBundleName | `string` | A user-visible short name for the bundle. | 294 | | CFBundleDisplayName | `string` | The user-visible name for the bundle. | 295 | | CFBundleSpokenName | `string` | A replacement for the app name in text-to-speech operations. | 296 | | CFBundleVersion | `string` | The version of the build that identifies an iteration of the bundle. | 297 | | CFBundleShortVersionString | `string` | The release or version number of the bundle. | 298 | | NSHumanReadableCopyright | `string` | A human-readable copyright notice for the bundle. | 299 | | NSLocalNetworkUsageDescription | `string` | A human-readable description of why the application needs access to the local network. | 300 | 301 | 302 | ## Guides 303 | 304 | ### Get unofficial MacOS builds 305 | 306 | If you're running older Apple machines, you can download the unofficial builds. 307 | 308 | > Note: You will have to manually remove quarantine flag. 309 | 310 | ```shell 311 | sudo xattr -r -d com.apple.quarantine /path/to/nwjs.app 312 | ``` 313 | 314 | ```javascript 315 | nwbuild({ 316 | mode: "get", 317 | platform: "osx", 318 | arch: "arm64", 319 | downloadUrl: "https://github.com/corwin-of-amber/nw.js/releases/download", 320 | manifestUrl: "https://raw.githubusercontent.com/nwutils/nw-builder/main/src/util/osx.arm.versions.json", 321 | }); 322 | ``` 323 | 324 | > Note: Community FFmpeg binaries may not be available for unofficial builds. You will have to rebuild them yourself. 325 | 326 | ### Get binaries via mirrors 327 | 328 | China mirror: 329 | 330 | ```javascript 331 | nwbuild({ 332 | mode: "get", 333 | downloadUrl: "https://npm.taobao.org/mirrors/nwjs", 334 | }); 335 | ``` 336 | 337 | Singapore mirror: 338 | 339 | ```javascript 340 | nwbuild({ 341 | mode: "get", 342 | downloadUrl: "https://cnpmjs.org/mirrors/nwjs/", 343 | }); 344 | ``` 345 | 346 | ### Let `nw-builder` manage your native addons 347 | 348 | > Note: this behaviour is buggy and quite limited. This guide is to show what will be possible in the coming minor releases. 349 | 350 | ```javascript 351 | nwbuild({ 352 | mode: "build", 353 | managedManifest: true, 354 | nativeAddon: "gyp", 355 | }); 356 | ``` 357 | 358 | ## Contributing 359 | 360 | ### External contributor 361 | 362 | - We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) style of commit messages. 363 | - On opening a new PR, the comments will guide you on how to construct the new PR. 364 | - Pull requests are squashed and merged onto the `main` branch. 365 | - PR title is used as commit's first line, PR description is used as commit body. 366 | - Only commit messages starting with `fix`, `feat` and `chore` are recognised by the Release Please bot. 367 | - Lint your code before commiting your change. 368 | - Add tests whenever possible. 369 | 370 | ### Maintainer guidelines 371 | 372 | - Approve pull requests before merging. 373 | - Enforce conventional commits before merging pull requests. 374 | - A commit's first line should be formatted as `[optional scope]: `. 375 | - A commit's body should have a description of changes in bullet points followed by any links it references or issues it fixes or closes. 376 | - Google's Release Please Action is used to update the changelog, bump the package version and generate GitHub releases. 377 | - NPM Publish Action publishes to `npm` if there is a version bump. 378 | 379 | ## Roadmap 380 | 381 | ### Bugs 382 | 383 | - Managed Manifest is broken. If glob is disabled and srcDir has no package.json, build fails. 384 | - Add back error, info, warn and debug logs 385 | 386 | ### Features 387 | 388 | - feat(get): support canary releases 389 | - feat(pkg): add `AppImage` installer 390 | - feat(pkg): add `NSIS` installer 391 | - feat(pkg): add `DMG` installer 392 | - feat(get): add Linux ARM unofficial support 393 | - feat(bld): add source code protection 394 | - feat(pkg): add code signing 395 | 396 | ### Chores 397 | 398 | - chore(docs): don't store JSDoc definitions in `typedef`s - get's hard to understand during development. 399 | - chore: annotate file paths as `fs.PathLike` instead of `string`. 400 | - chore(bld): factor out core build step 401 | - chore(bld): factor out linux config 402 | - chore(bld): factor out windows config 403 | - chore(bld): factor out native addon 404 | - chore(bld): factor out compressing 405 | - chore(bld): factor out managed manifest 406 | - chore(bld): move `.desktop` entry file logic to `create-desktop-shortcuts` package 407 | 408 | ## FAQ 409 | 410 | ## License 411 | 412 | MIT License. 413 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a Vulnerability 2 | 3 | If you discover a security vulnerability within `nw-builder`, please submit a report via the Github's Private Vulnerability Reporting feature. 4 | 5 | All security vulnerabilities will be promptly addressed. 6 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import jsdoc from 'eslint-plugin-jsdoc'; 3 | import globals from "globals"; 4 | 5 | export default [ 6 | { 7 | languageOptions: 8 | { 9 | globals: globals.node 10 | } 11 | }, 12 | js.configs.recommended, 13 | jsdoc.configs['flat/recommended'], 14 | { 15 | rules: { 16 | "no-control-regex": ["off"], 17 | "semi": ["error", "always"], 18 | "quotes": ["error", "single"], 19 | } 20 | } 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nw-builder", 3 | "version": "4.13.14", 4 | "description": "Build NW.js desktop applications for MacOS, Windows and Linux.", 5 | "keywords": [ 6 | "NW.js", 7 | "node-webkit", 8 | "Desktop", 9 | "Application" 10 | ], 11 | "author": { 12 | "name": "Steffen Muller", 13 | "url": "https://www.mllrsohn.com/" 14 | }, 15 | "maintainers": [ 16 | { 17 | "name": "Ayushman Chhabra", 18 | "url": "https://ayushmanchhabra.com/" 19 | } 20 | ], 21 | "contributors": [ 22 | { 23 | "name": "nw-builder Contributors", 24 | "url": "https://github.com/nwutil/nw-builder/graphs/contributors" 25 | } 26 | ], 27 | "license": "MIT", 28 | "main": "./src/index.js", 29 | "bin": { 30 | "nwbuild": "./src/cli.js" 31 | }, 32 | "types": "./src/index.d.ts", 33 | "type": "module", 34 | "files": [ 35 | "LICENSE", 36 | "src" 37 | ], 38 | "homepage": "https://github.com/nwutils/nw-builder", 39 | "repository": { 40 | "type": "git", 41 | "url": "https://github.com/nwutils/nw-builder.git" 42 | }, 43 | "scripts": { 44 | "postinstall": "node ./src/postinstall.js", 45 | "lint": "eslint ./src ./tests", 46 | "lint:fix": "eslint --fix ./src ./tests", 47 | "test": "vitest run --coverage", 48 | "test:cov": "vitest --coverage.enabled true", 49 | "demo:bld:linux": "node ./tests/fixtures/demo.linux.js", 50 | "demo:bld:osx": "node ./tests/fixtures/demo.osx.js", 51 | "demo:bld:win": "node ./tests/fixtures/demo.win.js", 52 | "demo:exe:linux": "./tests/fixtures/out/linux/Demo", 53 | "demo:exe:osx": "./tests/fixtures/out/osx/Demo.app/Contents/MacOS/Demo", 54 | "demo:exe:win": "./tests/fixtures/out/win/Demo.exe", 55 | "demo:cli": "nwbuild --mode=get --flavor=sdk --glob=false --cacheDir=./node_modules/nw --logLevel=debug './tests/fixtures/app'" 56 | }, 57 | "devDependencies": { 58 | "@eslint/js": "^9.26.0", 59 | "@vitest/coverage-v8": "^3.1.3", 60 | "base-volta-off-of-nwjs": "^1.0.5", 61 | "eslint": "^9.26.0", 62 | "eslint-plugin-jsdoc": "^50.6.17", 63 | "globals": "^16.1.0", 64 | "nw": "^0.99.1", 65 | "selenium-webdriver": "^4.32.0", 66 | "vitest": "^3.0.7" 67 | }, 68 | "dependencies": { 69 | "archiver": "^7.0.1", 70 | "axios": "^1.9.0", 71 | "commander": "^13.1.0", 72 | "glob": "^11.0.2", 73 | "node-gyp": "^11.2.0", 74 | "plist": "^3.1.0", 75 | "resedit": "^2.0.3", 76 | "semver": "^7.7.2", 77 | "tar": "^7.4.3", 78 | "yauzl-promise": "^4.0.0" 79 | }, 80 | "volta": { 81 | "node": "23.11.0", 82 | "npm": "11.2.0" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | ".": { 4 | "last-release-sha": "4ab7b8d2a1453ebbdd65aea6518b789d64e833a4", 5 | "include-component-in-tag": false, 6 | "release-type": "node", 7 | "changelog-sections": [ 8 | { "type": "feat", "section": "Features", "hidden": false }, 9 | { "type": "fix", "section": "Bug Fixes", "hidden": false }, 10 | { "type": "chore", "section": "Chores", "hidden": false } 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/bld.js: -------------------------------------------------------------------------------- 1 | import child_process from 'node:child_process'; 2 | import console from 'node:console'; 3 | import fs from 'node:fs'; 4 | import path from 'node:path'; 5 | import process from 'node:process'; 6 | 7 | import archiver from 'archiver'; 8 | import * as resedit from 'resedit'; 9 | // pe-library is a direct dependency of resedit 10 | import * as peLibrary from 'pe-library'; 11 | import * as tar from 'tar'; 12 | 13 | import util from './util.js'; 14 | import setOsxConfig from './bld/osx.js'; 15 | 16 | /** 17 | * References: 18 | * https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html 19 | * @typedef {object} LinuxRc Linux configuration options 20 | * @property {string} name Name of the application 21 | * @property {string} genericName Generic name of the application 22 | * @property {boolean} noDisplay If true the application is not displayed 23 | * @property {string} comment Tooltip for the entry, for example "View sites on the Internet". 24 | * @property {string} icon Icon to display in file manager, menus, etc. 25 | * @property {boolean} hidden TBD 26 | * @property {string[]} onlyShowIn A list of strings identifying the desktop environments that should (/not) display a given desktop entry 27 | * @property {string[]} notShowIn A list of strings identifying the desktop environments that should (/not) display a given desktop entry 28 | * @property {boolean} dBusActivatable A boolean value specifying if D-Bus activation is supported for this application 29 | * @property {string} tryExec Path to an executable file on disk used to determine if the program is actually installed 30 | * @property {string} exec Program to execute, possibly with arguments. 31 | * @property {string} path If entry is of type Application, the working directory to run the program in. 32 | * @property {boolean} terminal Whether the program runs in a terminal window. 33 | * @property {string[]} actions Identifiers for application actions. 34 | * @property {string[]} mimeType The MIME type(s) supported by this application. 35 | * @property {string[]} categories Categories in which the entry should be shown in a menu 36 | * @property {string[]} implements A list of interfaces that this application implements. 37 | * @property {string[]} keywords A list of strings which may be used in addition to other metadata to describe this entry. 38 | * @property {boolean} startupNotify If true, it is KNOWN that the application will send a "remove" message when started with the DESKTOP_STARTUP_ID environment variable set. If false, it is KNOWN that the application does not work with startup notification at all. 39 | * @property {string} startupWMClass If specified, it is known that the application will map at least one window with the given string as its WM class or WM name hin 40 | * @property {boolean} prefersNonDefaultGPU If true, the application prefers to be run on a more powerful discrete GPU if available. 41 | * @property {string} singleMainWindow If true, the application has a single main window, and does not support having an additional one opened. 42 | */ 43 | 44 | /** 45 | * References: 46 | * https://developer.apple.com/documentation/bundleresources/information_property_list 47 | * @typedef {object} OsxRc OSX resource configuration options 48 | * @property {string} name The name of the application 49 | * @property {string} icon The path to the icon file. It should be a .icns file. 50 | * @property {string} LSApplicationCategoryType The category that best describes your app for the App Store. 51 | * @property {string} CFBundleIdentifier A unique identifier for a bundle usually in reverse DNS format. 52 | * @property {string} CFBundleName A user-visible short name for the bundle. 53 | * @property {string} CFBundleDisplayName The user-visible name for the bundle. 54 | * @property {string} CFBundleSpokenName A replacement for the app name in text-to-speech operations. 55 | * @property {string} CFBundleVersion The version of the build that identifies an iteration of the bundle. 56 | * @property {string} CFBundleShortVersionString The release or version number of the bundle. 57 | * @property {string} NSHumanReadableCopyright A human-readable copyright notice for the bundle. 58 | * @property {string} NSLocalNetworkUsageDescription A human-readable description of why the application needs access to the local network. 59 | */ 60 | 61 | /** 62 | * References: 63 | * https://learn.microsoft.com/en-us/windows/win32/msi/version 64 | * https://learn.microsoft.com/en-gb/windows/win32/sbscs/application-manifests 65 | * https://learn.microsoft.com/en-us/visualstudio/deployment/trustinfo-element-clickonce-application?view=vs-2022#requestedexecutionlevel 66 | * https://learn.microsoft.com/en-gb/windows/win32/menurc/versioninfo-resource 67 | * @typedef {object} WinRc Windows configuration options. More info 68 | * @property {string} name The name of the application 69 | * @property {string} version The version of the application 70 | * @property {string} comments Additional information that should be displayed for diagnostic purposes. 71 | * @property {string} company Company that produced the file—for example, Microsoft Corporation or Standard Microsystems Corporation, Inc. This string is required. 72 | * @property {string} fileDescription File description to be presented to users. This string may be displayed in a list box when the user is choosing files to install. For example, Keyboard Driver for AT-Style Keyboards. This string is required. 73 | * @property {string} fileVersion Version number of the file. For example, 3.10 or 5.00.RC2. This string is required. 74 | * @property {string} icon The path to the icon file. It should be a .ico file. 75 | * @property {string} internalName Internal name of the file, if one exists—for example, a module name if the file is a dynamic-link library. If the file has no internal name, this string should be the original filename, without extension. This string is required. 76 | * @property {string} legalCopyright Copyright notices that apply to the file. This should include the full text of all notices, legal symbols, copyright dates, and so on. This string is optional. 77 | * @property {string} legalTrademark Trademarks and registered trademarks that apply to the file. This should include the full text of all notices, legal symbols, trademark numbers, and so on. This string is optional. 78 | * @property {string} originalFilename Original name of the file, not including a path. This information enables an application to determine whether a file has been renamed by a user. The format of the name depends on the file system for which the file was created. This string is required. 79 | * @property {string} privateBuild Information about a private version of the file—for example, Built by TESTER1 on \\TESTBED. This string should be present only if VS_FF_PRIVATEBUILD is specified in the fileflags parameter of the root block. 80 | * @property {string} productName Name of the product with which the file is distributed. This string is required. 81 | * @property {string} productVersion Version of the product with which the file is distributed—for example, 3.10 or 5.00.RC2. This string is required. 82 | * @property {string} specialBuild Text that specifies how this version of the file differs from the standard version—for example, Private build for TESTER1 solving mouse problems on M250 and M250E computers. This string should be present only if VS_FF_SPECIALBUILD is specified in the fileflags parameter of the root block. 83 | * @property {string} languageCode Language of the file, defined by Microsoft, see: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a 84 | */ 85 | 86 | /** 87 | * @typedef {object} BuildOptions 88 | * @property {string | "latest" | "stable" | "lts"} [version = "latest"] Runtime version 89 | * @property {"normal" | "sdk"} [flavor = "normal"] Build flavor 90 | * @property {"linux" | "osx" | "win"} [platform] Target platform 91 | * @property {"ia32" | "x64" | "arm64"} [arch] Target arch 92 | * @property {string} [manifestUrl = "https://nwjs.io/versions"] Manifest URL 93 | * @property {string} [srcDir = "./src"] Source directory 94 | * @property {string} [cacheDir = "./cache"] Cache directory 95 | * @property {string} [outDir = "./out"] Out directory 96 | * @property {LinuxRc | WinRc | OsxRc} [app] Platform specific rc 97 | * @property {boolean} [glob = true] File globbing 98 | * @property {boolean | string | object} [managedManifest = false] Manage manifest 99 | * @property {false | "gyp"} [nativeAddon = false] Rebuild native modules 100 | * @property {false | "zip" | "tar" | "tgz"} [zip = false] Compress built artifacts 101 | * @property {object} [releaseInfo = {}] Version specific release metadata. 102 | */ 103 | 104 | /** 105 | * Build NW.js application. 106 | * @async 107 | * @function 108 | * @param {BuildOptions} options - Build options 109 | * @returns {Promise} 110 | */ 111 | async function bld({ 112 | version = 'latest', 113 | flavor = 'normal', 114 | platform = util.PLATFORM_KV[process.platform], 115 | arch = util.ARCH_KV[process.arch], 116 | srcDir = './src', 117 | cacheDir = './cache', 118 | outDir = './out', 119 | app, 120 | glob = true, 121 | managedManifest = false, 122 | nativeAddon = false, 123 | zip = false, 124 | releaseInfo = {}, 125 | }) { 126 | const nwDir = path.resolve( 127 | cacheDir, 128 | `nwjs${flavor === 'sdk' ? '-sdk' : ''}-v${version}-${platform 129 | }-${arch}`, 130 | ); 131 | 132 | await fs.promises.rm(outDir, { force: true, recursive: true }); 133 | await fs.promises.cp(nwDir, outDir, { recursive: true, verbatimSymlinks: true }); 134 | 135 | const files = await util.globFiles({ srcDir, glob }); 136 | let manifest = await util.getNodeManifest({ srcDir, glob }); 137 | 138 | /* Set `product_string` in manifest for MacOS. This is used in renaming the Helper apps. */ 139 | if (platform === 'osx') { 140 | manifest.json.product_string = app.name; 141 | await fs.promises.writeFile(manifest.path, JSON.stringify(manifest.json)); 142 | } 143 | 144 | if (glob) { 145 | for (let file of files) { 146 | const stats = await fs.promises.stat(file); 147 | if (stats.isDirectory()) { 148 | continue; 149 | } 150 | await fs.promises.cp( 151 | file, 152 | path.resolve( 153 | outDir, 154 | platform !== 'osx' 155 | ? 'package.nw' 156 | : 'nwjs.app/Contents/Resources/app.nw', 157 | file, 158 | ), 159 | { recursive: true, force: true }, 160 | ); 161 | } 162 | } else { 163 | await fs.promises.cp( 164 | files, 165 | path.resolve( 166 | outDir, 167 | platform !== 'osx' 168 | ? 'package.nw' 169 | : 'nwjs.app/Contents/Resources/app.nw', 170 | ), 171 | { recursive: true, verbatimSymlinks: true }, 172 | ); 173 | } 174 | 175 | // const nodeVersion = releaseInfo.components.node; 176 | 177 | if ( 178 | managedManifest === true || 179 | typeof managedManifest === 'object' || 180 | typeof managedManifest === 'string' 181 | ) { 182 | await manageManifest({ nwPkg: manifest.json, managedManifest, outDir, platform }); 183 | } 184 | 185 | if (platform === 'linux') { 186 | await setLinuxConfig({ app, outDir }); 187 | } else if (platform === 'win') { 188 | await setWinConfig({ app, outDir }); 189 | } else if (platform === 'osx') { 190 | await setOsxConfig({ app, outDir, releaseInfo }); 191 | } 192 | 193 | if (nativeAddon === 'gyp') { 194 | throw new Error('Rebuilding Node addons functionality is broken and has been disabled. This functionality may be removed in the future.'); 195 | // buildNativeAddon({ cacheDir, version, platform, arch, outDir, nodeVersion }); 196 | } 197 | 198 | if (zip !== false) { 199 | await compress({ zip, outDir }); 200 | } 201 | } 202 | 203 | const manageManifest = async ({ nwPkg, managedManifest, outDir, platform }) => { 204 | let manifest = undefined; 205 | 206 | if (managedManifest === true) { 207 | manifest = nwPkg; 208 | } 209 | 210 | if (typeof managedManifest === 'object') { 211 | manifest = managedManifest; 212 | } 213 | 214 | if (typeof managedManifest === 'string') { 215 | manifest = JSON.parse(await fs.promises.readFile(managedManifest)); 216 | } 217 | 218 | if (manifest.devDependencies) { 219 | manifest.devDependencies = undefined; 220 | } 221 | manifest.packageManager = manifest.packageManager ?? 'npm@*'; 222 | 223 | await fs.promises.writeFile( 224 | path.resolve( 225 | outDir, 226 | platform !== 'osx' 227 | ? 'package.nw' 228 | : 'nwjs.app/Contents/Resources/app.nw', 229 | 'package.json', 230 | ), 231 | JSON.stringify(manifest, null, 2), 232 | 'utf8', 233 | ); 234 | 235 | const cwd = path.resolve( 236 | outDir, 237 | platform !== 'osx' 238 | ? 'package.nw' 239 | : 'nwjs.app/Contents/Resources/app.nw', 240 | ); 241 | 242 | if (manifest.packageManager.startsWith('npm')) { 243 | child_process.execSync('npm install', { cwd }); 244 | } else if (manifest.packageManager.startsWith('yarn')) { 245 | child_process.execSync('yarn install', { cwd }); 246 | } else if (manifest.packageManager.startsWith('pnpm')) { 247 | child_process.execSync('pnpm install', { cwd }); 248 | } 249 | }; 250 | 251 | const setLinuxConfig = async ({ app, outDir }) => { 252 | if (process.platform === 'win32') { 253 | console.warn( 254 | 'Linux apps built on Windows platform do not preserve all file permissions. See #716', 255 | ); 256 | } 257 | let desktopEntryFile = { 258 | Type: 'Application', 259 | Version: '1.5', 260 | Name: app.name, 261 | GenericName: app.genericName, 262 | NoDisplay: app.noDisplay, 263 | Comment: app.comment, 264 | Icon: app.icon ? path.resolve(outDir, 'package.nw', app.icon) : '', 265 | Hidden: app.hidden, 266 | OnlyShowIn: app.onlyShowIn, 267 | NotShowIn: app.notShowIn, 268 | DBusActivatable: app.dBusActivatable, 269 | TryExec: app.tryExec, 270 | Exec: app.exec, 271 | Path: app.path, 272 | Terminal: app.terminal, 273 | Actions: app.actions, 274 | MimeType: app.mimeType, 275 | Categories: app.categories, 276 | Implements: app.implements, 277 | Keywords: app.keywords, 278 | StartupNotify: app.startupNotify, 279 | StartupWMClass: app.startupWMClass, 280 | PrefersNonDefaultGPU: app.prefersNonDefaultGPU, 281 | SingleMainWindow: app.singleMainWindow, 282 | }; 283 | 284 | await fs.promises.rename(`${outDir}/nw`, `${outDir}/${app.name}`); 285 | 286 | let fileContent = '[Desktop Entry]\n'; 287 | Object.keys(desktopEntryFile).forEach((key) => { 288 | if (desktopEntryFile[key] !== undefined) { 289 | fileContent += `${key}=${desktopEntryFile[key]}\n`; 290 | } 291 | }); 292 | let filePath = `${outDir}/${app.name}.desktop`; 293 | await fs.promises.writeFile(filePath, fileContent); 294 | }; 295 | 296 | const setWinConfig = async ({ app, outDir }) => { 297 | let versionString = { 298 | Comments: app.comments, 299 | CompanyName: app.company, 300 | FileDescription: app.fileDescription, 301 | FileVersion: app.fileVersion, 302 | InternalName: app.internalName, 303 | LegalCopyright: app.legalCopyright, 304 | LegalTrademarks: app.legalTrademark, 305 | OriginalFilename: app.originalFilename, 306 | PrivateBuild: app.privateBuild, 307 | ProductName: app.productName, 308 | ProductVersion: app.productVersion, 309 | SpecialBuild: app.specialBuild, 310 | }; 311 | 312 | Object.keys(versionString).forEach((option) => { 313 | if (versionString[option] === undefined) { 314 | delete versionString[option]; 315 | } 316 | }); 317 | 318 | const outDirAppExe = path.resolve(outDir, `${app.name}.exe`); 319 | await fs.promises.rename(path.resolve(outDir, 'nw.exe'), outDirAppExe); 320 | const exe = peLibrary.NtExecutable.from(await fs.promises.readFile(outDirAppExe)); 321 | const res = peLibrary.NtExecutableResource.from(exe); 322 | // English (United States) 323 | const EN_US = 1033; 324 | if (app.icon) { 325 | const iconBuffer = await fs.promises.readFile(path.resolve(app.icon)); 326 | const iconFile = resedit.Data.IconFile.from(iconBuffer); 327 | const iconGroupIDs = resedit.Resource.IconGroupEntry.fromEntries(res.entries).map((entry) => entry.id); 328 | resedit.Resource.IconGroupEntry.replaceIconsForResource( 329 | res.entries, 330 | /* Should be `IDR_MAINFRAME` */ 331 | iconGroupIDs[0], 332 | EN_US, 333 | iconFile.icons.map(i => i.data) 334 | ); 335 | } 336 | const [vi] = resedit.Resource.VersionInfo.fromEntries(res.entries); 337 | if (app.languageCode !== EN_US) { 338 | res.removeResourceEntry(16, 1, EN_US); 339 | vi.removeAllStringValues({ 340 | lang: EN_US, 341 | codepage: 1200, 342 | }); 343 | vi.lang=app.languageCode; 344 | } 345 | vi.setFileVersion(app.fileVersion, app.languageCode); 346 | vi.setProductVersion(app.productVersion, app.languageCode); 347 | vi.setStringValues({ 348 | lang: app.languageCode, 349 | codepage: 1200 350 | }, versionString); 351 | vi.outputToResourceEntries(res.entries); 352 | res.outputResource(exe); 353 | const outBuffer = Buffer.from(exe.generate()); 354 | await fs.promises.writeFile(outDirAppExe, outBuffer); 355 | }; 356 | 357 | /* 358 | const buildNativeAddon = ({ cacheDir, version, platform, arch, outDir, nodeVersion }) => { 359 | let nodePath = path.resolve(cacheDir, `node-v${version}-${platform}-${arch}`); 360 | const cwd = path.resolve( 361 | outDir, 362 | platform !== 'osx' 363 | ? 'package.nw' 364 | : 'nwjs.app/Contents/Resources/app.nw', 365 | ); 366 | 367 | child_process.execFileSync('node-gyp', ['rebuild', `--target=${nodeVersion}`, `--nodedir=${nodePath}`], { cwd }); 368 | }; 369 | */ 370 | 371 | const compress = async ({ 372 | zip, 373 | outDir, 374 | }) => { 375 | if (zip === true || zip === 'zip') { 376 | const archive = archiver('zip'); 377 | const writeStream = fs.createWriteStream(`${outDir}.zip`); 378 | archive.pipe(writeStream); 379 | archive.directory(outDir, false); 380 | await archive.finalize(); 381 | } else if (zip === 'tar') { 382 | await tar.create({ 383 | gzip: false, 384 | file: `${outDir}.tar`, 385 | }, [outDir]); 386 | } else if (zip === 'tgz') { 387 | await tar.create({ 388 | gzip: true, 389 | file: `${outDir}.tgz`, 390 | }, [outDir]); 391 | } 392 | 393 | await fs.promises.rm(outDir, { recursive: true, force: true }); 394 | }; 395 | 396 | export default bld; 397 | -------------------------------------------------------------------------------- /src/bld/osx.js: -------------------------------------------------------------------------------- 1 | import console from 'node:console'; 2 | import fs from 'node:fs'; 3 | import path from 'node:path'; 4 | import process from 'node:process'; 5 | 6 | import plist from 'plist'; 7 | 8 | /** 9 | * Function to update Helper App Plist Files 10 | * @param {string} plistPath - Path to Helper App Plist File 11 | * @param {string} helperName - Helper App Name 12 | * @param {string} helperId - Helper App ID 13 | * @param {string} appCFBundleIdentifier - options.app.CFBundleIdentifier 14 | */ 15 | async function updateHelperPlist (plistPath, helperName, helperId, appCFBundleIdentifier) { 16 | const plistFullPath = path.resolve(plistPath, 'Contents/Info.plist'); 17 | const plistJson = plist.parse(await fs.promises.readFile(plistFullPath, 'utf-8')); 18 | plistJson.CFBundleDisplayName = helperName; 19 | plistJson.CFBundleName = helperName; 20 | plistJson.CFBundleExecutable = helperName; 21 | plistJson.CFBundleIdentifier = `${appCFBundleIdentifier}.${helperId}`; 22 | await fs.promises.writeFile(plistFullPath, plist.build(plistJson)); 23 | } 24 | 25 | /** 26 | * 27 | * @param {object} options - Options. 28 | * @param {object} options.app - Application configuration. 29 | * @param {string} options.outDir - Output directory. 30 | * @param {string} options.releaseInfo - Release information. 31 | * @returns {Promise} - Promise. 32 | */ 33 | export default async function setOsxConfig({ app, outDir, releaseInfo }) { 34 | if (process.platform === 'win32') { 35 | console.warn( 36 | 'MacOS apps built on Windows platform do not preserve all file permissions. See #716', 37 | ); 38 | } 39 | 40 | try { 41 | /** 42 | * @type {string | null} 43 | */ 44 | const chromiumVersion = releaseInfo?.components?.chromium; 45 | if (!chromiumVersion) { 46 | throw new Error('Chromium version is missing.'); 47 | } 48 | /** 49 | * Path to MacOS application. 50 | * @type {string} 51 | */ 52 | const nwjsApp = path.resolve(outDir, 'nwjs.app'); 53 | 54 | /** 55 | * Path to renamed MacOS application. 56 | * @type {string} 57 | */ 58 | const outApp = path.resolve(outDir, `${app.name}.app`); 59 | 60 | /* Rename `nwjs.app` to `${app.name}.app` */ 61 | await fs.promises.rename(nwjsApp, outApp); 62 | 63 | /* Rename `Contents/MacOS/nwjs` to `Contents/MacOS/${app.name}` */ 64 | await fs.promises.rename( 65 | path.resolve(outApp, 'Contents', 'MacOS', 'nwjs'), 66 | path.resolve(outApp, 'Contents', 'MacOS', app.name), 67 | ); 68 | 69 | /* Rename all Helper apps */ 70 | const helperBaseDir = path.resolve( 71 | outApp, 72 | 'Contents/Frameworks/nwjs Framework.framework/Versions', 73 | chromiumVersion, 74 | 'Helpers/' 75 | ); 76 | 77 | const helperApps = [ 78 | { name: 'nwjs Helper (Alerts).app', id: 'helper.alert' }, 79 | { name: 'nwjs Helper (GPU).app', id: 'helper.gpu' }, 80 | { name: 'nwjs Helper (Plugin).app', id: 'helper.plugin' }, 81 | { name: 'nwjs Helper (Renderer).app', id: 'helper.renderer' }, 82 | { name: 'nwjs Helper.app', id: 'helper' }, 83 | ]; 84 | 85 | for (const helperApp of helperApps) { 86 | const newHelperAppName = helperApp.name.replace(/^nwjs/, app.name); 87 | const oldPath = path.resolve(helperBaseDir, helperApp.name); 88 | const newPath = path.resolve(helperBaseDir, newHelperAppName); 89 | 90 | // Rename Helper base directory 91 | await fs.promises.rename(oldPath, newPath); 92 | 93 | // Rename Helper sub-directory 94 | const helperBaseName = helperApp.name.replace(/.app$/, ''); 95 | const subPathBase = path.resolve(newPath, 'Contents/MacOS/'); 96 | const oldSubPath = path.resolve(subPathBase, helperBaseName); 97 | const newSubPath = path.resolve(subPathBase, helperBaseName.replace(/^nwjs/, app.name)); 98 | await fs.promises.rename(oldSubPath, newSubPath); 99 | 100 | // Update Helper Plist file 101 | await updateHelperPlist(newPath, newHelperAppName.replace(/.app$/, ''), helperApp.id, app.CFBundleIdentifier); 102 | } 103 | 104 | /* Replace default icon with user defined icon if specified. */ 105 | if (app.icon !== undefined) { 106 | await fs.promises.copyFile( 107 | path.resolve(app.icon), 108 | path.resolve(outApp, 'Contents', 'Resources', 'app.icns'), 109 | ); 110 | } 111 | 112 | /** 113 | * Path to `nwjs.app/Contents/Info.plist` 114 | * @type {string} 115 | */ 116 | const contentsInfoPlistPath = path.resolve( 117 | outApp, 118 | 'Contents', 119 | 'Info.plist' 120 | ); 121 | 122 | /** 123 | * Path to `nwjs.app/Contents/Resources/en.lproj/InfoPlist.settings` 124 | * @type {string} 125 | */ 126 | const contentsResourcesEnLprojInfoPlistStringsPath = path.resolve( 127 | outApp, 128 | 'Contents', 129 | 'Resources', 130 | 'en.lproj', 131 | 'InfoPlist.strings', 132 | ); 133 | 134 | /** 135 | * JSON from `nwjs.app/Contents/Info.plist` 136 | * @type {object} 137 | */ 138 | const contentsInfoPlistJson = plist.parse( 139 | await fs.promises.readFile( 140 | contentsInfoPlistPath, 141 | 'utf-8' 142 | ) 143 | ); 144 | 145 | /* Update Plist with user defined values. */ 146 | contentsInfoPlistJson.LSApplicationCategoryType = app.LSApplicationCategoryType; 147 | contentsInfoPlistJson.CFBundleIdentifier = app.CFBundleIdentifier; 148 | contentsInfoPlistJson.CFBundleName = app.CFBundleName; 149 | contentsInfoPlistJson.CFBundleDisplayName = app.CFBundleDisplayName; 150 | contentsInfoPlistJson.CFBundleSpokenName = app.CFBundleSpokenName; 151 | contentsInfoPlistJson.CFBundleVersion = app.CFBundleVersion; 152 | contentsInfoPlistJson.CFBundleShortVersionString = app.CFBundleShortVersionString; 153 | contentsInfoPlistJson.CFBundleExecutable = app.name; 154 | contentsInfoPlistJson.NSLocalNetworkUsageDescription = app.NSLocalNetworkUsageDescription; 155 | 156 | /* Remove properties that were not updated by the user. */ 157 | Object.keys(contentsInfoPlistJson).forEach((option) => { 158 | if (contentsInfoPlistJson[option] === undefined) { 159 | delete contentsInfoPlistJson[option]; 160 | } 161 | }); 162 | 163 | /** 164 | * Data from `nwjs.app/Contents/Resources/en.lproj/InfoPlist.settings` 165 | * @type {string[]} 166 | */ 167 | const contentsResourcesEnLprojInfoPlistStringsArray = (await fs.promises.readFile( 168 | contentsResourcesEnLprojInfoPlistStringsPath, 169 | 'utf-8', 170 | )).split('\n'); 171 | contentsResourcesEnLprojInfoPlistStringsArray.forEach((line, idx, arr) => { 172 | if (line.includes('NSHumanReadableCopyright')) { 173 | arr[idx] = 174 | `NSHumanReadableCopyright = "${app.NSHumanReadableCopyright}";`; 175 | } 176 | }); 177 | 178 | /* Write the updated values to their config files. */ 179 | await fs.promises.writeFile( 180 | contentsInfoPlistPath, 181 | plist.build(contentsInfoPlistJson)); 182 | await fs.promises.writeFile( 183 | contentsResourcesEnLprojInfoPlistStringsPath, 184 | contentsResourcesEnLprojInfoPlistStringsArray.toString().replace(/,/g, '\n'), 185 | ); 186 | 187 | } catch (error) { 188 | console.error(error); 189 | } 190 | }; 191 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import process from 'node:process'; 4 | 5 | import { program } from 'commander'; 6 | 7 | import nwbuild from './index.js'; 8 | import util from './util.js'; 9 | 10 | program 11 | .argument('', 'File path(s) to project') 12 | .option('--mode ', 'get, run or build mode', 'build') 13 | .option('--version ', 'NW.js version', 'latest') 14 | .option('--flavor ', 'NW.js build flavor', 'normal') 15 | .option('--platform ', 'NW.js supported platform', util.PLATFORM_KV[process.platform]) 16 | .option('--arch ', 'NW.js supported architecture', util.ARCH_KV[process.arch]) 17 | .option('--downloadUrl ', 'NW.js download server', 'https://dl.nwjs.io') 18 | .option('--manifestUrl ', 'NW.js version info', 'https://nwjs.io/versions.json') 19 | .option('--cacheDir ', 'Cache NW.js binaries', './cache') 20 | .option('--outDir ', 'NW.js build artifacts', './out') 21 | .option('--app ', 'Platform specific app metadata. Refer to docs for more info', {}) 22 | .option('--cache ', 'Flag to enable/disable caching', true) 23 | .option('--ffmpeg ', 'Flag to enable/disable downloading community ffmpeg', false) 24 | .option('--glob ', 'Flag to enable/disable globbing', true) 25 | .option('--logLevel ', 'Specify log level', 'info') 26 | .option('--shaSum ', 'Flag to enable/disable shasum', true) 27 | .option('--zip ', 'Flag to enable/disable compression', false) 28 | .option('--managedManifest ', 'Managed manifest mode', false) 29 | .option('--nodeAddon ', 'Download NW.js Node headers', false); 30 | 31 | program.parse(); 32 | 33 | nwbuild({ 34 | ...program.opts(), 35 | srcDir: program.args.join(' '), 36 | cli: true, 37 | }); 38 | -------------------------------------------------------------------------------- /src/get/decompress.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | import stream from 'node:stream'; 4 | 5 | import * as tar from 'tar'; 6 | import yauzl from 'yauzl-promise'; 7 | 8 | /** 9 | * Decompresses a file at `filePath` to `cacheDir` directory. 10 | * @param {string} filePath - file path to compressed binary 11 | * @param {string} cacheDir - directory to decompress into 12 | */ 13 | export default async function decompress(filePath, cacheDir) { 14 | if (filePath.endsWith('.zip')) { 15 | await unzip(filePath, cacheDir); 16 | } else { 17 | await tar.extract({ 18 | file: filePath, 19 | C: cacheDir 20 | }); 21 | } 22 | } 23 | 24 | /** 25 | * Get file mode from entry. Reference implementation is [here](https://github.com/fpsqdb/zip-lib/blob/ac447d269218d396e05cd7072d0e9cd82b5ec52c/src/unzip.ts#L380). 26 | * @param {yauzl.Entry} entry - Yauzl entry 27 | * @returns {number} - entry's file mode 28 | */ 29 | function modeFromEntry(entry) { 30 | const attr = entry.externalFileAttributes >> 16 || 33188; 31 | 32 | return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */] 33 | .map(mask => attr & mask) 34 | .reduce((a, b) => a + b, attr & 61440 /* S_IFMT */); 35 | } 36 | 37 | /** 38 | * Unzip `zippedFile` to `cacheDir`. 39 | * @async 40 | * @function 41 | * @param {string} zippedFile - file path to .zip file 42 | * @param {string} cacheDir - directory to unzip in 43 | * @returns {Promise} 44 | */ 45 | async function unzip(zippedFile, cacheDir) { 46 | const zip = await yauzl.open(zippedFile); 47 | let entry = await zip.readEntry(); 48 | const symlinks = []; // Array to hold symbolic link entries 49 | 50 | while (entry !== null) { 51 | let entryPathAbs = path.join(cacheDir, entry.filename); 52 | // Check if entry is a symbolic link 53 | const isSymlink = ((modeFromEntry(entry) & 0o170000) === 0o120000); 54 | 55 | if (isSymlink) { 56 | // Store symlink entries to process later 57 | symlinks.push(entry); 58 | } else { 59 | // Handle regular files and directories 60 | await fs.promises.mkdir(path.dirname(entryPathAbs), {recursive: true}); 61 | if (!entry.filename.endsWith('/')) { // Skip directories 62 | const readStream = await entry.openReadStream(); 63 | const writeStream = fs.createWriteStream(entryPathAbs); 64 | await stream.promises.pipeline(readStream, writeStream); 65 | 66 | // Set file permissions after the file has been written 67 | const mode = modeFromEntry(entry); 68 | await fs.promises.chmod(entryPathAbs, mode); 69 | } 70 | } 71 | 72 | // Read next entry 73 | entry = await zip.readEntry(); 74 | } 75 | 76 | // Process symbolic links after all other files have been extracted 77 | for (const symlinkEntry of symlinks) { 78 | let entryPathAbs = path.join(cacheDir, symlinkEntry.filename); 79 | const readStream = await symlinkEntry.openReadStream(); 80 | const chunks = []; 81 | readStream.on('data', (chunk) => chunks.push(chunk)); 82 | await new Promise(resolve => readStream.on('end', resolve)); 83 | const linkTarget = Buffer.concat(chunks).toString('utf8').trim(); 84 | 85 | // Check if the symlink or a file/directory already exists at the destination 86 | if (fs.existsSync(entryPathAbs)) { 87 | //skip 88 | } else { 89 | // Create symbolic link 90 | await fs.promises.symlink(linkTarget, entryPathAbs); 91 | } 92 | } 93 | await zip.close(); 94 | } 95 | -------------------------------------------------------------------------------- /src/get/ffmpeg.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | 3 | import request from './request.js'; 4 | 5 | /** 6 | * Download community FFmpeg binary from `https://github.com/nwjs-ffmpeg-prebuilt/nwjs-ffmpeg-prebuilt`. 7 | * @param {string} downloadUrl - Download server 8 | * @param {string} version - Runtime version 9 | * @param {string} platform - NW supported platform 10 | * @param {string} arch - NW supported architecture 11 | * @param {string} cacheDir - Directory to store FFmpeg binary 12 | * @returns {Promise} Path of compressed file which containscommunity FFmpeg binary. 13 | */ 14 | export default async function ffmpeg(downloadUrl, version, platform, arch, cacheDir) { 15 | 16 | /** 17 | * URL to download specific FFmpeg binary from. 18 | * @type {string} 19 | */ 20 | const url = [ 21 | downloadUrl, 22 | version, 23 | `${version}-${platform}-${arch}.zip`, 24 | ].join('/'); 25 | 26 | /** 27 | * Absolute path of compressed file which contains FFmpeg binary. 28 | */ 29 | const ffmpegFileAbs = path.resolve( 30 | cacheDir, 31 | `ffmpeg-${version}-${platform}-${arch}.zip`, 32 | ); 33 | await request(url, ffmpegFileAbs); 34 | 35 | return ffmpegFileAbs; 36 | } 37 | -------------------------------------------------------------------------------- /src/get/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | import url from 'node:url'; 4 | 5 | import decompress from './decompress.js'; 6 | import ffmpeg from './ffmpeg.js'; 7 | import node from './node.js'; 8 | import nw from './nw.js'; 9 | import verify from './verify.js'; 10 | 11 | import util from '../util.js'; 12 | 13 | /** 14 | * @typedef {object} GetOptions 15 | * @property {string | "latest" | "stable" | "lts"} [version = "latest"] Runtime version 16 | * @property {"normal" | "sdk"} [flavor = "normal"] Build flavor 17 | * @property {"linux" | "osx" | "win"} [platform] Target platform 18 | * @property {"ia32" | "x64" | "arm64"} [arch] Target arch 19 | * @property {string} [downloadUrl = "https://dl.nwjs.io"] Download server 20 | * @property {string} [cacheDir = "./cache"] Cache directory 21 | * @property {boolean} [cache = true] If false, remove cache and redownload. 22 | * @property {boolean} [ffmpeg = false] If true, ffmpeg is not downloaded. 23 | * @property {false | "gyp"} [nativeAddon = false] Rebuild native modules 24 | * @property {string} [logLevel = 'info'] User defined log level. 25 | * @property {boolean} [shaSum = true] If true shasum is enabled, otherwise disabled. 26 | */ 27 | 28 | /** 29 | * Get binaries. 30 | * @async 31 | * @function 32 | * @param {GetOptions} options Get mode options 33 | * @returns {Promise} 34 | */ 35 | async function get(options) { 36 | 37 | const uri = new url.URL(options.downloadUrl); 38 | 39 | /* Download server is the cached directory. */ 40 | if (uri.protocol === 'file:') { 41 | options.cacheDir = path.resolve(decodeURIComponent(options.downloadUrl.slice('file://'.length))); 42 | } 43 | 44 | /** 45 | * If `options.cacheDir` exists, then `true`. Otherwise, it is `false`. 46 | * @type {boolean} 47 | */ 48 | const cacheDirExists = await util.fileExists(options.cacheDir); 49 | if (cacheDirExists === false) { 50 | await fs.promises.mkdir(options.cacheDir, { recursive: true }); 51 | } 52 | 53 | /** 54 | * File path to compressed binary. 55 | * @type {string} 56 | */ 57 | let nwFilePath = path.resolve( 58 | options.cacheDir, 59 | `nwjs${options.flavor === 'sdk' ? '-sdk' : ''}-v${options.version}-${options.platform}-${options.arch}.${options.platform === 'linux' ? 'tar.gz' : 'zip' 60 | }`, 61 | ); 62 | 63 | /** 64 | * File path to directory which contain NW.js and related binaries. 65 | * @type {string} 66 | */ 67 | let nwDirPath = path.resolve( 68 | options.cacheDir, 69 | `nwjs${options.flavor === 'sdk' ? '-sdk' : ''}-v${options.version}-${options.platform}-${options.arch}`, 70 | ); 71 | 72 | // If `options.cache` is false, then remove the compressed binary. 73 | if (options.cache === false) { 74 | await fs.promises.rm(nwFilePath, { 75 | recursive: true, 76 | force: true, 77 | }); 78 | } 79 | 80 | // We remove the nwDir to prevent the edge case where you download with ffmpeg flag enabled 81 | // but want a subsequent build with ffmpeg flag disabled. By removing the directory and 82 | // decompressing it again, we prevent the community ffmpeg files from being left over. 83 | // This is important since the community ffmpeg builds have specific licensing constraints. 84 | await fs.promises.rm(nwDirPath, { recursive: true, force: true }); 85 | 86 | /** 87 | * If the compressed binary exists, then `true`. Otherwise, it is `false`. 88 | * @type {boolean} 89 | */ 90 | const nwFilePathExists = await util.fileExists(nwFilePath); 91 | if (nwFilePathExists === false) { 92 | nwFilePath = await nw(options.downloadUrl, options.version, options.flavor, options.platform, options.arch, options.cacheDir); 93 | } 94 | 95 | await decompress(nwFilePath, options.cacheDir); 96 | 97 | await verify( 98 | `${options.downloadUrl}/v${options.version}/SHASUMS256.txt`, 99 | `${options.cacheDir}/shasum/${options.version}.txt`, 100 | options.cacheDir, 101 | options.ffmpeg, 102 | options.logLevel, 103 | options.shaSum, 104 | ); 105 | 106 | if (options.ffmpeg === true) { 107 | 108 | /** 109 | * File path to compressed binary which contains community FFmpeg binary. 110 | * @type {string} 111 | */ 112 | let ffmpegFilePath = path.resolve( 113 | options.cacheDir, 114 | `ffmpeg-${options.version}-${options.platform}-${options.arch}.zip`, 115 | ); 116 | 117 | // If `options.cache` is false, then remove the compressed binary. 118 | if (options.cache === false) { 119 | await fs.promises.rm(ffmpegFilePath, { 120 | recursive: true, 121 | force: true, 122 | }); 123 | } 124 | 125 | /** 126 | * If the compressed binary exists, then `true`. Otherwise, it is `false`. 127 | * @type {boolean} 128 | */ 129 | const ffmpegFilePathExists = await util.fileExists(ffmpegFilePath); 130 | if (ffmpegFilePathExists === false) { 131 | // Do not update the options.downloadUrl with the ffmpeg URL here. Doing so would lead to error when options.ffmpeg and options.nativeAddon are both enabled. 132 | const downloadUrl = 133 | 'https://github.com/nwjs-ffmpeg-prebuilt/nwjs-ffmpeg-prebuilt/releases/download'; 134 | ffmpegFilePath = await ffmpeg(downloadUrl, options.version, options.platform, options.arch, options.cacheDir); 135 | } 136 | 137 | await decompress(ffmpegFilePath, options.cacheDir); 138 | 139 | /** 140 | * Platform dependant file name of FFmpeg binary. 141 | * @type {string} 142 | */ 143 | let ffmpegFileName = ''; 144 | 145 | if (options.platform === 'linux') { 146 | ffmpegFileName = 'libffmpeg.so'; 147 | } else if (options.platform === 'win') { 148 | ffmpegFileName = 'ffmpeg.dll'; 149 | } else if (options.platform === 'osx') { 150 | ffmpegFileName = 'libffmpeg.dylib'; 151 | } 152 | 153 | /** 154 | * File path to platform specific FFmpeg file. 155 | * @type {string} 156 | */ 157 | let ffmpegBinaryPath = path.resolve(options.cacheDir, ffmpegFileName); 158 | 159 | /** 160 | * File path of where FFmpeg will be copied to. 161 | * @type {string} 162 | */ 163 | let ffmpegBinaryDest = ''; 164 | 165 | if (options.platform === 'linux') { 166 | ffmpegBinaryDest = path.resolve(nwDirPath, 'lib', ffmpegFileName); 167 | } else if (options.platform === 'win') { 168 | ffmpegBinaryDest = path.resolve(nwDirPath, ffmpegFileName); 169 | } else if (options.platform === 'osx') { 170 | ffmpegBinaryDest = path.resolve( 171 | nwDirPath, 172 | 'nwjs.app', 173 | 'Contents', 174 | 'Frameworks', 175 | 'nwjs Framework.framework', 176 | 'Versions', 177 | 'Current', 178 | ffmpegFileName, 179 | ); 180 | } 181 | 182 | await fs.promises.copyFile(ffmpegBinaryPath, ffmpegBinaryDest); 183 | 184 | } 185 | 186 | if (options.nativeAddon === 'gyp') { 187 | 188 | /** 189 | * File path to NW'js Node headers tarball. 190 | * @type {string} 191 | */ 192 | let nodeFilePath = path.resolve( 193 | options.cacheDir, 194 | `headers-v${options.version}.tar.gz`, 195 | ); 196 | 197 | // If `options.cache` is false, then remove the compressed binary. 198 | if (options.cache === false) { 199 | await fs.promises.rm(nodeFilePath, { 200 | recursive: true, 201 | force: true, 202 | }); 203 | } 204 | 205 | /** 206 | * If the compressed binary exists, then `true`. Otherwise, it is `false`. 207 | * @type {boolean} 208 | */ 209 | const nodeFilePathExists = await util.fileExists(nodeFilePath); 210 | if (nodeFilePathExists === false) { 211 | nodeFilePath = await node(options.downloadUrl, options.version, options.cacheDir); 212 | } 213 | 214 | await decompress(nodeFilePath, options.cacheDir); 215 | 216 | } 217 | } 218 | 219 | export default get; 220 | -------------------------------------------------------------------------------- /src/get/node.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | 3 | import request from './request.js'; 4 | 5 | /** 6 | * Download NW.js's Node.js headers. 7 | * @param {string} downloadUrl - Download server 8 | * @param {string} version - Runtime version 9 | * @param {string} cacheDir - Directory to store NW binaries 10 | * @returns {Promise} - path of compressed file which contains the Node headers. 11 | */ 12 | export default async function nw(downloadUrl, version, cacheDir) { 13 | 14 | /** 15 | * Name of directory which contains Node headers. 16 | * @type {string} 17 | */ 18 | const nodeDir = `node-v${version}`; 19 | 20 | /** 21 | * Name of compressed file which contains Node headers. 22 | * @type {string} 23 | */ 24 | const nwFile = `${nodeDir}.tar.gz`; 25 | 26 | /** 27 | * URL to download specific Node headers from. 28 | * @type {string} 29 | */ 30 | const url = [ 31 | downloadUrl, 32 | `v${version}`, 33 | `nw-headers-v${version}.tar.gz` 34 | ].join('/'); 35 | 36 | /** 37 | * Absolute path of compressed file which contains Node headers. 38 | */ 39 | const nwFileAbs = path.resolve( 40 | cacheDir, 41 | nwFile 42 | ); 43 | await request(url, nwFileAbs); 44 | 45 | return nwFileAbs; 46 | } 47 | -------------------------------------------------------------------------------- /src/get/nw.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | 3 | import request from './request.js'; 4 | 5 | /** 6 | * Download NW.js binary. 7 | * @param {string} downloadUrl - Download server 8 | * @param {string} version - Runtime version 9 | * @param {string} flavor - Runtime build flavor 10 | * @param {string} platform - NW supported platform 11 | * @param {string} arch - NW supported architecture 12 | * @param {string} cacheDir - Directory to store NW binaries 13 | * @returns {Promise} - path of compressed file which contains NW.js binaries. 14 | */ 15 | export default async function nw(downloadUrl, version, flavor, platform, arch, cacheDir) { 16 | 17 | /** 18 | * Name of directory which contains NW.js binaries. 19 | * @type {string} 20 | */ 21 | const nwDir = [ 22 | 'nwjs', 23 | flavor === 'sdk' ? '-sdk' : '', 24 | `-v${version}-${platform}-${arch}`, 25 | ].join(''); 26 | 27 | /** 28 | * Name of compressed file which contains NW.js binaries. 29 | * @type {string} 30 | */ 31 | const nwFile = [ 32 | nwDir, 33 | platform === 'linux' ? 'tar.gz' : 'zip' 34 | ].join('.'); 35 | 36 | /** 37 | * URL to download specific NW.js binary from. 38 | * @type {string} 39 | */ 40 | const url = [ 41 | downloadUrl, 42 | `v${version}`, 43 | nwFile, 44 | ].join('/'); 45 | 46 | /** 47 | * Absolute path of compressed file which contains NW.js binaries. 48 | */ 49 | const nwFileAbs = path.resolve( 50 | cacheDir, 51 | nwFile 52 | ); 53 | await request(url, nwFileAbs); 54 | 55 | return nwFileAbs; 56 | } 57 | -------------------------------------------------------------------------------- /src/get/request.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import process from 'node:process'; 3 | import stream from 'node:stream'; 4 | 5 | import axios from 'axios'; 6 | 7 | /** 8 | * Download from `url`. 9 | * @async 10 | * @function 11 | * @param {string} url - Download server 12 | * @param {string} filePath - file path of downloaded content 13 | * @returns {Promise} 14 | */ 15 | export default async function request(url, filePath) { 16 | 17 | const writeStream = fs.createWriteStream(filePath); 18 | 19 | /* Listen for SIGINT (Ctrl+C) */ 20 | process.on('SIGINT', function () { 21 | /* Delete file if it exists. This prevents unnecessary `Central Directory not found` errors. */ 22 | if (fs.existsSync(filePath)) { 23 | fs.unlinkSync(filePath); 24 | } 25 | process.exit(); 26 | }); 27 | 28 | const response = await axios({ 29 | method: 'get', 30 | url: url, 31 | responseType: 'stream' 32 | }); 33 | 34 | await stream.promises.pipeline(response.data, writeStream); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/get/verify.js: -------------------------------------------------------------------------------- 1 | import crypto from 'node:crypto'; 2 | import fs from 'node:fs'; 3 | import path from 'node:path'; 4 | 5 | import request from './request.js'; 6 | 7 | import util from '../util.js'; 8 | 9 | /** 10 | * Verify the SHA256 checksum of downloaded artifacts. 11 | * @param {string} shaUrl - URL to get the shasum text file from. 12 | * @param {string} shaOut - File path to shasum text file. 13 | * @param {string} cacheDir - File path to cache directory. 14 | * @param {boolean} ffmpeg - Toggle between community (true) and official (false) ffmpeg binary 15 | * @param {string} logLevel - User defined log level. 16 | * @param {boolean} shaSum - Throws error if true, otherwise logs a warning. 17 | * @throws {Error} 18 | * @returns {Promise} - Returns true if the checksums match. 19 | */ 20 | export default async function verify(shaUrl, shaOut, cacheDir, ffmpeg, logLevel, shaSum) { 21 | const shaOutExists = await util.fileExists(shaOut); 22 | 23 | if (shaOutExists === false) { 24 | /* Create directory if does not exist. */ 25 | await fs.promises.mkdir(path.dirname(shaOut), { recursive: true }); 26 | 27 | /* Download SHASUM text file. */ 28 | await request(shaUrl, shaOut); 29 | } 30 | 31 | /* Read SHASUM text file */ 32 | const shasum = await fs.promises.readFile(shaOut, { encoding: 'utf-8' }); 33 | const shasums = shasum.trim().split('\n'); 34 | for await (const line of shasums) { 35 | const [storedSha, filePath] = line.split(/\s+/); 36 | const relativeFilePath = path.resolve(cacheDir, filePath); 37 | const relativefilePathExists = await util.fileExists(relativeFilePath); 38 | if (relativefilePathExists) { 39 | const fileBuffer = await fs.promises.readFile(relativeFilePath); 40 | const hash = crypto.createHash('sha256'); 41 | hash.update(fileBuffer); 42 | const generatedSha = hash.digest('hex'); 43 | if (!crypto.timingSafeEqual(Buffer.from(generatedSha, 'hex'), Buffer.from(storedSha, 'hex'))) { 44 | if (filePath.includes('ffmpeg') && ffmpeg) { 45 | console.warn(`The generated shasum for the community ffmpeg at ${filePath} is ${generatedSha}. The integrity of this file should be manually verified.`); 46 | } else { 47 | const message = `SHA256 checksums do not match. The file ${filePath} expected shasum is ${storedSha} but the actual shasum is ${generatedSha}.`; 48 | if (shaSum) { 49 | throw new Error(message); 50 | } else { 51 | util.log('warn', logLevel, message); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | return true; 59 | } 60 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | 2 | /** NW supported platform */ 3 | export type SupportedPlatform = "linux" | "osx" | "win"; 4 | 5 | /** Configuration options 6 | */ 7 | export interface Options

{ 8 | /** String of space separated glob patterns which correspond to NW app code */ 9 | srcDir?: "./" | string, 10 | /** Run or build application */ 11 | mode?: "build" | "get" | "run", 12 | /** NW runtime version */ 13 | version?: "latest" | "stable" | string, 14 | /** NW runtime flavor */ 15 | flavor?: "normal" | "sdk", 16 | /** NW supported platforms */ 17 | platform?: P, 18 | /** NW supported architectures */ 19 | arch?: "ia32" | "x64" | "arm64", 20 | /** Directory to store build artifacts */ 21 | outDir?: "./out" | string, 22 | /** Directry to store NW binaries */ 23 | cacheDir?: "./cache" | string, 24 | /** URI to download NW binaries from */ 25 | downloadUrl?: "https://dl.nwjs.io" | string, 26 | /** URI to download manifest */ 27 | manifestUrl?: "https://nwjs.io/versions" | string, 28 | /** Refer to Linux/Windows/Osx specific options */ 29 | app: AppOptions

, 30 | /** If true the existing cache is used. Otherwise it removes and redownloads it. */ 31 | cache?: boolean, 32 | /** If true, "zip", "tar" or "tgz" the outDir directory is compressed. */ 33 | zip?: boolean | "zip" | "tar" | "tgz", 34 | /** If true the CLI is used to glob srcDir and parse other options */ 35 | cli?: boolean, 36 | /** If true the chromium ffmpeg is replaced by community version */ 37 | ffmpeg?: boolean, 38 | /** If true globbing is enabled */ 39 | glob?: boolean, 40 | /** Specified log level. */ 41 | logLevel?: "error" | "warn" | "info" | "debug", 42 | /** Managed manifest */ 43 | managedManifest?: boolean | string | object, 44 | } 45 | 46 | /** Platform-specific application options */ 47 | export type AppOptions

= 48 | P extends 'win' ? WindowsAppOptions : 49 | P extends 'osx' ? OsxAppOptions : 50 | P extends 'linux' ? LinuxAppOptions 51 | : (WindowsAppOptions | OsxAppOptions | LinuxAppOptions); 52 | 53 | /** Windows configuration options 54 | * 55 | * References: 56 | * - https://learn.microsoft.com/en-us/windows/win32/msi/version 57 | * - https://learn.microsoft.com/en-gb/windows/win32/sbscs/application-manifests 58 | * - https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/deployment/trustinfo-element-clickonce-application?view=vs-2015#requestedexecutionlevel 59 | * - https://learn.microsoft.com/en-gb/windows/win32/menurc/versioninfo-resource 60 | */ 61 | export interface WindowsAppOptions { 62 | /** The name of the application */ 63 | name?: string, 64 | /** The version of the application */ 65 | version?: string, 66 | /** Additional information that should be displayed for diagnostic purposes. */ 67 | comments?: string, 68 | /** Company that produced the file—for example, Microsoft Corporation or Standard Microsystems Corporation, Inc. This string is required. */ 69 | company: string, 70 | /** File description to be presented to users. This string may be displayed in a list box when the user is choosing files to install. For example, Keyboard Driver for AT-Style Keyboards. This string is required. */ 71 | fileDescription: string, 72 | /** Version number of the file. For example, 3.10 or 5.00.RC2. This string is required. */ 73 | fileVersion: string, 74 | /** The path to the icon file. It should be a .ico file. */ 75 | icon?: string, 76 | /** Internal name of the file, if one exists—for example, a module name if the file is a dynamic-link library. If the file has no internal name, this string should be the original filename, without extension. This string is required. */ 77 | internalName: string, 78 | /** Copyright notices that apply to the file. This should include the full text of all notices, legal symbols, copyright dates, and so on. This string is optional. */ 79 | legalCopyright?: string, 80 | /** Trademarks and registered trademarks that apply to the file. This should include the full text of all notices, legal symbols, trademark numbers, and so on. This string is optional. */ 81 | legalTrademark?: string, 82 | /** Original name of the file, not including a path. This information enables an application to determine whether a file has been renamed by a user. The format of the name depends on the file system for which the file was created. This string is required. */ 83 | originalFilename: string, 84 | /** Information about a private version of the file—for example, Built by TESTER1 on \\TESTBED. This string should be present only if VS_FF_PRIVATEBUILD is specified in the fileflags parameter of the root block. */ 85 | privateBuild?: string, 86 | /** Name of the product with which the file is distributed. This string is required. */ 87 | productName: string, 88 | /** Version of the product with which the file is distributed—for example, 3.10 or 5.00.RC2. This string is required. */ 89 | productVersion: string, 90 | /** Text that specifies how this version of the file differs from the standard version—for example, Private build for TESTER1 solving mouse problems on M250 and M250E computers. This string should be present only if VS_FF_SPECIALBUILD is specified in the fileflags parameter of the root block. */ 91 | specialBuild?: string, 92 | /** Language of the file, defined by Microsoft, see: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a */ 93 | languageCode?: number, 94 | } 95 | 96 | /** Linux configuration options 97 | * 98 | * References: 99 | * https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html 100 | */ 101 | export interface LinuxAppOptions { 102 | /** Name of the application */ 103 | name?: string, 104 | /** Generic name of the application */ 105 | genericName?: string, 106 | /** If true the application is not displayed */ 107 | noDisplay?: boolean, 108 | /** Tooltip for the entry, for example "View sites on the Internet". */ 109 | comment?: string, 110 | /** Icon to display in file manager, menus, etc. */ 111 | icon?: string, 112 | /** A list of strings identifying the desktop environments that should (/not) display a given desktop entry */ 113 | onlyShowIn?: string[], 114 | /** A list of strings identifying the desktop environments that should (/not) display a given desktop entry */ 115 | notShowIn?: string[], 116 | /** A boolean value specifying if D-Bus activation is supported for this application */ 117 | dBusActivatable?: boolean, 118 | /** Path to an executable file on disk used to determine if the program is actually installed */ 119 | tryExec?: string, 120 | /** Program to execute, possibly with arguments. */ 121 | exec?: string, 122 | /** If entry is of type Application, the working directory to run the program in. */ 123 | path?: string, 124 | /** Whether the program runs in a terminal window. */ 125 | terminal?: boolean, 126 | /** Identifiers for application actions. */ 127 | actions?: string[], 128 | /** The MIME type(s) supported by this application. */ 129 | mimeType?: string[], 130 | /** Categories in which the entry should be shown in a menu */ 131 | categories?: string[], 132 | /** A list of interfaces that this application implements. */ 133 | implements?: string[], 134 | /** A list of strings which may be used in addition to other metadata to describe this entry. */ 135 | keywords?: string[], 136 | /** If true, it is KNOWN that the application will send a "remove" message when started with the DESKTOP_STARTUP_ID environment variable set. If false, it is KNOWN that the application does not work with startup notification at all. */ 137 | startupNotify?: boolean, 138 | /** If specified, it is known that the application will map at least one window with the given string as its WM class or WM name hin */ 139 | startupWMClass?: string, 140 | /** If true, the application prefers to be run on a more powerful discrete GPU if available. */ 141 | prefersNonDefaultGPU?: boolean, 142 | /** If true, the application has a single main window, and does not support having an additional one opened. */ 143 | singleMainWindow?: string, 144 | } 145 | 146 | /** OSX resource configuration options 147 | * 148 | * References: 149 | * https://developer.apple.com/documentation/bundleresources/information_property_list 150 | */ 151 | export interface OsxAppOptions { 152 | /** The name of the application */ 153 | name?: string, 154 | /** The path to the icon file. It should be a .icns file. */ 155 | icon?: string, 156 | /** The category that best describes your app for the App Store. */ 157 | LSApplicationCategoryType?: string, 158 | /** A unique identifier for a bundle usually in reverse DNS format. */ 159 | CFBundleIdentifier?: string, 160 | /** A user-visible short name for the bundle. */ 161 | CFBundleName?: string, 162 | /** The user-visible name for the bundle. */ 163 | CFBundleDisplayName?: string, 164 | /** A replacement for the app name in text-to-speech operations. */ 165 | CFBundleSpokenName?: string, 166 | /** The version of the build that identifies an iteration of the bundle. */ 167 | CFBundleVersion?: string, 168 | /** The release or version number of the bundle. */ 169 | CFBundleShortVersionString?: string, 170 | /** A human-readable copyright notice for the bundle. */ 171 | NSHumanReadableCopyright?: string, 172 | /** A human-readable description of why the application needs access to the local network. */ 173 | NSLocalNetworkUsageDescription?: string, 174 | } 175 | 176 | /** 177 | * Automates building an NW.js application. 178 | */ 179 | declare function nwbuild

(options: Options

): Promise; 180 | 181 | export default nwbuild; 182 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import child_process from 'node:child_process'; 2 | import console from 'node:console'; 3 | import fs from 'node:fs'; 4 | import path from 'node:path'; 5 | 6 | import bld from './bld.js'; 7 | import get from './get/index.js'; 8 | import run from './run.js'; 9 | import util from './util.js'; 10 | 11 | /** 12 | * @typedef {object} Options Configuration options 13 | * @property {"get" | "run" | "build"} [mode="build"] Choose between get, run or build mode 14 | * @property {"latest" | "stable" | string} [version="latest"] Runtime version 15 | * @property {"normal" | "sdk"} [flavor="normal"] Runtime flavor 16 | * @property {"linux" | "osx" | "win"} platform Host platform 17 | * @property {"ia32" | "x64" | "arm64"} arch Host architecture 18 | * @property {"https://dl.nwjs.io" | string} [downloadUrl="https://dl.nwjs.io"] Download server 19 | * @property {"https://nwjs.io/versions" | string} [manifestUrl="https://nwjs.io/versions"] Versions manifest 20 | * @property {"./cache" | string} [cacheDir="./cache"] Directory to cache NW binaries 21 | * @property {"./" | string} [srcDir="./"] File paths to application code 22 | * @property {"./out" | string} [outDir="./out"] Directory to store build artifacts 23 | * @property {object} app Refer to Linux/Windows Specific Options under Getting Started in the docs 24 | * @property {boolean} [cache=true] If true the existing cache is used. Otherwise it removes and redownloads it. 25 | * @property {boolean} [ffmpeg=false] If true the chromium ffmpeg is replaced by community version 26 | * @property {boolean} [glob=true] If true file globbing is enabled when parsing srcDir. 27 | * @property {"error" | "warn" | "info" | "debug"} [logLevel="info"] Specify level of logging. 28 | * @property {boolean} [shaSum = true] If true, shasum is enabled. Otherwise, disabled. 29 | * @property {boolean | "zip" | "tar" | "tgz"} [zip=false] If true, "zip", "tar" or "tgz" the outDir directory is compressed. 30 | * @property {boolean | string | object} [managedManifest = false] Managed manifest mode 31 | * @property {false | "gyp"} [nodeAddon = false] Rebuild Node native addons 32 | * @property {boolean} [cli=false] If true the CLI is used to parse options. This option is used internally. 33 | */ 34 | 35 | /** 36 | * Main module exported. 37 | * @async 38 | * @function 39 | * @param {Options} options Options 40 | * @returns {Promise} - Returns NW.js process if run mode, otherwise returns `undefined`. 41 | */ 42 | async function nwbuild(options) { 43 | let built; 44 | let releaseInfo = {}; 45 | let manifest = { 46 | path: '', 47 | json: undefined, 48 | }; 49 | 50 | try { 51 | /* Parse options */ 52 | options = await util.parse(options, manifest); 53 | util.log('debug', 'info', 'Parse initial options'); 54 | 55 | util.log('debug', 'info', 'Get node manifest...'); 56 | manifest = await util.getNodeManifest({ srcDir: options.srcDir, glob: options.glob }); 57 | if (typeof manifest.json?.nwbuild === 'object') { 58 | options = manifest.json.nwbuild; 59 | } 60 | 61 | util.log('info', options.logLevel, 'Parse final options using node manifest'); 62 | options = await util.parse(options, manifest.json); 63 | util.log('debug', options.logLevel, 'Manifest: ', `${manifest.path}\n${manifest.json}\n`); 64 | 65 | built = fs.existsSync(options.cacheDir); 66 | if (built === false) { 67 | await fs.promises.mkdir(options.cacheDir, { recursive: true }); 68 | } 69 | 70 | if (options.mode === 'build') { 71 | built = fs.existsSync(options.outDir); 72 | if (built === false) { 73 | await fs.promises.mkdir(options.outDir, { recursive: true }); 74 | } 75 | } 76 | 77 | /* Validate options.version to get the version specific release info */ 78 | util.log('info', options.logLevel, 'Get version specific release info...'); 79 | releaseInfo = await util.getReleaseInfo( 80 | options.version, 81 | options.platform, 82 | options.arch, 83 | options.cacheDir, 84 | options.manifestUrl, 85 | ); 86 | util.log('debug', options.logLevel, `Release info:\n${JSON.stringify(releaseInfo, null, 2)}\n`); 87 | 88 | util.log('info', options.logLevel, 'Validate options.* ...'); 89 | await util.validate(options, releaseInfo); 90 | util.log('debug', options.logLevel, `Options:\n${JSON.stringify(options, null, 2)}`); 91 | 92 | /* Remove leading "v" from version string */ 93 | options.version = releaseInfo.version.slice(1); 94 | 95 | util.log('info', options.logLevel, 'Getting NW.js and related binaries...'); 96 | await get({ 97 | version: options.version, 98 | flavor: options.flavor, 99 | platform: options.platform, 100 | arch: options.arch, 101 | downloadUrl: options.downloadUrl, 102 | cacheDir: options.cacheDir, 103 | cache: options.cache, 104 | ffmpeg: options.ffmpeg, 105 | nativeAddon: options.nativeAddon, 106 | shaSum: options.shaSum, 107 | logLevel: options.logLevel, 108 | }); 109 | 110 | if (options.mode === 'get') { 111 | // Do nothing else since we have already downloaded the binaries. 112 | return undefined; 113 | } 114 | 115 | if (options.mode === 'run') { 116 | util.log('info', options.logLevel, 'Running NW.js in run mode...'); 117 | const nwProcess = await run({ 118 | version: options.version, 119 | flavor: options.flavor, 120 | platform: options.platform, 121 | arch: options.arch, 122 | srcDir: options.srcDir, 123 | cacheDir: options.cacheDir, 124 | glob: options.glob, 125 | argv: options.argv, 126 | }); 127 | return nwProcess; 128 | } else if (options.mode === 'build') { 129 | util.log('info', options.logLevel, `Build a NW.js application for ${options.platform} ${options.arch}...`); 130 | await bld({ 131 | version: options.version, 132 | flavor: options.flavor, 133 | platform: options.platform, 134 | arch: options.arch, 135 | manifestUrl: options.manifestUrl, 136 | srcDir: options.srcDir, 137 | cacheDir: options.cacheDir, 138 | outDir: options.outDir, 139 | app: options.app, 140 | glob: options.glob, 141 | managedManifest: options.managedManifest, 142 | nativeAddon: options.nativeAddon, 143 | zip: options.zip, 144 | releaseInfo: releaseInfo, 145 | }); 146 | util.log('info', options.logLevel, `Appliction is available at ${path.resolve(options.outDir)}`); 147 | } 148 | } catch (error) { 149 | console.error(error); 150 | throw error; 151 | } 152 | 153 | return undefined; 154 | } 155 | 156 | export default nwbuild; 157 | -------------------------------------------------------------------------------- /src/postinstall.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import url from 'node:url'; 3 | 4 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 5 | 6 | const baseVoltaPath = path.resolve(path.join(__dirname, '..', 'node_modules', 'base-volta-off-of-nwjs', 'index.js')); 7 | 8 | /* Execute the script in development mode only since it is used during testing */ 9 | import(baseVoltaPath) 10 | .then(() => console.log('Node version is updated')) 11 | .catch((error) => console.log(error)); 12 | -------------------------------------------------------------------------------- /src/run.js: -------------------------------------------------------------------------------- 1 | import child_process from 'node:child_process'; 2 | import console from 'node:console'; 3 | import path from 'node:path'; 4 | import process from 'node:process'; 5 | 6 | import util from './util.js'; 7 | 8 | /** 9 | * @typedef {object} RunOptions 10 | * @property {string | "latest" | "stable" | "lts"} [version = "latest"] Runtime version 11 | * @property {"normal" | "sdk"} [flavor = "normal"] Build flavor 12 | * @property {"linux" | "osx" | "win"} [platform] Target platform 13 | * @property {"ia32" | "x64" | "arm64"} [arch] Target arch 14 | * @property {string} [srcDir = "./src"] Source directory 15 | * @property {string} [cacheDir = "./cache"] Cache directory 16 | * @property {boolean} [glob = false] If true, throw error 17 | * @property {string[]} [argv = []] CLI arguments 18 | */ 19 | 20 | /** 21 | * Run NW.js application. 22 | * @async 23 | * @function 24 | * @param {RunOptions} options Run mode options 25 | * @returns {Promise} - A Node.js process object 26 | */ 27 | async function run({ 28 | version = 'latest', 29 | flavor = 'normal', 30 | platform = util.PLATFORM_KV[process.platform], 31 | arch = util.ARCH_KV[process.arch], 32 | srcDir = './src', 33 | cacheDir = './cache', 34 | glob = false, 35 | argv = [], 36 | }) { 37 | /** 38 | * @type {child_process.ChildProcess | null} 39 | */ 40 | let nwProcess = null; 41 | 42 | try { 43 | if (util.EXE_NAME[platform] === undefined) { 44 | throw new Error('Unsupported platform.'); 45 | } 46 | if (glob === true) { 47 | throw new Error('Globbing is not supported with run mode.'); 48 | } 49 | 50 | const nwDir = path.resolve( 51 | cacheDir, 52 | `nwjs${flavor === 'sdk' ? '-sdk' : ''}-v${version}-${platform}-${arch}`, 53 | ); 54 | 55 | return new Promise((res, rej) => { 56 | /* It is assumed that the package.json is located at `${options.srcDir}/package.json` */ 57 | nwProcess = child_process.spawn( 58 | path.resolve(nwDir, util.EXE_NAME[platform]), 59 | [...[srcDir], ...argv], 60 | { stdio: 'inherit' }, 61 | ); 62 | 63 | nwProcess.on('close', () => { 64 | res(); 65 | }); 66 | 67 | nwProcess.on('error', (error) => { 68 | console.error(error); 69 | rej(error); 70 | }); 71 | }); 72 | } catch (error) { 73 | console.error(error); 74 | } 75 | return nwProcess; 76 | } 77 | 78 | export default run; 79 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | import console from 'node:console'; 2 | import fs from 'node:fs'; 3 | import https from 'node:https'; 4 | import path from 'node:path'; 5 | import process from 'node:process'; 6 | 7 | import * as GlobModule from 'glob'; 8 | import semver from 'semver'; 9 | 10 | /** 11 | * Get manifest (array of NW release metadata) from URL. 12 | * @param {string} manifestUrl Url to manifest 13 | * @returns {Promise} - Manifest object 14 | */ 15 | function getManifest(manifestUrl) { 16 | let chunks = undefined; 17 | 18 | return new Promise((resolve) => { 19 | const req = https.get(manifestUrl, (response) => { 20 | response.on('data', (chunk) => { 21 | chunks += chunk; 22 | }); 23 | 24 | response.on('error', (e) => { 25 | console.error(e); 26 | resolve(undefined); 27 | }); 28 | 29 | response.on('end', () => { 30 | resolve(chunks); 31 | }); 32 | }); 33 | req.on('error', (e) => { 34 | console.error(e); 35 | resolve(undefined); 36 | }); 37 | }); 38 | } 39 | 40 | /** 41 | * Get version specific release metadata. 42 | * @param {string} version NW version 43 | * @param {string} platform NW platform 44 | * @param {string} arch NW architecture 45 | * @param {string} cacheDir Directory to store NW binaries 46 | * @param {string} manifestUrl Url to manifest 47 | * @returns {object} Version specific release info 48 | */ 49 | async function getReleaseInfo( 50 | version, 51 | platform, 52 | arch, 53 | cacheDir, 54 | manifestUrl, 55 | ) { 56 | let releaseData = undefined; 57 | let manifestPath = undefined; 58 | if (platform === 'osx' && arch === 'arm64') { 59 | manifestPath = path.resolve(cacheDir, 'manifest.mac.arm.json'); 60 | } else { 61 | manifestPath = path.resolve(cacheDir, 'manifest.json'); 62 | } 63 | 64 | try { 65 | const data = await getManifest(manifestUrl); 66 | if (data !== undefined) { 67 | await fs.promises.writeFile(manifestPath, data.slice(9)); 68 | } 69 | 70 | let manifest = JSON.parse(await fs.promises.readFile(manifestPath)); 71 | if (version === 'latest' || version === 'stable' || version === 'lts') { 72 | // Remove leading "v" from version string 73 | version = manifest[version].slice(1); 74 | } 75 | 76 | releaseData = manifest.versions.find( 77 | (release) => release.version === `v${version}`, 78 | ); 79 | } catch { 80 | console.error( 81 | 'The version manifest does not exist/was not downloaded. Please try again in some time.', 82 | ); 83 | } 84 | return releaseData; 85 | } 86 | 87 | const PLATFORM_KV = { 88 | darwin: 'osx', 89 | linux: 'linux', 90 | win32: 'win', 91 | }; 92 | 93 | const ARCH_KV = { 94 | x64: 'x64', 95 | ia32: 'ia32', 96 | arm64: 'arm64', 97 | }; 98 | 99 | const EXE_NAME = { 100 | win: 'nw.exe', 101 | osx: 'nwjs.app/Contents/MacOS/nwjs', 102 | linux: 'nw', 103 | }; 104 | 105 | /** 106 | * Glob files. 107 | * @async 108 | * @function 109 | * @param {object} options - glob file options 110 | * @param {string | string[]} options.srcDir - app src dir 111 | * @param {boolean} options.glob - glob flag 112 | * @returns {Promise} - Returns array of file paths 113 | */ 114 | async function globFiles({ 115 | srcDir, 116 | glob, 117 | }) { 118 | let files; 119 | let patterns; 120 | if (glob) { 121 | files = []; 122 | patterns = []; 123 | if (Array.isArray(srcDir)) { 124 | patterns = srcDir; 125 | } else { 126 | patterns = srcDir.split(' '); 127 | } 128 | 129 | for (const pattern of patterns) { 130 | let filePath = await GlobModule.glob(pattern); 131 | files.push(...filePath); 132 | } 133 | } else { 134 | files = srcDir; 135 | } 136 | return files; 137 | } 138 | 139 | /** 140 | * Get Node manifest. 141 | * @async 142 | * @function 143 | * @param {object} options - node manifest options 144 | * @param {string | string []} options.srcDir - src dir 145 | * @param {boolean} options.glob - glob flag 146 | * @returns {Promise.<{path: string, json: object}>} - Node manifest 147 | */ 148 | async function getNodeManifest({ 149 | srcDir, glob 150 | }) { 151 | let manifest = { 152 | path: '', 153 | json: undefined, 154 | }; 155 | let files; 156 | if (glob === true) { 157 | files = await globFiles({ srcDir, glob }); 158 | for (const file of files) { 159 | if (path.basename(file) === 'package.json' && manifest.json === undefined) { 160 | manifest.path = file; 161 | manifest.json = JSON.parse(await fs.promises.readFile(file)); 162 | } 163 | } 164 | } else { 165 | manifest.path = path.resolve(srcDir, 'package.json'); 166 | manifest.json = JSON.parse(await fs.promises.readFile(path.resolve(srcDir, 'package.json'))); 167 | } 168 | 169 | if (manifest.json === undefined) { 170 | throw new Error('package.json not found in srcDir file glob patterns.'); 171 | } 172 | 173 | return manifest; 174 | } 175 | 176 | /** 177 | * Function to convert `'true'` and `'false'` into `true` and `false`. 178 | * `commander` does not do the conversion automatically. 179 | * @param {any} option - a boolean type option 180 | * @returns {any} Usually `undefined`, `true` or `false`. if not then it is validated later on. 181 | */ 182 | function str2Bool(option) { 183 | if (typeof option === 'string') { 184 | if (option === 'true') { 185 | return true; 186 | } else if (option === 'false') { 187 | return false; 188 | } 189 | } else { 190 | return option; 191 | } 192 | } 193 | 194 | /** 195 | * Parse options. 196 | * @param {import("../../index.js").Options} options Options 197 | * @param {object} pkg Package.json as JSON 198 | * @returns {Promise} Options 199 | */ 200 | export const parse = async (options, pkg) => { 201 | options = options ?? {}; 202 | options.mode = options.mode ?? 'build'; 203 | 204 | options.version = options.version ?? 'latest'; 205 | options.flavor = options.flavor ?? 'normal'; 206 | options.platform = options.platform ?? PLATFORM_KV[process.platform]; 207 | options.arch = options.arch ?? ARCH_KV[process.arch]; 208 | options.downloadUrl = options.downloadUrl ?? 'https://dl.nwjs.io'; 209 | options.manifestUrl = options.manifestUrl ?? 'https://nwjs.io/versions'; 210 | options.cacheDir = options.cacheDir ?? './cache'; 211 | options.cache = str2Bool(options.cache ?? true); 212 | options.ffmpeg = str2Bool(options.ffmpeg ?? false); 213 | options.logLevel = options.logLevel ?? 'info'; 214 | options.shaSum = options.shaSum ?? true; 215 | 216 | if (options.mode === 'get') { 217 | return { ...options }; 218 | } 219 | 220 | options.argv = options.argv ?? []; 221 | options.glob = str2Bool(options.glob) ?? true; 222 | options.srcDir = options.srcDir ?? (options.glob ? './*' : '.'); 223 | 224 | if (options.mode === 'run') { 225 | return { ...options }; 226 | } 227 | 228 | options.outDir = path.resolve(options.outDir ?? './out'); 229 | options.zip = str2Bool(options.zip) ?? false; 230 | 231 | options.managedManifest = str2Bool(options.managedManifest) ?? false; 232 | options.nativeAddon = str2Bool(options.nativeAddon) ?? false; 233 | 234 | options.app = options.app ?? {}; 235 | options.app.name = options.app.name ?? pkg.name; 236 | /* Since the `parse` function is called twice, the first time `pkg` is `{}` and `options.app.name` is `undefined`. */ 237 | if (options.app.name) { 238 | /* Remove special and control characters from app.name to mitigate potential path traversal. */ 239 | options.app.name = options.app.name.replace(/[<>:"/\\|?*\u0000-\u001F]/g, ''); 240 | } 241 | /* Path to where the icon currently is in the filesystem */ 242 | options.app.icon = options.app.icon ?? pkg.window?.icon ?? undefined; 243 | if (options.app.icon) { 244 | options.app.icon = path.resolve(options.app.icon); 245 | } 246 | 247 | // TODO(#737): move this out 248 | if (options.platform === 'linux') { 249 | // linux desktop entry file configurations options 250 | options.app.genericName = options.app.genericName ?? undefined; 251 | options.app.noDisplay = options.app.noDisplay ?? undefined; 252 | options.app.comment = options.app.comment ?? undefined; 253 | options.app.hidden = options.app.hidden ?? undefined; 254 | options.app.onlyShowIn = options.app.onlyShowIn ?? undefined; 255 | options.app.notShowIn = options.app.notShowIn ?? undefined; 256 | options.app.dBusActivatable = options.app.dBusActivatable ?? undefined; 257 | options.app.tryExec = options.app.tryExec ?? undefined; 258 | options.app.exec = options.app.exec ?? undefined; 259 | if (options.app.exec) { 260 | options.app.exec = path.resolve(options.app.exec); 261 | } 262 | options.app.path = options.app.path ?? undefined; 263 | options.app.terminal = options.app.terminal ?? undefined; 264 | options.app.actions = options.app.actions ?? undefined; 265 | options.app.mimeType = options.app.mimeType ?? undefined; 266 | options.app.categories = options.app.categories ?? undefined; 267 | options.app.implements = options.app.implements ?? undefined; 268 | options.app.keywords = options.app.keywords ?? undefined; 269 | options.app.startupNotify = options.app.startupNotify ?? undefined; 270 | options.app.startupWMClass = options.app.startupWMClass ?? undefined; 271 | options.app.prefersNonDefaultGPU = 272 | options.app.prefersNonDefaultGPU ?? undefined; 273 | options.app.singleMainWindow = options.app.singleMainWindow ?? undefined; 274 | } 275 | if (options.platform === 'win') { 276 | // windows configuration options 277 | options.app.version = options.app.version ?? pkg.version; 278 | options.app.comments = options.app.comments ?? undefined; 279 | options.app.company = options.app.company ?? pkg.author; 280 | options.app.fileDescription = 281 | options.app.fileDescription ?? pkg.description; 282 | options.app.fileVersion = options.app.fileVersion ?? options.app.version ?? pkg.version; 283 | options.app.internalName = options.app.internalName ?? pkg.name; 284 | options.app.legalCopyright = options.app.legalCopyright ?? undefined; 285 | options.app.legalTrademark = options.app.legalTrademark ?? undefined; 286 | options.app.originalFilename = options.app.originalFilename ?? options.app.name; 287 | options.app.privateBuild = options.app.privateBuild ?? undefined; 288 | options.app.productName = options.app.productName ?? pkg.name; 289 | options.app.productVersion = options.app.productVersion ?? pkg.version; 290 | options.app.specialBuild = options.app.specialBuild ?? undefined; 291 | options.app.languageCode = options.app.languageCode ?? 1033; 292 | } 293 | 294 | if (options.platform === 'osx') { 295 | options.app.LSApplicationCategoryType = 296 | options.app.LSApplicationCategoryType ?? undefined; 297 | options.app.CFBundleIdentifier = 298 | options.app.CFBundleIdentifier ?? options.app.name; 299 | options.app.CFBundleName = options.app.CFBundleName ?? pkg.name; 300 | options.app.CFBundleDisplayName = 301 | options.app.CFBundleDisplayName ?? pkg.name; 302 | options.app.CFBundleSpokenName = options.app.CFBundleSpokenName ?? pkg.name; 303 | options.app.CFBundleShortVersionString = 304 | options.app.CFBundleVersion ?? pkg.version; 305 | options.app.CFBundleVersion = 306 | options.app.CFBundleShortVersionString ?? pkg.version; 307 | options.app.NSHumanReadableCopyright = 308 | options.app.NSHumanReadableCopyright ?? undefined; 309 | options.app.NSLocalNetworkUsageDescription = 310 | options.app.NSLocalNetworkUsageDescription ?? undefined; 311 | } 312 | 313 | return { ...options }; 314 | }; 315 | 316 | /** 317 | * Validate options. 318 | * @param {import("../index.js").Options} options Options 319 | * @param {object} releaseInfo Version specific NW release info 320 | * @returns {Promise} Return undefined if options are valid 321 | * @throws {Error} Throw error if options are invalid 322 | */ 323 | export const validate = async (options, releaseInfo) => { 324 | if ( 325 | options.mode !== 'get' && 326 | options.mode !== 'run' && 327 | options.mode !== 'build' 328 | ) { 329 | throw new Error( 330 | `Unknown mode ${options.mode}. Expected "get", "run" or "build".`, 331 | ); 332 | } 333 | if (typeof releaseInfo === 'undefined') { 334 | throw new Error( 335 | 'Either the specific version info does not exist or the version manifest itself does not exist. In case of the latter, please check your internet connection and try again later.', 336 | ); 337 | } 338 | if (!releaseInfo.flavors.includes(options.flavor)) { 339 | throw new Error( 340 | `${options.flavor} flavor is not supported by this download server.`, 341 | ); 342 | } 343 | if ( 344 | options.platform && 345 | options.arch && 346 | !releaseInfo.files.includes(`${options.platform}-${options.arch}`) 347 | ) { 348 | throw new Error( 349 | `Platform ${options.platform} and architecture ${options.arch} is not supported by this download server.`, 350 | ); 351 | } 352 | if (typeof options.downloadUrl === 'string' && !options.downloadUrl.startsWith('http') && !options.downloadUrl.startsWith('file')) { 353 | throw new Error('Expected options.downloadUrl to be a string and starts with `http` or `file`.'); 354 | } 355 | if (typeof options.manifestUrl === 'string' && !options.manifestUrl.startsWith('http') && !options.manifestUrl.startsWith('file')) { 356 | throw new Error('Expected options.manifestUrl to be a string and starts with `http` or `file`.'); 357 | } 358 | if (typeof options.cacheDir !== 'string') { 359 | throw new Error('Expected options.cacheDir to be a string. Got ' + typeof options.cacheDir); 360 | } 361 | 362 | if (typeof options.cache !== 'boolean') { 363 | throw new Error( 364 | 'Expected options.cache to be a boolean. Got ' + typeof options.cache, 365 | ); 366 | } 367 | if (typeof options.ffmpeg !== 'boolean') { 368 | throw new Error( 369 | 'Expected options.ffmpeg to be a boolean. Got ' + typeof options.ffmpeg, 370 | ); 371 | } 372 | 373 | if ( 374 | options.logLevel !== 'error' && 375 | options.logLevel !== 'warn' && 376 | options.logLevel !== 'info' && 377 | options.logLevel !== 'debug' 378 | ) { 379 | throw new Error( 380 | 'Expected options.logLevel to be \'error\', \'warn\', \'info\' or \'debug\'. Got ' + 381 | options.logLevel, 382 | ); 383 | } 384 | 385 | if (typeof options.shaSum !== 'boolean') { 386 | throw new Error( 387 | 'Expected options.shaSum to be a boolean. Got ' + typeof options.shaSum, 388 | ); 389 | } 390 | 391 | if (options.mode === 'get') { 392 | return undefined; 393 | } 394 | if (typeof options.srcDir !== 'string' && !Array.isArray(options.srcDir)) { 395 | throw new Error('Expected options.srcDir to be a string or Array. Got ' + typeof options.srcDir); 396 | } 397 | if (!Array.isArray(options.argv)) { 398 | throw new Error( 399 | 'Expected options.argv to be an array. Got ' + typeof options.argv, 400 | ); 401 | } 402 | if (typeof options.glob !== 'boolean') { 403 | throw new Error( 404 | 'Expected options.glob to be a boolean. Got ' + typeof options.glob, 405 | ); 406 | } 407 | 408 | if (options.mode === 'run') { 409 | return undefined; 410 | } 411 | 412 | if (typeof options.outDir !== 'string') { 413 | throw new Error('Expected options.outDir to be a string. Got ' + typeof options.outDir); 414 | } 415 | 416 | if ( 417 | typeof options.managedManifest !== 'boolean' && 418 | typeof options.managedManifest !== 'object' && 419 | typeof options.managedManifest !== 'string' 420 | ) { 421 | throw new Error( 422 | 'Expected options.managedManifest to be a boolean, object or string. Got ' + 423 | typeof options.managedManifest, 424 | ); 425 | } 426 | 427 | if (typeof options.managedManifest === 'object') { 428 | if (options.managedManifest.name === undefined) { 429 | throw new Error('Expected NW.js Manifest to have a `name` property.'); 430 | } 431 | if (options.managedManifest.main === undefined) { 432 | throw new Error('Expected NW.js Manifest to have a `main` property.'); 433 | } 434 | } 435 | 436 | if (typeof options.nativeAddon !== 'boolean') { 437 | if (typeof options.nativeAddon !== 'boolean' && typeof options.nativeAddon !== 'string') { 438 | throw new Error('Expected options.nativeAddon to be a boolean or string type. Got ' + typeof options.nativeAddon); 439 | } 440 | 441 | if (semver.parse(options.version).minor >= '83' && options.nativeAddon !== false) { 442 | throw new Error('Native addons are not supported for NW.js v0.82.0 and below'); 443 | } 444 | 445 | if (typeof options.zip !== 'boolean' & 446 | options.zip !== 'zip' && 447 | options.zip !== 'tar' && 448 | options.zip !== 'tgz') { 449 | throw new Error('Expected options.zip to be a boolean, `zip`, `tar` or `tgz`. Got ' + typeof options.zip); 450 | } 451 | } 452 | 453 | if (options.platform === 'linux') { 454 | if (options.app.name && typeof options.app.name !== 'string') { 455 | throw new Error('Expected options.app.name to be a string. Got ' + options.app.name); 456 | } 457 | if (options.app.genericName && typeof options.app.genericName !== 'string') { 458 | throw new Error('Expected options.app.genericName to be a string. Got ' + options.app.genericName); 459 | } 460 | if (options.app.noDisplay && typeof options.app.noDisplay !== 'boolean') { 461 | throw new Error('Expected options.app.noDisplay to be a boolean. Got ' + options.app.noDisplay); 462 | } 463 | if (options.app.comment && typeof options.app.comment !== 'string') { 464 | throw new Error('Expected options.app.comment to be a string. Got ' + options.app.comment); 465 | } 466 | if (options.app.icon && typeof options.app.icon !== 'string') { 467 | throw new Error('Expected options.app.icon to be a string. Got ' + options.app.icon); 468 | } 469 | if (options.app.hidden && typeof options.app.hidden !== 'string') { 470 | throw new Error('Expected options.app.hidden to be a string. Got ' + options.app.hidden); 471 | } 472 | if (options.app.onlyShowIn && !Array.isArray(options.app.onlyShowIn)) { 473 | throw new Error('Expected options.app.onlyShowIn to be an Array. Got ' + options.app.onlyShowIn); 474 | } 475 | if (options.app.notShowIn && !Array.isArray(options.app.notShowIn)) { 476 | throw new Error('Expected options.app.notShowIn to be an Array. Got ' + options.app.notShowIn); 477 | } 478 | if (options.app.dBusActivatable && typeof options.app.dBusActivatable !== 'boolean') { 479 | throw new Error('Expected options.app.dBusActivatable to be a boolean. Got ' + options.app.dBusActivatable); 480 | } 481 | if (options.app.tryExec && typeof options.app.tryExec !== 'string') { 482 | throw new Error('Expected options.app.tryExec to be a string. Got ' + options.app.tryExec); 483 | } 484 | if (options.app.exec && typeof options.app.exec !== 'string') { 485 | throw new Error('Expected options.app.exec to be a string. Got ' + options.app.exec); 486 | } 487 | if (options.app.path && typeof options.app.path !== 'string') { 488 | throw new Error('Expected options.app.path to be a string. Got ' + options.app.path); 489 | } 490 | if (options.app.terminal && typeof options.app.terminal !== 'boolean') { 491 | throw new Error('Expected options.app.terminal to be a boolean. Got ' + options.app.terminal); 492 | } 493 | if (options.app.actions && !Array.isArray(options.app.actions)) { 494 | throw new Error('Expected options.app.actions to be a Array. Got ' + options.app.actions); 495 | } 496 | if (options.app.mimeType && !Array.isArray(options.app.mimeType)) { 497 | throw new Error('Expected options.app.mimeType to be a Array. Got ' + options.app.mimeType); 498 | } 499 | if (options.app.categories && !Array.isArray(options.app.categories)) { 500 | throw new Error('Expected options.app.categories to be a Array. Got ' + options.app.categories); 501 | } 502 | if (options.app.implements && !Array.isArray(options.app.implements)) { 503 | throw new Error('Expected options.app.implements to be a Array. Got ' + options.app.implements); 504 | } 505 | if (options.app.keywords && !Array.isArray(options.app.keywords)) { 506 | throw new Error('Expected options.app.keywords to be a Array. Got ' + options.app.keywords); 507 | } 508 | if (options.app.startupNotify && typeof options.app.startupNotify !== 'boolean') { 509 | throw new Error('Expected options.app.startupNotify to be a boolean. Got ' + options.app.startupNotify); 510 | } 511 | if (options.app.startupWMClass && typeof options.app.startupWMClass !== 'string') { 512 | throw new Error('Expected options.app.startupWMClass to be a string. Got ' + options.app.startupWMClass); 513 | } 514 | if (options.app.prefersNonDefaultGPU && typeof options.app.prefersNonDefaultGPU !== 'boolean') { 515 | throw new Error('Expected options.app.prefersNonDefaultGPU to be a boolean. Got ' + options.app.prefersNonDefaultGPU); 516 | } 517 | if (options.app.singleMainWindow && typeof options.app.singleMainWindow !== 'string') { 518 | throw new Error('Expected options.app.singleMainWindow to be a string. Got ' + options.app.singleMainWindow); 519 | } 520 | } else if (options.platform === 'osx') { 521 | if (typeof options.app.name !== 'string') { 522 | throw new Error('Expected options.app.name to be a string. Got ' + options.app.name); 523 | } 524 | if (typeof options.app.icon !== 'string') { 525 | throw new Error('Expected options.app.icon to be a string. Got ' + options.app.icon); 526 | } 527 | if (typeof options.app.LSApplicationCategoryType !== 'string') { 528 | throw new Error('Expected options.app.LSApplicationCategoryType to be a string. Got ' + options.app.LSApplicationCategoryType); 529 | } 530 | if (typeof options.app.CFBundleIdentifier !== 'string') { 531 | throw new Error('Expected options.app.CFBundleIdentifier to be a string. Got ' + options.app.CFBundleIdentifier); 532 | } 533 | if (typeof options.app.CFBundleName !== 'string') { 534 | throw new Error('Expected options.app.CFBundleName to be a string. Got ' + options.app.CFBundleName); 535 | } 536 | if (typeof options.app.CFBundleDisplayName !== 'string') { 537 | throw new Error('Expected options.app.CFBundleDisplayName to be a string. Got ' + options.app.CFBundleDisplayName); 538 | } 539 | if (typeof options.app.CFBundleSpokenName !== 'string') { 540 | throw new Error('Expected options.app.CFBundleSpokenName to be a string. Got ' + options.app.CFBundleSpokenName); 541 | } 542 | if (typeof options.app.CFBundleVersion !== 'string') { 543 | throw new Error('Expected options.app.CFBundleVersion to be a string. Got ' + options.app.CFBundleVersion); 544 | } 545 | if (typeof options.app.CFBundleShortVersionString !== 'string') { 546 | throw new Error('Expected options.app.CFBundleShortVersionString to be a string. Got ' + options.app.CFBundleShortVersionString); 547 | } 548 | if (typeof options.app.NSHumanReadableCopyright !== 'string') { 549 | throw new Error('Expected options.app.NSHumanReadableCopyright to be a string. Got ' + options.app.NSHumanReadableCopyright); 550 | } 551 | if (typeof options.app.NSLocalNetworkUsageDescription !== 'string') { 552 | throw new Error('Expected options.app.NSLocalNetworkUsageDescription to be a string. Got ' + options.app.NSLocalNetworkUsageDescription); 553 | } 554 | } else { 555 | if (typeof options.app.name !== 'string') { 556 | throw new Error('Expected options.app.name to be a string. Got ' + options.app.name); 557 | } 558 | if (typeof options.app.icon !== 'string') { 559 | throw new Error('Expected options.app.icon to be a string. Got ' + options.app.icon); 560 | } 561 | if (typeof options.app.version !== 'string') { 562 | throw new Error('Expected options.app.version to be a string. Got ' + options.app.version); 563 | } 564 | if (options.app.comments && typeof options.app.comments !== 'string') { 565 | throw new Error('Expected options.app.comments to be a string. Got ' + options.app.comments); 566 | } 567 | if (options.app.company && typeof options.app.company !== 'string') { 568 | throw new Error('Expected options.app.company to be a string. Got ' + options.app.company); 569 | } 570 | if (options.app.fileDescription && typeof options.app.fileDescription !== 'string') { 571 | throw new Error('Expected options.app.fileDescription to be a string. Got ' + options.app.fileDescription); 572 | } 573 | if (options.app.fileVersion && typeof options.app.fileVersion !== 'string') { 574 | throw new Error('Expected options.app.fileVersion to be a string. Got ' + options.app.fileVersion); 575 | } 576 | if (options.app.internalName && typeof options.app.internalName !== 'string') { 577 | throw new Error('Expected options.app.internalName to be a string. Got ' + options.app.internalName); 578 | } 579 | if (options.app.legalCopyright && options.app.legalCopyright && typeof options.app.legalCopyright !== 'string') { 580 | throw new Error('Expected options.app.legalCopyright to be a string. Got ' + options.app.legalCopyright); 581 | } 582 | if (options.app.legalTrademark && typeof options.app.legalTrademark !== 'string') { 583 | throw new Error('Expected options.app.legalTrademark to be a string. Got ' + options.app.legalTrademark); 584 | } 585 | if (options.app.originalFilename && typeof options.app.originalFilename !== 'string') { 586 | throw new Error('Expected options.app.originalFilename to be a string. Got ' + options.app.originalFilename); 587 | } 588 | if (options.app.privateBuild && typeof options.app.privateBuild !== 'string') { 589 | throw new Error('Expected options.app.privateBuild to be a string. Got ' + options.app.privateBuild); 590 | } 591 | if (options.app.productName && typeof options.app.productName !== 'string') { 592 | throw new Error('Expected options.app.productName to be a string. Got ' + options.app.productName); 593 | } 594 | if (options.app.productVersion && typeof options.app.productVersion !== 'string') { 595 | throw new Error('Expected options.app.productVersion to be a string. Got ' + options.app.productVersion); 596 | } 597 | if (options.app.specialBuild && options.app.specialBuild && typeof options.app.specialBuild !== 'string') { 598 | throw new Error('Expected options.app.specialBuild to be a string. Got ' + options.app.specialBuild); 599 | } 600 | if (options.app.legalCopyright && typeof options.app.legalCopyright !== 'string') { 601 | throw new Error('Expected options.app.legalCopyright to be a string. Got ' + options.app.legalCopyright); 602 | } 603 | if (typeof options.app.languageCode !== 'number') { 604 | throw new Error('Expected options.app.languageCode to be a number. Got ' + options.app.languageCode); 605 | } 606 | } 607 | return undefined; 608 | }; 609 | 610 | /** 611 | * Get path to various NW specific file paths. 612 | * @async 613 | * @function 614 | * @param {"chromedriver"} type - NW specific file or directory 615 | * @param {object} options - nwbuild options 616 | * @returns {string} - Path to chromedriver 617 | * @throws {Error} 618 | */ 619 | async function getPath(type, options) { 620 | if (type === 'chromedriver') { 621 | return path.resolve( 622 | options.cacheDir, 623 | `nwjs${options.flavor === 'sdk' ? '-sdk' : ''}-v${options.version}-${options.platform 624 | }-${options.arch}`, 625 | `chromedriver${options.platform === 'win' ? '.exe' : ''}`, 626 | ); 627 | } else { 628 | throw new Error('Invalid type. Expected `chromedriver` but got ', type); 629 | } 630 | } 631 | 632 | /** 633 | * Check if file exists at specified path. 634 | * @param {string} filePath - File path to check existence of 635 | * @returns {Promise} `true` if exists, otherwise `false` 636 | */ 637 | async function fileExists(filePath) { 638 | let exists = true; 639 | try { 640 | await fs.promises.stat(filePath); 641 | } catch { 642 | exists = false; 643 | } 644 | return exists; 645 | } 646 | 647 | /** 648 | * Custom logging function 649 | * @param {'debug' | 'info' | 'warn' | 'error'} severity - severity of message 650 | * @param {'debug' | 'info' | 'warn' | 'error'} logLevel - log level requested by user 651 | * @param {string} message - contents of message 652 | * @throws {Error} - throw error on invalid input 653 | * @returns {string} - stdout 654 | */ 655 | function log(severity, logLevel, message) { 656 | if (!['debug', 'info', 'warn', 'error'].includes(severity)) { 657 | throw new Error(`Expected debug, info, warn or error message severity. Got ${severity}`); 658 | } 659 | if (!['debug', 'info', 'warn', 'error'].includes(logLevel)) { 660 | throw new Error(`Expected debug, info, warn or error user defined log level. Got ${logLevel}`); 661 | } 662 | 663 | const sev = { 664 | 'debug': 4, 665 | 'info': 3, 666 | 'warn': 2, 667 | 'error': 1, 668 | }; 669 | let stdout = ''; 670 | const messageSeverity = sev[severity]; 671 | const userDefSeverity = sev[logLevel]; 672 | 673 | if (messageSeverity <= userDefSeverity) { 674 | stdout = `[ ${severity.toUpperCase()} ] ${message}`; 675 | } 676 | 677 | console.log(stdout); 678 | 679 | return stdout; 680 | } 681 | 682 | export default { fileExists, getReleaseInfo, getPath, PLATFORM_KV, ARCH_KV, EXE_NAME, globFiles, getNodeManifest, parse, validate, log }; 683 | -------------------------------------------------------------------------------- /src/util/osx.arm.versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "latest": "v0.75.0", 3 | "stable": "v0.75.0", 4 | "lts": "v0.75.0", 5 | "versions": [ 6 | { 7 | "version": "v0.75.0", 8 | "date": "2023/04/15", 9 | "files": ["osx-arm64"], 10 | "flavors": ["normal", "sdk"], 11 | "components": { 12 | "node": "19.7.0", 13 | "chromium": "112.0.5615.49" 14 | } 15 | }, 16 | { 17 | "version": "v0.70.0", 18 | "date": "2022/11/30", 19 | "files": ["osx-arm64"], 20 | "flavors": ["normal", "sdk"], 21 | "components": { 22 | "node": "19.0.0", 23 | "chromium": "107.0.5304.88" 24 | } 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tests/fixtures/app/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwutils/nw-builder/a7f57edc281d3fecd18ba81591a12bcf744f3d14/tests/fixtures/app/icon.icns -------------------------------------------------------------------------------- /tests/fixtures/app/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwutils/nw-builder/a7f57edc281d3fecd18ba81591a12bcf744f3d14/tests/fixtures/app/icon.ico -------------------------------------------------------------------------------- /tests/fixtures/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwutils/nw-builder/a7f57edc281d3fecd18ba81591a12bcf744f3d14/tests/fixtures/app/icon.png -------------------------------------------------------------------------------- /tests/fixtures/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | 9 | Hello, World! 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/fixtures/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Demo", 3 | "main": "index.html", 4 | "version": "0.0.0", 5 | "product_string": "Demo", 6 | "window": { 7 | "icon": "./icon.png" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/fixtures/demo.linux.js: -------------------------------------------------------------------------------- 1 | import nwbuild from '../../src/index.js'; 2 | 3 | await nwbuild({ 4 | mode: 'build', 5 | flavor: 'sdk', 6 | platform: 'linux', 7 | srcDir: './tests/fixtures/app', 8 | cacheDir: './node_modules/nw', 9 | outDir: './tests/fixtures/out/linux', 10 | glob: false, 11 | logLevel: 'debug', 12 | app: { 13 | name: 'Demo', 14 | genericName: 'Demo', 15 | noDisplay: false, 16 | comment: 'Tooltip information', 17 | /* File path of icon from where it is copied. */ 18 | icon: './tests/fixtures/app/icon.png', 19 | hidden: false, 20 | // TODO: test in different Linux desktop environments 21 | // onlyShowIn: [], 22 | // notShowIn: [], 23 | dBusActivatable: true, 24 | // TODO: test in Linux environment 25 | // tryExec: '/path/to/exe?' 26 | exec: './tests/fixtures/out/linux/Demo', 27 | } 28 | }); 29 | 30 | console.log('\nExecute `npm run demo:exe:linux` to run the application.'); 31 | -------------------------------------------------------------------------------- /tests/fixtures/demo.osx.js: -------------------------------------------------------------------------------- 1 | import nwbuild from '../../src/index.js'; 2 | 3 | await nwbuild({ 4 | mode: 'build', 5 | flavor: 'sdk', 6 | platform: 'osx', 7 | srcDir: './tests/fixtures/app', 8 | cacheDir: './node_modules/nw', 9 | outDir: './tests/fixtures/out/osx', 10 | glob: false, 11 | logLevel: 'debug', 12 | app: { 13 | name: 'Demo', 14 | /* File path of icon from where it is copied. */ 15 | icon: './tests/fixtures/app/icon.icns', 16 | LSApplicationCategoryType: 'public.app-category.utilities', 17 | CFBundleIdentifier: 'io.nwutils.demo', 18 | CFBundleName: 'Demo', 19 | CFBundleDisplayName: 'Demo', 20 | CFBundleSpokenName: 'Demo', 21 | CFBundleVersion: '0.0.0', 22 | CFBundleShortVersionString: '0.0.0', 23 | NSHumanReadableCopyright: 'Copyright (c) 2024 NW.js Utilities', 24 | NSLocalNetworkUsageDescription: 'Demo requires access to network to showcase its capabilities', 25 | } 26 | }); 27 | 28 | console.log('\nExecute `npm run demo:exe:osx` to run the application.'); 29 | -------------------------------------------------------------------------------- /tests/fixtures/demo.win.js: -------------------------------------------------------------------------------- 1 | import nwbuild from '../../src/index.js'; 2 | 3 | await nwbuild({ 4 | mode: 'build', 5 | flavor: 'sdk', 6 | platform: 'win', 7 | srcDir: './tests/fixtures/app', 8 | cacheDir: './node_modules/nw', 9 | outDir: './tests/fixtures/out/win', 10 | glob: false, 11 | logLevel: 'debug', 12 | app: { 13 | name: 'Demo', 14 | /* File path of icon from where it is copied. */ 15 | icon: './tests/fixtures/app/icon.ico', 16 | version: '0.0.0', 17 | comments: 'Diagnostic information', 18 | company: 'NW.js Utilities', 19 | fileDescription: 'This is a demo app to test nw-builder functionality', 20 | fileVersion: '0.0.0', 21 | internalName: 'Demo', 22 | legalCopyright: 'Copyright (c) 2024 NW.js Utilities', 23 | originalFilename: 'Demo', 24 | productName: 'Demo', 25 | productVersion: '0.0.0', 26 | } 27 | }); 28 | 29 | console.log('\nExecute `npm run demo:exe:win` to run the application.'); 30 | -------------------------------------------------------------------------------- /tests/specs/bld.test.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import process from 'node:process'; 3 | 4 | import { By } from 'selenium-webdriver'; 5 | import chrome from 'selenium-webdriver/chrome.js'; 6 | import { beforeAll, describe, expect, it } from 'vitest'; 7 | 8 | import build from '../../src/bld.js'; 9 | import get from '../../src/get/index.js'; 10 | import util from '../../src/util.js'; 11 | 12 | const { Driver, ServiceBuilder, Options } = chrome; 13 | 14 | describe.skip('bld test suite', async () => { 15 | let driver = undefined; 16 | 17 | const nwOptions = { 18 | srcDir: 'tests/fixtures/app', 19 | mode: 'build', 20 | version: '0.93.0', 21 | flavor: 'sdk', 22 | platform: util.PLATFORM_KV[process.platform], 23 | arch: util.ARCH_KV[process.arch], 24 | downloadUrl: 'https://dl.nwjs.io', 25 | manifestUrl: 'https://nwjs.io/versions', 26 | outDir: 'tests/fixtures/out/app', 27 | cacheDir: './node_modules/nw', 28 | cache: true, 29 | ffmpeg: false, 30 | glob: false, 31 | managedManifest: false, 32 | nativeAddon: false, 33 | zip: false 34 | }; 35 | 36 | beforeAll(async () => { 37 | await get(nwOptions); 38 | }, Infinity); 39 | 40 | it('builds without errors', async () => { 41 | await build(nwOptions); 42 | }); 43 | 44 | it('runs after build', async () => { 45 | const options = new Options(); 46 | const args = [ 47 | `--nwapp=${path.resolve('test', 'fixture', 'app')}`, 48 | '--headless=new', 49 | ]; 50 | options.addArguments(args); 51 | 52 | const chromedriverPath = util.getPath('chromedriver', nwOptions); 53 | 54 | const service = new ServiceBuilder(chromedriverPath).build(); 55 | 56 | driver = Driver.createSession(options, service); 57 | const text = await driver.findElement(By.id('test')).getText(); 58 | expect(text).toBe('Hello, World!'); 59 | }, { timeout: Infinity }); 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /tests/specs/decompress.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | import process from 'node:process'; 4 | 5 | import * as nw from 'nw'; 6 | import { afterAll, beforeAll, describe, expect, it } from 'vitest'; 7 | 8 | import decompress from '../../src/get/decompress.js'; 9 | 10 | import util from '../../src/util.js'; 11 | 12 | describe('get/decompress', async function () { 13 | 14 | let nwFilePath = ''; 15 | let nwDirPath = ''; 16 | let nwOutPath = './tests/fixtures/cache'; 17 | 18 | afterAll(async function () { 19 | await fs.promises.rm(nwOutPath, { recursive: true, force: true }); 20 | }); 21 | 22 | beforeAll(async function () { 23 | nwDirPath = await nw.findpath('all', { flavor: 'sdk' }); 24 | 25 | const cacheExists = await util.fileExists(nwOutPath); 26 | if (!cacheExists) { 27 | await fs.promises.mkdir(nwOutPath); 28 | } 29 | 30 | if (process.platform === 'linux') { 31 | nwFilePath = nwDirPath + '.tar.gz'; 32 | } else { 33 | nwFilePath = nwDirPath + '.zip'; 34 | } 35 | }); 36 | 37 | it('decompresses a NW.js binary', async function () { 38 | await decompress(nwFilePath, nwOutPath); 39 | }, Infinity); 40 | 41 | it.runIf(process.platform === 'darwin')('preserves symlinks on macos', async function () { 42 | const frameworksPath = path.resolve(process.cwd(), nwOutPath, nwDirPath, 'nwjs.app', 'Contents', 'Frameworks', 'nwjs Framework.framework'); 43 | const symlinks = [ 44 | path.join(frameworksPath, 'Helpers'), 45 | path.join(frameworksPath, 'Libraries'), 46 | path.join(frameworksPath, 'nwjs Framework'), 47 | path.join(frameworksPath, 'Resources'), 48 | path.join(frameworksPath, 'Versions', 'Current'), 49 | ]; 50 | 51 | for (const symlink of symlinks) { 52 | const stats = await fs.promises.lstat(symlink); 53 | expect(stats.isSymbolicLink()).toEqual(true); 54 | } 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/specs/ffmpeg.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import process from 'node:process'; 3 | 4 | import { afterEach, describe, expect, it } from 'vitest'; 5 | 6 | import util from '../../src/util.js'; 7 | 8 | import ffmpeg from '../../src/get/ffmpeg.js'; 9 | 10 | describe('get/ffmpeg', function () { 11 | 12 | let ffmpegFile = ''; 13 | 14 | afterEach(function () { 15 | fs.promises.rm(ffmpegFile, { recursive: true, force: true }); 16 | }); 17 | 18 | it('downloades community prebuild FFmpeg for specifc platform', async function () { 19 | ffmpegFile = await ffmpeg( 20 | 'https://github.com/nwjs-ffmpeg-prebuilt/nwjs-ffmpeg-prebuilt/releases/download', 21 | '0.83.0', 22 | util.PLATFORM_KV[process.platform], 23 | util.ARCH_KV[process.arch], 24 | './tests/fixtures' 25 | ); 26 | expect(util.fileExists(ffmpegFile)).resolves.toBe(true); 27 | }, Infinity); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/specs/node.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | 3 | import { afterEach, describe, expect, it } from 'vitest'; 4 | 5 | import util from '../../src/util.js'; 6 | 7 | import node from '../../src/get/node.js'; 8 | 9 | describe('get/node', function () { 10 | 11 | let nodeFile = ''; 12 | 13 | afterEach(function () { 14 | fs.promises.rm(nodeFile, { recursive: true, force: true }); 15 | }); 16 | 17 | it('downloades Node headers', async function () { 18 | nodeFile = await node( 19 | 'https://dl.nwjs.io', 20 | '0.83.0', 21 | './tests/fixtures' 22 | ); 23 | expect(util.fileExists(nodeFile)).resolves.toBe(true); 24 | }, Infinity); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/specs/nw.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import process from 'node:process'; 3 | 4 | import { afterEach, describe, expect, it } from 'vitest'; 5 | 6 | import util from '../../src/util.js'; 7 | 8 | import nw from '../../src/get/nw.js'; 9 | 10 | describe('get/nw', function () { 11 | 12 | let nwFile = ''; 13 | 14 | afterEach(function () { 15 | fs.promises.rm(nwFile, { recursive: true, force: true }); 16 | }); 17 | 18 | it('downloades a NW.js Linux tarball or Windows/MacOS zip', async function () { 19 | nwFile = await nw( 20 | 'https://dl.nwjs.io', 21 | '0.83.0', 22 | 'sdk', 23 | util.PLATFORM_KV[process.platform], 24 | util.ARCH_KV[process.arch], 25 | './tests/fixtures' 26 | ); 27 | expect(util.fileExists(nwFile)).resolves.toBe(true); 28 | }, Infinity); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/specs/osx.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import path from 'node:path'; 3 | import process from 'node:process'; 4 | 5 | import * as nw from 'nw'; 6 | import plist from 'plist'; 7 | import { afterAll, beforeAll, describe, expect, it } from 'vitest'; 8 | 9 | import setOsxConfig from '../../src/bld/osx.js'; 10 | import util from '../../src/util.js'; 11 | 12 | import nodeManifest from '../../package.json'; 13 | 14 | describe.runIf(process.platform === 'darwin')('bld/setOsxConfig', async function () { 15 | 16 | const outDir = './tests/fixtures/macos'; 17 | const appPath = path.join(outDir, 'Demo.app'); 18 | const releaseInfo = await util.getReleaseInfo( 19 | nodeManifest.devDependencies.nw.split('^')[1], 20 | util.PLATFORM_KV['darwin'], 21 | util.ARCH_KV['arm64'], 22 | './node_modules/nw', 23 | 'https://nwjs.io/versions', 24 | ); 25 | const chromiumVersion = releaseInfo.components.chromium; 26 | const helperAlertsPath = path.join(appPath, 27 | 'Contents', 28 | 'Frameworks', 29 | 'nwjs Framework.framework', 30 | 'Versions', 31 | chromiumVersion, 32 | 'Helpers', 33 | 'Demo Helper (Alerts).app'); 34 | const helperGPUPath = path.join(appPath, 35 | 'Contents', 36 | 'Frameworks', 37 | 'nwjs Framework.framework', 38 | 'Versions', 39 | chromiumVersion, 40 | 'Helpers', 41 | 'Demo Helper (GPU).app'); 42 | const helperPluginPath = path.join(appPath, 43 | 'Contents', 44 | 'Frameworks', 45 | 'nwjs Framework.framework', 46 | 'Versions', 47 | chromiumVersion, 48 | 'Helpers', 49 | 'Demo Helper (Plugin).app'); 50 | const helperRendererPath = path.join(appPath, 51 | 'Contents', 52 | 'Frameworks', 53 | 'nwjs Framework.framework', 54 | 'Versions', 55 | chromiumVersion, 56 | 'Helpers', 57 | 'Demo Helper (Renderer).app'); 58 | const helperPath = path.join(appPath, 59 | 'Contents', 60 | 'Frameworks', 61 | 'nwjs Framework.framework', 62 | 'Versions', 63 | chromiumVersion, 64 | 'Helpers', 65 | 'Demo Helper.app'); 66 | 67 | beforeAll(async function () { 68 | /* Copy the cached NW.js into a specific `outDir`. */ 69 | const nwDir = await nw.findpath('all', { flavor: 'sdk' }); 70 | await fs.promises.cp(nwDir, outDir, { recursive: true, force: true }); 71 | 72 | /* Rename relevant bundles' plists and executables. */ 73 | await setOsxConfig({ 74 | app: { 75 | name: 'Demo', 76 | LSApplicationCategoryType: 'public.app-category.utilities', 77 | CFBundleIdentifier: 'io.nwutils.demo', 78 | CFBundleName: 'Demo', 79 | CFBundleDisplayName: 'Demo', 80 | CFBundleSpokenName: 'Demo', 81 | CFBundleVersion: '0.0.0', 82 | CFBundleShortVersionString: '0.0.0', 83 | NSHumanReadableCopyright: 'Copyright (c) 2024 NW.js Utilities', 84 | NSLocalNetworkUsageDescription: 'This test application needs to access the local network for testing purposes.' 85 | }, 86 | outDir: outDir, 87 | releaseInfo: { 88 | components: { 89 | chromium: chromiumVersion, 90 | } 91 | }, 92 | }); 93 | }); 94 | 95 | it('renames the .app files correctly', async function () { 96 | const appPathExists = await util.fileExists(appPath); 97 | expect(appPathExists).toEqual(true); 98 | 99 | const helperAlertsPathExists = await util.fileExists(helperAlertsPath); 100 | expect(helperAlertsPathExists).toEqual(true); 101 | 102 | const helperGPUPathExists = await util.fileExists(helperGPUPath); 103 | expect(helperGPUPathExists).toEqual(true); 104 | 105 | const helperPluginPathExists = await util.fileExists(helperPluginPath); 106 | expect(helperPluginPathExists).toEqual(true); 107 | 108 | const helperRendererPathExists = await util.fileExists(helperRendererPath); 109 | expect(helperRendererPathExists).toEqual(true); 110 | 111 | const helperPathExists = await util.fileExists(helperPath); 112 | expect(helperPathExists).toEqual(true); 113 | }); 114 | 115 | it('renames the executables correctly', async function () { 116 | const appExePath = path.join(appPath, 'Contents', 'MacOS', 'Demo'); 117 | const appExePathExists = await util.fileExists(appExePath); 118 | expect(appExePathExists).toEqual(true); 119 | 120 | const helperAlertsExePath = path.join(helperAlertsPath, 'Contents', 'MacOS', 'Demo Helper (Alerts)'); 121 | const helperAlertsExePathExists = await util.fileExists(helperAlertsExePath); 122 | expect(helperAlertsExePathExists).toEqual(true); 123 | 124 | const helperGPUExePath = path.join(helperGPUPath, 'Contents', 'MacOS', 'Demo Helper (GPU)'); 125 | const helperGPUExePathExists = await util.fileExists(helperGPUExePath); 126 | expect(helperGPUExePathExists).toEqual(true); 127 | 128 | const helperPluginExePath = path.join(helperPluginPath, 'Contents', 'MacOS', 'Demo Helper (Plugin)'); 129 | const helperPluginExePathExists = await util.fileExists(helperPluginExePath); 130 | expect(helperPluginExePathExists).toEqual(true); 131 | 132 | const helperRendererExePath = path.join(helperRendererPath, 'Contents', 'MacOS', 'Demo Helper (Renderer)'); 133 | const helperRendererExePathExists = await util.fileExists(helperRendererExePath); 134 | expect(helperRendererExePathExists).toEqual(true); 135 | 136 | const helperExePath = path.join(helperPath, 'Contents', 'MacOS', 'Demo Helper'); 137 | const helperExePathExists = await util.fileExists(helperExePath); 138 | expect(helperExePathExists).toEqual(true); 139 | }); 140 | 141 | it('', async function () { 142 | const ContentsInfoPlistPath = path.resolve( 143 | appPath, 144 | 'Contents', 145 | 'Info.plist' 146 | ); 147 | const ContentsInfoPlistJson = plist.parse( 148 | await fs.promises.readFile( 149 | ContentsInfoPlistPath, 150 | 'utf-8' 151 | ) 152 | ); 153 | expect(ContentsInfoPlistJson.LSApplicationCategoryType).toEqual('public.app-category.utilities'); 154 | expect(ContentsInfoPlistJson.CFBundleIdentifier).toEqual('io.nwutils.demo'); 155 | expect(ContentsInfoPlistJson.CFBundleName).toEqual('Demo'); 156 | expect(ContentsInfoPlistJson.CFBundleDisplayName).toEqual('Demo'); 157 | expect(ContentsInfoPlistJson.CFBundleSpokenName).toEqual('Demo'); 158 | expect(ContentsInfoPlistJson.CFBundleVersion).toEqual('0.0.0'); 159 | expect(ContentsInfoPlistJson.CFBundleShortVersionString).toEqual('0.0.0'); 160 | expect(ContentsInfoPlistJson.CFBundleExecutable).toEqual('Demo'); 161 | expect(ContentsInfoPlistJson.NSLocalNetworkUsageDescription).toEqual('This test application needs to access the local network for testing purposes.'); 162 | 163 | const HelperAlertsAppJson = plist.parse( 164 | await fs.promises.readFile( 165 | path.resolve( 166 | helperAlertsPath, 167 | 'Contents', 168 | 'Info.plist' 169 | ), 170 | 'utf-8' 171 | ) 172 | ); 173 | 174 | expect(HelperAlertsAppJson.CFBundleDisplayName).toEqual('Demo Helper (Alerts)'); 175 | expect(HelperAlertsAppJson.CFBundleName).toEqual('Demo Helper (Alerts)'); 176 | expect(HelperAlertsAppJson.CFBundleIdentifier).toEqual('io.nwutils.demo.helper.alert'); 177 | expect(HelperAlertsAppJson.CFBundleExecutable).toEqual('Demo Helper (Alerts)'); 178 | 179 | const HelperGpuAppJson = plist.parse( 180 | await fs.promises.readFile( 181 | path.resolve( 182 | helperGPUPath, 183 | 'Contents', 184 | 'Info.plist' 185 | ), 186 | 'utf-8' 187 | ) 188 | ); 189 | 190 | expect(HelperGpuAppJson.CFBundleDisplayName).toEqual('Demo Helper (GPU)'); 191 | expect(HelperGpuAppJson.CFBundleName).toEqual('Demo Helper (GPU)'); 192 | expect(HelperGpuAppJson.CFBundleIdentifier).toEqual('io.nwutils.demo.helper.gpu'); 193 | expect(HelperGpuAppJson.CFBundleExecutable).toEqual('Demo Helper (GPU)'); 194 | 195 | const HelperPluginAppJson = plist.parse( 196 | await fs.promises.readFile( 197 | path.resolve( 198 | helperPluginPath, 199 | 'Contents', 200 | 'Info.plist' 201 | ), 202 | 'utf-8' 203 | ) 204 | ); 205 | 206 | expect(HelperPluginAppJson.CFBundleDisplayName).toEqual('Demo Helper (Plugin)'); 207 | expect(HelperPluginAppJson.CFBundleName).toEqual('Demo Helper (Plugin)'); 208 | expect(HelperPluginAppJson.CFBundleIdentifier).toEqual('io.nwutils.demo.helper.plugin'); 209 | expect(HelperPluginAppJson.CFBundleExecutable).toEqual('Demo Helper (Plugin)'); 210 | 211 | const HelperRendererAppJson = plist.parse( 212 | await fs.promises.readFile( 213 | path.resolve( 214 | helperRendererPath, 215 | 'Contents', 216 | 'Info.plist' 217 | ), 218 | 'utf-8' 219 | ) 220 | ); 221 | 222 | expect(HelperRendererAppJson.CFBundleDisplayName).toEqual('Demo Helper (Renderer)'); 223 | expect(HelperRendererAppJson.CFBundleName).toEqual('Demo Helper (Renderer)'); 224 | expect(HelperRendererAppJson.CFBundleIdentifier).toEqual('io.nwutils.demo.helper.renderer'); 225 | expect(HelperRendererAppJson.CFBundleExecutable).toEqual('Demo Helper (Renderer)'); 226 | 227 | const HelperAppJson = plist.parse( 228 | await fs.promises.readFile( 229 | path.resolve( 230 | helperPath, 231 | 'Contents', 232 | 'Info.plist' 233 | ), 234 | 'utf-8' 235 | ) 236 | ); 237 | 238 | expect(HelperAppJson.CFBundleDisplayName).toEqual('Demo Helper'); 239 | expect(HelperAppJson.CFBundleName).toEqual('Demo Helper'); 240 | expect(HelperAppJson.CFBundleIdentifier).toEqual('io.nwutils.demo.helper'); 241 | expect(HelperAppJson.CFBundleExecutable).toEqual('Demo Helper'); 242 | 243 | afterAll(async function () { 244 | await fs.promises.rm('./tests/fixtures/macos', { recursive: true, force: true }); 245 | }); 246 | }); 247 | }); 248 | -------------------------------------------------------------------------------- /tests/specs/request.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import util from '../../src/util.js'; 4 | 5 | import request from '../../src/get/request.js'; 6 | 7 | describe.skip('get/request', function () { 8 | 9 | let url = 'https://raw.githubusercontent.com/nwutils/nw-builder/main/src/util/osx.arm.versions.json'; 10 | const filePath = './tests/fixtures/cache/request.test.json'; 11 | 12 | it('downloads from specific url', async function () { 13 | await request(url, filePath); 14 | expect(util.fileExists(filePath)).resolves.toBe(true); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/specs/run.test.js: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | 3 | import { beforeAll, describe, expect, it } from 'vitest'; 4 | 5 | import get from '../../src/get/index.js'; 6 | import run from '../../src/run.js'; 7 | import util from '../../src/util.js'; 8 | 9 | describe('run test suite', async () => { 10 | 11 | const nwOptions = { 12 | srcDir: 'tests/fixtures/app', 13 | mode: 'build', 14 | version: '0.93.0', 15 | flavor: 'sdk', 16 | platform: util.PLATFORM_KV[process.platform], 17 | arch: util.ARCH_KV[process.arch], 18 | downloadUrl: 'https://dl.nwjs.io', 19 | manifestUrl: 'https://nwjs.io/versions', 20 | outDir: 'tests/fixtures/out/app', 21 | cacheDir: './node_modules/nw', 22 | cache: true, 23 | ffmpeg: false, 24 | glob: false, 25 | managedManifest: false, 26 | nativeAddon: false, 27 | zip: false 28 | }; 29 | 30 | beforeAll(async () => { 31 | await get(nwOptions); 32 | }, Infinity); 33 | 34 | it.skipIf(process.platform === 'win32')('runs and is killed via code', async () => { 35 | const nwProcess = await run(nwOptions); 36 | if (nwProcess) { 37 | nwProcess.kill(); 38 | expect(nwProcess.killed).toEqual(true); 39 | } 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /tests/specs/util.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import util from '../../src/util.js'; 4 | 5 | describe('util/log', function () { 6 | 7 | it('shows only error message if log level is error', async function () { 8 | expect(util.log('error', 'error', 'Lorem ipsum')).toBe('[ ERROR ] Lorem ipsum'); 9 | }); 10 | 11 | it('shows only error message if log level is debug', async function () { 12 | expect(util.log('debug', 'error', 'Lorem ipsum')).toBe(''); 13 | }); 14 | 15 | it('throws error if message severity is invalid', async function () { 16 | expect(() => util.log('debuggy', 'error', 'Lorem ipsum')).toThrow(); 17 | }); 18 | 19 | it('throws error if user defined log level is invalid', async function () { 20 | expect(() => util.log('debug', 'errory', 'Lorem ipsum')).toThrow(); 21 | }); 22 | 23 | }); 24 | 25 | describe('util/validate', function () { 26 | 27 | it('throws error on invalid mode', async function () { 28 | await expect(util.validate({ mode: 'gety' }, {})).rejects.toThrow(Error); 29 | }); 30 | 31 | it('throws error if releases info is undefined', async function () { 32 | await expect(util.validate({ mode: 'get' }, undefined)).rejects.toThrow(Error); 33 | }); 34 | 35 | it('throws error on invalid flavor', async function () { 36 | await expect(util.validate({ mode: 'get', flavor: 'notsdk' }, { flavours: ['normal'] })).rejects.toThrow(Error); 37 | }); 38 | 39 | it('throws error on invalid platform', async function () { 40 | await expect(util.validate({ mode: 'get', flavor: 'normal', platform: 'linox' }, { flavours: ['normal'], files: ['linux-x64'] })).rejects.toThrow(Error); 41 | }); 42 | 43 | it('throws error on invalid architecture', async function () { 44 | await expect(util.validate({ mode: 'get', flavor: 'normal', platform: 'linux', arch: 'x64000' }, { flavors: ['normal'], files: ['linux-x64'] })).rejects.toThrow(Error); 45 | }); 46 | 47 | it('throws error on invalid download url', async function () { 48 | await expect(util.validate({ mode: 'get', flavor: 'normal', platform: 'linux', arch: 'x64', downloadUrl: null }, { flavors: ['normal'], files: ['linux-x64'] })).rejects.toThrow(Error); 49 | }); 50 | 51 | it('throws error on invalid manifest url', async function () { 52 | await expect(util.validate({ mode: 'get', flavor: 'normal', platform: 'linux', arch: 'x64', downloadUrl: 'file://path/to/fs', manifestUrl: null }, { flavors: ['normal'], files: ['linux-x64'] })).rejects.toThrow(Error); 53 | }); 54 | 55 | it('throws error on invalid cache directory', async function () { 56 | await expect(util.validate({ mode: 'get', flavor: 'normal', platform: 'linux', arch: 'x64', downloadUrl: 'file://path/to/fs', manifestUrl: 'http://path/to/manifest', cacheDir: null }, { flavors: ['normal'], files: ['linux-x64'] })).rejects.toThrow(Error); 57 | }); 58 | 59 | it('throws error on invalid cache flag', async function () { 60 | await expect(util.validate({ mode: 'get', flavor: 'normal', platform: 'linux', arch: 'x64', downloadUrl: 'file://path/to/fs', manifestUrl: 'http://path/to/manifest', cacheDir: './path/to/cache', cache: 'true' }, { flavors: ['normal'], files: ['linux-x64'] })).rejects.toThrow(Error); 61 | }); 62 | 63 | it('throws error on invalid ffmpeg flag', async function () { 64 | await expect(util.validate({ mode: 'get', flavor: 'normal', platform: 'linux', arch: 'x64', downloadUrl: 'file://path/to/fs', manifestUrl: 'http://path/to/manifest', cacheDir: './path/to/cache', cache: true, ffmpeg: 'true' }, { flavors: ['normal'], files: ['linux-x64'] })).rejects.toThrow(Error); 65 | }); 66 | 67 | }); -------------------------------------------------------------------------------- /tests/specs/verify.test.js: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | 3 | import verify from '../../src/get/verify.js'; 4 | 5 | import nodeManifest from '../../package.json'; 6 | 7 | describe('get/verify', function () { 8 | 9 | it('verify shasums', async function () { 10 | const status = await verify( 11 | `https://dl.nwjs.io/v${nodeManifest.devDependencies.nw.split('^')[1]}/SHASUMS256.txt`, 12 | `./node_modules/nw/shasum/${nodeManifest.devDependencies.nw.split('^')[1]}.txt`, 13 | './node_modules/nw' 14 | ); 15 | expect(status).toBe(true); 16 | }, Infinity); 17 | }); 18 | -------------------------------------------------------------------------------- /vitest.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | coverage: { 6 | provider: 'v8', 7 | reporter: ['json', 'json-summary'], 8 | reportOnFailure: true, 9 | } 10 | } 11 | }); 12 | --------------------------------------------------------------------------------