Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ jobs:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: aarch64-unknown-linux-gnu
os: ubuntu-24.04-arm
- target: x86_64-apple-darwin
os: macos-14-large
- target: aarch64-apple-darwin
Expand Down Expand Up @@ -168,3 +170,49 @@ jobs:
sha256sums.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

npm:
name: Publish to npm
needs: [prepare, build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.prepare.outputs.tag }}

- uses: actions/setup-node@v4
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"

- name: Download build artifacts
uses: actions/download-artifact@v4
with:
pattern: release-*
path: artifacts

- name: Extract binaries by target
run: |
# artifacts/release-<target>/aleo-devnode-<tag>-<target>.zip
# -> bins/<target>/aleo-devnode[.exe]
for dir in artifacts/release-*; do
target="${dir#artifacts/release-}"
mkdir -p "bins/$target"
unzip -o "$dir"/*.zip -d "bins/$target"
done

- name: Build npm packages
run: node scripts/build-npm.mjs --version "${{ needs.prepare.outputs.version }}" --artifacts bins --out dist-npm

- name: Publish
run: |
set -e
# Platform packages first, then the main launcher last, so main's
# exact-pinned optionalDependencies always resolve on install.
for pkg in dist-npm/*/; do
[ "$pkg" = "dist-npm/main/" ] && continue
npm publish "$pkg" --access public
done
npm publish dist-npm/main/ --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
34 changes: 34 additions & 0 deletions npm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# aleo-devnode

Prebuilt binary distribution of [`aleo-devnode`](https://github.com/ProvableHQ/aleo-devnode) — a local Aleo development node that skips proof verification so clients can broadcast transactions built with placeholder proofs.

## Install

```sh
npm install -g @provablehq/aleo-devnode
```

This installs a tiny launcher plus one platform-specific binary package (selected automatically by npm via `os`/`cpu`). Supported platforms:

| OS | Arch |
| ------- | ----- |
| macOS | arm64 |
| macOS | x64 |
| Linux | x64 |
| Linux | arm64 |
| Windows | x64 |

## Usage

```sh
aleo-devnode accounts
aleo-devnode start --private-key <DEV_PRIVATE_KEY>
```

All arguments are forwarded directly to the underlying binary. See the [project README](https://github.com/ProvableHQ/aleo-devnode) for full CLI documentation.

## How it works

The `@provablehq/aleo-devnode` package declares each platform binary as an `optionalDependency`. npm installs only the one whose `os`/`cpu` match the host. The `aleo-devnode` command is a thin Node shim that resolves the matching package and execs the real binary.

If you install with `--no-optional` (or `--omit=optional`), the binary won't be present; reinstall without that flag.
55 changes: 55 additions & 0 deletions npm/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env node
"use strict";

// Thin launcher for the aleo-devnode binary. The actual platform-specific
// binary ships in an optional dependency (e.g. @provablehq/aleo-devnode-darwin-arm64);
// npm installs only the one matching the host's os/cpu. This shim resolves that
// package and execs the binary, forwarding argv, stdio, and the exit code.

const { spawnSync } = require("node:child_process");

// suffix -> [process.platform, process.arch]
const PLATFORMS = {
"darwin-arm64": ["darwin", "arm64"],
"darwin-x64": ["darwin", "x64"],
"linux-x64": ["linux", "x64"],
"linux-arm64": ["linux", "arm64"],
"win32-x64": ["win32", "x64"],
};

function resolveBinary() {
const suffix = Object.keys(PLATFORMS).find(
(k) => PLATFORMS[k][0] === process.platform && PLATFORMS[k][1] === process.arch,
);
if (!suffix) {
return { error: `unsupported platform: ${process.platform}-${process.arch}` };
}
const pkg = `@provablehq/aleo-devnode-${suffix}`;
const binName = process.platform === "win32" ? "aleo-devnode.exe" : "aleo-devnode";
try {
return { path: require.resolve(`${pkg}/bin/${binName}`) };
} catch {
return {
error:
`the platform package "${pkg}" is not installed.\n` +
`It is normally installed automatically as an optional dependency; a\n` +
`"--no-optional" / "--omit=optional" install will skip it. Reinstall\n` +
`without that flag, or add the package directly:\n` +
` npm install ${pkg}`,
};
}
}

const resolved = resolveBinary();
if (resolved.error) {
console.error(`aleo-devnode: ${resolved.error}`);
process.exit(1);
}

const result = spawnSync(resolved.path, process.argv.slice(2), { stdio: "inherit" });
if (result.error) {
console.error(`aleo-devnode: failed to launch binary: ${result.error.message}`);
process.exit(1);
}
// Propagate the child's exit code; if it was killed by a signal, exit non-zero.
process.exit(typeof result.status === "number" ? result.status : 1);
114 changes: 114 additions & 0 deletions scripts/build-npm.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env node
// Assembles the npm packages for release into <out>/:
// - <out>/<suffix>/ one per platform, each carrying the compiled binary
// - <out>/main/ the @provablehq/aleo-devnode launcher package
//
// Inputs are the release binaries the GitHub Actions build matrix produced,
// laid out as <artifacts>/<rust-target>/aleo-devnode[.exe].
//
// Usage:
// node scripts/build-npm.mjs --version 0.2.0 --artifacts bins --out dist-npm
//
// The publish step (release.yml) publishes every platform package first, then
// main last, so main's exact-pinned optionalDependencies always resolve.

import { existsSync, mkdirSync, copyFileSync, writeFileSync, chmodSync, readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const SCOPE = "@provablehq";
const BASE = "aleo-devnode";
const LICENSE = "Apache-2.0";
const REPO = "https://github.com/ProvableHQ/aleo-devnode";

// Single source of truth for the target matrix. `target` must match the
// directory names produced by the release build matrix (the Rust triples).
const PLATFORMS = [
{ suffix: "darwin-arm64", target: "aarch64-apple-darwin", os: "darwin", cpu: "arm64" },
{ suffix: "darwin-x64", target: "x86_64-apple-darwin", os: "darwin", cpu: "x64" },
{ suffix: "linux-x64", target: "x86_64-unknown-linux-gnu", os: "linux", cpu: "x64" },
{ suffix: "linux-arm64", target: "aarch64-unknown-linux-gnu", os: "linux", cpu: "arm64" },
{ suffix: "win32-x64", target: "x86_64-pc-windows-msvc", os: "win32", cpu: "x64" },
];

function parseArgs(argv) {
const args = {};
for (let i = 0; i < argv.length; i += 2) {
const key = argv[i];
if (!key?.startsWith("--")) throw new Error(`unexpected argument: ${key}`);
args[key.slice(2)] = argv[i + 1];
}
return args;
}

const { version, artifacts, out } = parseArgs(process.argv.slice(2));
if (!version || !artifacts || !out) {
console.error("usage: build-npm.mjs --version <v> --artifacts <dir> --out <dir>");
process.exit(1);
}

const scriptDir = dirname(fileURLToPath(import.meta.url));
const shimSrc = resolve(scriptDir, "..", "npm", "run.js");
const readmeSrc = resolve(scriptDir, "..", "npm", "README.md");
const artifactsDir = resolve(artifacts);
const outDir = resolve(out);

const writeJson = (path, obj) => writeFileSync(path, JSON.stringify(obj, null, 2) + "\n");

// --- platform packages -----------------------------------------------------
for (const p of PLATFORMS) {
const binName = p.os === "win32" ? "aleo-devnode.exe" : "aleo-devnode";
const binSrc = join(artifactsDir, p.target, binName);
if (!existsSync(binSrc)) {
throw new Error(`missing binary for ${p.target}: expected ${binSrc}`);
}

const pkgDir = join(outDir, p.suffix);
const binDir = join(pkgDir, "bin");
mkdirSync(binDir, { recursive: true });

copyFileSync(binSrc, join(binDir, binName));
if (p.os !== "win32") chmodSync(join(binDir, binName), 0o755);

writeJson(join(pkgDir, "package.json"), {
name: `${SCOPE}/${BASE}-${p.suffix}`,
version,
description: `Prebuilt aleo-devnode binary for ${p.suffix}`,
license: LICENSE,
repository: { type: "git", url: `git+${REPO}.git` },
os: [p.os],
cpu: [p.cpu],
files: ["bin/"],
});
console.log(`built ${SCOPE}/${BASE}-${p.suffix}`);
}

// --- main launcher package -------------------------------------------------
const mainDir = join(outDir, "main");
mkdirSync(mainDir, { recursive: true });
copyFileSync(shimSrc, join(mainDir, "run.js"));
copyFileSync(readmeSrc, join(mainDir, "README.md"));

writeJson(join(mainDir, "package.json"), {
name: `${SCOPE}/${BASE}`,
version,
description: "Local Aleo development node (prebuilt binary distribution)",
license: LICENSE,
repository: { type: "git", url: `git+${REPO}.git` },
bin: { "aleo-devnode": "run.js" },
files: ["run.js", "README.md"],
engines: { node: ">=18" },
optionalDependencies: Object.fromEntries(
PLATFORMS.map((p) => [`${SCOPE}/${BASE}-${p.suffix}`, version]),
),
});
console.log(`built ${SCOPE}/${BASE} (main)`);

// Sanity check: the shim's platform table must match this script's matrix,
// otherwise a published platform package would never be resolved at runtime.
const shim = readFileSync(shimSrc, "utf8");
for (const p of PLATFORMS) {
if (!shim.includes(`"${p.suffix}"`)) {
throw new Error(`npm/run.js is missing platform "${p.suffix}" — matrix drift`);
}
}
Loading