Skip to content
Merged
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
179 changes: 179 additions & 0 deletions .github/workflows/release-npm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
name: Release NPM

# Runs after the main Release workflow (release.yml) publishes a GitHub
# Release with the per-target binary archives. Downloads those archives,
# assembles one npm platform package per target, publishes them, and finally
# publishes the root @pulseengine/rivet package that depends on them via
# optionalDependencies.
#
# Platform packages MUST be published before the root package so npm can
# resolve optionalDependencies on the first install after tag.

on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: "Release tag to publish (e.g. v0.4.0)"
required: true

permissions:
contents: read

jobs:
publish-platform-packages:
name: Publish platform package (${{ matrix.platform }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
platform: darwin-arm64
binary: rivet
archive_ext: tar.gz
- target: x86_64-apple-darwin
platform: darwin-x64
binary: rivet
archive_ext: tar.gz
- target: aarch64-unknown-linux-gnu
platform: linux-arm64
binary: rivet
archive_ext: tar.gz
- target: x86_64-unknown-linux-gnu
platform: linux-x64
binary: rivet
archive_ext: tar.gz
- target: x86_64-pc-windows-msvc
platform: win32-x64
binary: rivet.exe
archive_ext: zip
steps:
- uses: actions/checkout@v4

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

- name: Resolve version
id: version
env:
EVENT_TAG: ${{ github.event.release.tag_name }}
INPUT_TAG: ${{ github.event.inputs.version }}
run: |
TAG="${EVENT_TAG:-$INPUT_TAG}"
if [ -z "$TAG" ]; then
echo "No tag provided" >&2
exit 1
fi
VERSION="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Download release asset
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.version.outputs.tag }}
TARGET: ${{ matrix.target }}
PLATFORM: ${{ matrix.platform }}
BINARY: ${{ matrix.binary }}
ARCHIVE_EXT: ${{ matrix.archive_ext }}
run: |
ASSET="rivet-${TAG}-${TARGET}.${ARCHIVE_EXT}"
mkdir -p staging
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --pattern "$ASSET" --dir staging
ls -la staging
if [ "$ARCHIVE_EXT" = "zip" ]; then
unzip -q "staging/$ASSET" -d staging
else
tar -xzf "staging/$ASSET" -C staging
fi
cp "staging/$BINARY" "platform-packages/$PLATFORM/$BINARY"
if [ "$ARCHIVE_EXT" != "zip" ]; then
chmod +x "platform-packages/$PLATFORM/$BINARY"
fi

- name: Update package version
working-directory: platform-packages/${{ matrix.platform }}
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
jq --arg v "$VERSION" '.version = $v' package.json > package.json.tmp
mv package.json.tmp package.json
cat package.json

- name: Publish to npm
working-directory: platform-packages/${{ matrix.platform }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --access public

publish-root-package:
name: Publish root package (@pulseengine/rivet)
needs: publish-platform-packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

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

- name: Resolve version
id: version
env:
EVENT_TAG: ${{ github.event.release.tag_name }}
INPUT_TAG: ${{ github.event.inputs.version }}
run: |
TAG="${EVENT_TAG:-$INPUT_TAG}"
VERSION="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Update root package version and optionalDependencies
working-directory: npm
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
jq --arg v "$VERSION" '
.version = $v |
.optionalDependencies = {
"@pulseengine/rivet-darwin-arm64": $v,
"@pulseengine/rivet-darwin-x64": $v,
"@pulseengine/rivet-linux-arm64": $v,
"@pulseengine/rivet-linux-x64": $v,
"@pulseengine/rivet-win32-x64": $v
}
' package.json > package.json.tmp
mv package.json.tmp package.json
cat package.json

- name: Publish to npm
working-directory: npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --access public

- name: Summary
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
{
echo "## NPM packages published";
echo "";
echo "- \`@pulseengine/rivet@$VERSION\`";
echo "- \`@pulseengine/rivet-darwin-arm64@$VERSION\`";
echo "- \`@pulseengine/rivet-darwin-x64@$VERSION\`";
echo "- \`@pulseengine/rivet-linux-arm64@$VERSION\`";
echo "- \`@pulseengine/rivet-linux-x64@$VERSION\`";
echo "- \`@pulseengine/rivet-win32-x64@$VERSION\`";
echo "";
echo "### Usage";
echo '```bash';
echo "npx @pulseengine/rivet --version";
echo "claude mcp add rivet npx -y @pulseengine/rivet mcp";
echo '```';
} >> "$GITHUB_STEP_SUMMARY"
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ vscode-rivet/node_modules/
vscode-rivet/bin/rivet
vscode-rivet/out/
vscode-rivet/*.vsix

# npm distribution: binaries are populated by release-npm.yml at publish time,
# never committed. node_modules from local npm installs should also be ignored.
platform-packages/*/rivet
platform-packages/*/rivet.exe
npm/bin/
npm/node_modules/
platform-packages/*/node_modules/
4 changes: 4 additions & 0 deletions npm/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
bin/
*.tmp
.DS_Store
Thumbs.db
40 changes: 40 additions & 0 deletions npm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# @pulseengine/rivet

SDLC traceability, validation, and MCP server for safety-critical systems.

Rivet links requirements, features, architecture, decisions, and verification
evidence across ISO 26262, DO-178C, ASPICE, and STPA. This npm package bundles
the `rivet` CLI binary (shipped per-platform via `optionalDependencies`) so it
can be invoked from any Node.js environment — including as a Claude Code MCP
server.

## Install

```bash
# One-shot (no install) — preferred for CI and MCP registration
npx @pulseengine/rivet --version

# Global install
npm install -g @pulseengine/rivet
rivet --version
```

## Claude Code MCP server

```bash
claude mcp add rivet npx -y @pulseengine/rivet mcp
```

## Supported platforms

- `darwin-arm64`, `darwin-x64`
- `linux-arm64`, `linux-x64`
- `win32-x64`

Binaries are pre-built and published alongside each GitHub release at
<https://github.com/pulseengine/rivet/releases>.

## License

Apache-2.0. See the [repository](https://github.com/pulseengine/rivet) for
source, documentation, and issues.
101 changes: 101 additions & 0 deletions npm/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env node

// Platform detection and binary-path resolution for @pulseengine/rivet.
// Chooses the correct platform-specific optional dependency, or falls back
// to a locally downloaded binary under ./bin (populated by install.js when
// optionalDependencies fail to resolve — e.g., on an unsupported platform
// triplet or with --no-optional).

const os = require("os");
const path = require("path");

/**
* Map (process.platform, process.arch) to the matching @pulseengine platform
* package name. Throws for unsupported combinations so the caller can surface
* a clear error.
*/
function getPlatformPackageName() {
const platform = os.platform();
const arch = os.arch();

let platformName;
switch (platform) {
case "darwin":
platformName = "darwin";
break;
case "linux":
platformName = "linux";
break;
case "win32":
platformName = "win32";
break;
default:
throw new Error(`Unsupported platform: ${platform}`);
}

let archName;
switch (arch) {
case "x64":
archName = "x64";
break;
case "arm64":
archName = "arm64";
break;
default:
throw new Error(`Unsupported architecture: ${arch}`);
}

return `@pulseengine/rivet-${platformName}-${archName}`;
}

/**
* Resolve the absolute path to the rivet binary for the current platform.
* Prefers the optional-dependency platform package; falls back to ./bin
* (populated by install.js downloading from the GitHub Release).
*/
function getBinaryPath() {
const platform = os.platform();
const binaryName = platform === "win32" ? "rivet.exe" : "rivet";

try {
const platformPackage = getPlatformPackageName();
const platformPackagePath = require.resolve(`${platformPackage}/package.json`);
const platformPackageDir = path.dirname(platformPackagePath);
return path.join(platformPackageDir, binaryName);
} catch (_err) {
// Fallback: binary downloaded directly from GitHub release by install.js.
return path.join(__dirname, "bin", binaryName);
}
}

function getPlatformInfo() {
return {
platform: os.platform(),
arch: os.arch(),
platformPackage: getPlatformPackageName(),
binaryPath: getBinaryPath(),
binaryName: os.platform() === "win32" ? "rivet.exe" : "rivet",
};
}

module.exports = {
getPlatformPackageName,
getBinaryPath,
getPlatformInfo,
};

// When invoked directly, print platform info (useful for debugging installs).
if (require.main === module) {
try {
const info = getPlatformInfo();
console.log("Platform Information:");
console.log(` Platform: ${info.platform}`);
console.log(` Architecture: ${info.arch}`);
console.log(` Platform Package: ${info.platformPackage}`);
console.log(` Binary Name: ${info.binaryName}`);
console.log(` Binary Path: ${info.binaryPath}`);
} catch (err) {
console.error("Error:", err.message);
process.exit(1);
}
}
Loading
Loading