├── packages └── cli │ ├── bin │ └── zig │ ├── install.js │ ├── uninstall.js │ ├── reinstall.js │ ├── zig.js │ ├── package.json │ ├── postinstall.js │ ├── index.js │ └── README.md ├── .gitignore ├── pnpm-workspace.yaml ├── .prettierrc ├── README.md ├── package.json ├── LICENSE └── pnpm-lock.yaml /packages/cli/bin/zig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bin/ 3 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "printWidth": 100, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-zig 2 | 3 | Use Zig in node.js projects 4 | 5 | ## Packages 6 | 7 | - [`@ziglang/cli`](packages/cli) 8 | 9 | ## License 10 | 11 | [MIT](LICENSE) 12 | -------------------------------------------------------------------------------- /packages/cli/install.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { install } from './index.js'; 4 | 5 | try { 6 | await install(); 7 | } catch (error) { 8 | console.error(error); 9 | process.exit(1); 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/uninstall.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { uninstall } from './index.js'; 4 | 5 | try { 6 | await uninstall(); 7 | } catch (error) { 8 | console.error(error); 9 | process.exit(1); 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/reinstall.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { install } from './index.js'; 4 | 5 | try { 6 | await install({ force: true }); 7 | } catch (error) { 8 | console.error(error); 9 | process.exit(1); 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/zig.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { run } from './index.js'; 4 | 5 | try { 6 | const args = process.argv.slice(2); 7 | await run(...args); 8 | } catch (error) { 9 | console.error(error); 10 | process.exit(1); 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-zig", 3 | "version": "0.0.0", 4 | "keywords": [ 5 | "zig", 6 | "cli" 7 | ], 8 | "license": "MIT", 9 | "bugs": { 10 | "url": "https://github.com/pluvial/node-zig/issues" 11 | }, 12 | "homepage": "https://github.com/pluvial/node-zig#readme", 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/pluvial/node-zig.git" 16 | }, 17 | "packageManager": "pnpm@9.11.0+sha1.4cd20e68438613738e8f2bc9aece61eaa6b3e649", 18 | "devDependencies": { 19 | "prettier": "^3.3.3" 20 | }, 21 | "scripts": { 22 | "lint": "prettier --check .", 23 | "format": "prettier --write ." 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 pluvial 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ziglang/cli", 3 | "version": "0.0.14", 4 | "description": "", 5 | "type": "module", 6 | "exports": "./index.js", 7 | "main": "index.js", 8 | "bin": { 9 | "zig": "bin/zig", 10 | "zig-cli": "zig.js", 11 | "zig-install": "install.js", 12 | "zig-postinstall": "postinstall.js", 13 | "zig-reinstall": "reinstall.js", 14 | "zig-uninstall": "uninstall.js" 15 | }, 16 | "files": [ 17 | "index.js", 18 | "install.js", 19 | "postinstall.js", 20 | "reinstall.js", 21 | "uninstall.js", 22 | "zig.js" 23 | ], 24 | "scripts": { 25 | "postinstall": "node postinstall.js", 26 | "preuninstall": "node uninstall.js", 27 | "zig": "node zig.js", 28 | "zig-install": "node install.js", 29 | "zig-postinstall": "node postinstall.js", 30 | "zig-reinstall": "node reinstall.js", 31 | "zig-uninstall": "node uninstall.js", 32 | "prepublish": "rm -rf bin && git checkout bin" 33 | }, 34 | "dependencies": { 35 | "zx": "^8.1.8" 36 | }, 37 | "keywords": [ 38 | "zig", 39 | "cli" 40 | ], 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/pluvial/node-zig/issues" 44 | }, 45 | "homepage": "https://github.com/pluvial/node-zig/tree/main/packages/cli#readme", 46 | "repository": { 47 | "type": "git", 48 | "url": "git+https://github.com/pluvial/node-zig.git", 49 | "directory": "packages/cli" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/cli/postinstall.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as fs from 'fs/promises'; 4 | import { $ } from 'zx'; 5 | import { binaryPath, install, uninstall, version } from './index.js'; 6 | 7 | try { 8 | try { 9 | const stats = await fs.lstat(binaryPath); 10 | // if the binary is a symlink, it should link to the system-wide zig 11 | if (stats.isSymbolicLink()) { 12 | console.log('Skipping zig installation, symlink to system zig exists'); 13 | process.exit(0); 14 | } 15 | // an empty ./bin/zig file is used as a placeholder for npm/pnpm/yarn to 16 | // create the bin symlink, so if the file already exists and has non-zero 17 | // size, and is not a symlink, then zig is already installed locally 18 | if (stats.size !== 0) { 19 | console.log('Skipping zig installation, binary exists'); 20 | process.exit(0); 21 | } 22 | } catch {} 23 | try { 24 | // remove the node_modules/.bin symlinks from the PATH before checking if a 25 | // zig is installed system-wide, to avoid hitting the symlink 26 | const pathDirs = process.env.PATH.split(':'); 27 | process.env.PATH = pathDirs.filter(dir => !dir.endsWith('node_modules/.bin')).join(':'); 28 | // check if there's already an installed zig binary 29 | const which = await $`which zig`; 30 | const zigVersion = await $`zig version`; 31 | const systemZig = which.stdout.trim(); 32 | const systemZigVersion = zigVersion.stdout.trim(); 33 | if (systemZig.length !== 0 && systemZigVersion.length !== 0) { 34 | console.log(`Skipping zig installation, ${systemZigVersion} already installed in system`); 35 | console.log(`Creating symlink to: ${systemZig}`); 36 | await $`ln -sf ${systemZig} ${binaryPath}`; 37 | console.log(`Manually run the zig-install script to install ${version} locally`); 38 | process.exit(0); 39 | } 40 | } catch (error) {} 41 | console.log('Did not detect zig in system, will be installed locally'); 42 | // use force to remove the empty placeholder and skip re-checking the file 43 | await install({ force: true }); 44 | } catch (error) { 45 | console.error(error); 46 | process.exit(1); 47 | } 48 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | .: 9 | devDependencies: 10 | prettier: 11 | specifier: ^3.3.3 12 | version: 3.3.3 13 | 14 | packages/cli: 15 | dependencies: 16 | zx: 17 | specifier: ^8.1.8 18 | version: 8.1.8 19 | 20 | packages: 21 | '@types/fs-extra@11.0.4': 22 | resolution: 23 | { 24 | integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==, 25 | } 26 | 27 | '@types/jsonfile@6.1.4': 28 | resolution: 29 | { 30 | integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==, 31 | } 32 | 33 | '@types/node@22.7.2': 34 | resolution: 35 | { 36 | integrity: sha512-866lXSrpGpgyHBZUa2m9YNWqHDjjM0aBTJlNtYaGEw4rqY/dcD7deRVTbBBAJelfA7oaGDbNftXF/TL/A6RgoA==, 37 | } 38 | 39 | prettier@3.3.3: 40 | resolution: 41 | { 42 | integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==, 43 | } 44 | engines: { node: '>=14' } 45 | hasBin: true 46 | 47 | undici-types@6.19.8: 48 | resolution: 49 | { 50 | integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==, 51 | } 52 | 53 | zx@8.1.8: 54 | resolution: 55 | { 56 | integrity: sha512-m8s48skYQ8EcRz9KXfc7rZCjqlZevOGiNxq5tNhDiGnhOvXKRGxVr+ajUma9B6zxMdHGSSbnjV/R/r7Ue2xd+A==, 57 | } 58 | engines: { node: '>= 12.17.0' } 59 | hasBin: true 60 | 61 | snapshots: 62 | '@types/fs-extra@11.0.4': 63 | dependencies: 64 | '@types/jsonfile': 6.1.4 65 | '@types/node': 22.7.2 66 | optional: true 67 | 68 | '@types/jsonfile@6.1.4': 69 | dependencies: 70 | '@types/node': 22.7.2 71 | optional: true 72 | 73 | '@types/node@22.7.2': 74 | dependencies: 75 | undici-types: 6.19.8 76 | optional: true 77 | 78 | prettier@3.3.3: {} 79 | 80 | undici-types@6.19.8: 81 | optional: true 82 | 83 | zx@8.1.8: 84 | optionalDependencies: 85 | '@types/fs-extra': 11.0.4 86 | '@types/node': 22.7.2 87 | -------------------------------------------------------------------------------- /packages/cli/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as fs from 'fs/promises'; 4 | import * as path from 'path'; 5 | import * as url from 'url'; 6 | import { $, ProcessOutput } from 'zx'; 7 | 8 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 9 | 10 | const arch = { 11 | x64: 'x86_64', 12 | arm64: 'aarch64', 13 | }[process.arch]; 14 | const platform = { 15 | darwin: 'macos', 16 | freebsd: 'freebsd', 17 | linux: 'linux', 18 | win32: 'windows', 19 | }[process.platform]; 20 | 21 | const windows = platform === 'windows'; 22 | 23 | export const installDirectory = path.join(__dirname, 'bin'); 24 | export const name = windows ? 'zig.exe' : 'zig'; 25 | export const binaryPath = path.join(installDirectory, name); 26 | 27 | const index = await fetch('https://ziglang.org/download/index.json').then(response => 28 | response.json(), 29 | ); 30 | const { master } = index; 31 | const { tarball } = master[`${arch}-${platform}`]; 32 | // TODO: use fixed stable version if requested by user 33 | // export const version = '0.13.0'; 34 | export const version = index.master.version; 35 | 36 | export async function install({ force = false } = {}) { 37 | if (!force) { 38 | try { 39 | // an empty ./bin/zig file is used as a placeholder for npm/pnpm/yarn to 40 | // create the bin symlink, so the file exists but will have a zero size in 41 | // the base case, check for it here 42 | const stats = await fs.lstat(binaryPath); 43 | if (stats.isSymbolicLink()) { 44 | console.log(`Replacing symlink with local installation: ${binaryPath}`); 45 | } else if (stats.size !== 0) { 46 | console.log(`${name} is already installed, did you mean to reinstall?`); 47 | return; 48 | } 49 | } catch {} 50 | } 51 | 52 | await uninstall(); 53 | await $`mkdir -p ${installDirectory}`; 54 | 55 | await $`curl -fsSL ${tarball} | tar xJ -C ${installDirectory} --strip-components=1`; 56 | } 57 | 58 | export async function run(...args) { 59 | try { 60 | await fs.access(binaryPath); 61 | } catch (err) { 62 | throw new Error(`You must install ${name} before you can run it`); 63 | } 64 | 65 | try { 66 | await $`${binaryPath} ${args}`; 67 | } catch (error) { 68 | if (error instanceof ProcessOutput && error.exitCode) { 69 | // console.error({ stderr: error.stderr, stdout: error.stdout }); 70 | process.exit(error.exitCode); 71 | } 72 | throw error; 73 | } 74 | } 75 | 76 | export async function uninstall() { 77 | await $`rm -rf ${installDirectory}`; 78 | } 79 | -------------------------------------------------------------------------------- /packages/cli/README.md: -------------------------------------------------------------------------------- 1 | # @ziglang/cli 2 | 3 | This package serves as a wrapper for the Zig compiler CLI itself. It checks if Zig is installed system-wide, and otherwise detects the current platform, fetches the corresponding Zig binary, and installs it locally in your `node_modules` folder. This will be the typical scenario when setting up in a CI environment where Zig is not available. 4 | 5 | If Zig is already installed, by default only a symlink to it will be created, no additional Zig package is installed. This will be the typical setup in development. If desired, the `zig-install` script can still be used to install a local version of Zig, regardless of any system-wide version. 6 | 7 | ### Installing 8 | 9 | ```sh 10 | npm i -D @ziglang/cli # or yarn or pnpm 11 | ``` 12 | 13 | ### Scripts 14 | 15 | At the end of the `npm install` process, the `postinstall.js` script should run automatically, and install/symlink Zig (and other scripts) locally into your `./node_modules/.bin` folder: 16 | 17 | ```sh 18 | ./node_modules/.bin/zig # the actual zig binary/symlink once installed 19 | ./node_modules/.bin/zig-install # installs zig locally 20 | ./node_modules/.bin/zig-reinstall # reinstalls zig locally 21 | ./node_modules/.bin/zig-uninstall # removes the local zig binary/symlink 22 | ``` 23 | 24 | The scripts can be manually executed if needed to fix any issue with the installation, either via `npx` (or `pnpx` or `yarn`), `npm exec`, or just referring to the script by name in your package.json `scripts`: 25 | 26 | ```sh 27 | # examples for zig-install 28 | ./node_modules/.bin/zig-install 29 | npx zig-install 30 | npm exec zig-install 31 | ``` 32 | 33 | ```json 34 | { 35 | "scripts": { 36 | "postinstall": "zig-install" 37 | } 38 | } 39 | ``` 40 | 41 | ## Notes and TODOs 42 | 43 | Priority: 44 | 45 | - Handle versions, ideally it should be possible to track upstream Zig releases as tags, `latest` for stable releases, `next` to track master builds for example. 46 | - Add native Windows support, it works on WSL2, Linux, and macOS. Should work well on most UNIX-like environments with `curl`, `tar`, and `xz`. Also tested deployment using Cloudflare Pages, Netlify, and Vercel (needed to manually `yum install xz` in the install command). 47 | 48 | Explore: 49 | 50 | - Provide a JS API for common zig CLI functionality (`build`, `build-lib`, `fmt`, `run`, `test`, `translate-c`, etc.) 51 | - Compile Zig's `std.zig` functions into Wasm (export C-compatible functions), provide a JS API for the raw compiler functionality (tokenizer, parser) 52 | 53 | ## Inspiration 54 | 55 | - [binary-install](https://github.com/EverlastingBugstopper/binary-install) 56 | - [wasm-pack npm module](https://github.com/rustwasm/wasm-pack/tree/master/npm) 57 | 58 | ## License 59 | 60 | [MIT](LICENSE) 61 | --------------------------------------------------------------------------------