diff --git a/.github/workflows/CreateRelease.yml b/.github/workflows/CreateRelease.yml index c346da4..821bddb 100644 --- a/.github/workflows/CreateRelease.yml +++ b/.github/workflows/CreateRelease.yml @@ -30,6 +30,8 @@ jobs: environment: release runs-on: [self-hosted, Linux, X64, "1ES.Pool=hld-kvm-amd"] if: ${{ contains(github.ref, 'refs/heads/release/') }} + outputs: + version: ${{ steps.set-version.outputs.version }} steps: - uses: actions/checkout@v6 @@ -48,11 +50,13 @@ jobs: id: crates-io-auth - name: Set crate versions + id: set-version run: | git fetch --tags || true version=$(echo "${{ github.ref }}" | sed -E 's#refs/heads/release/v##') echo "Setting version to 'v$version'" echo "HYPERLIGHT_JS_VERSION=v$version" >> $GITHUB_ENV + echo "version=$version" >> $GITHUB_OUTPUT - name: Publish hyperlight-js run: | @@ -94,3 +98,13 @@ jobs: benchmarks_Linux_hyperv3.tar.gz env: GH_TOKEN: ${{ github.token }} + + publish-npm-packages: + needs: [publish-hyperlight-js-packages-and-create-release] + if: ${{ contains(github.ref, 'refs/heads/release/') }} + uses: ./.github/workflows/npm-publish.yml + with: + version: ${{ needs.publish-hyperlight-js-packages-and-create-release.outputs.version }} + dry-run: false + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..94523d0 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,199 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Publish npm packages + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 0.17.0)' + required: true + type: string + dry-run: + description: 'Dry run (skip actual publish)' + required: false + type: boolean + default: false + workflow_call: + inputs: + version: + description: 'Version to publish' + required: true + type: string + dry-run: + description: 'Dry run (skip actual publish)' + required: false + type: boolean + default: false + secrets: + NPM_TOKEN: + required: true + +permissions: + contents: read + id-token: write + +env: + WORKING_DIR: src/js-host-api + +jobs: + build: + strategy: + fail-fast: true + matrix: + include: + - target: x86_64-unknown-linux-gnu + os: [self-hosted, Linux, X64, "1ES.Pool=hld-kvm-amd"] + build_name: linux-x64-gnu + - target: x86_64-pc-windows-msvc + os: [self-hosted, Windows, X64, "1ES.Pool=hld-win2022-amd"] + build_name: win32-x64-msvc + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Hyperlight setup + uses: hyperlight-dev/ci-setup-workflow@v1.8.0 + with: + rust-toolchain: "1.89" + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'npm' + cache-dependency-path: 'src/js-host-api/package-lock.json' + + - name: Install dependencies + working-directory: ${{ env.WORKING_DIR }} + run: npm ci --ignore-scripts --omit=optional + + - name: Set package version + working-directory: ${{ env.WORKING_DIR }} + shell: bash + run: | + npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version + + - name: Build native module + working-directory: ${{ env.WORKING_DIR }} + run: npm run build + + - name: Upload artifact + uses: actions/upload-artifact@v8 + with: + name: bindings-${{ matrix.build_name }} + path: ${{ env.WORKING_DIR }}/*.node + if-no-files-found: error + + publish: + needs: build + runs-on: [self-hosted, Linux, X64, "1ES.Pool=hld-kvm-amd"] + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + registry-url: 'https://registry.npmjs.org' + cache: 'npm' + cache-dependency-path: 'src/js-host-api/package-lock.json' + + - name: Install dependencies + working-directory: ${{ env.WORKING_DIR }} + run: npm ci --ignore-scripts --omit=optional + + - name: Download Linux artifact + uses: actions/download-artifact@v8 + with: + name: bindings-linux-x64-gnu + path: ${{ env.WORKING_DIR }}/artifacts/linux-x64-gnu + + - name: Download Windows artifact + uses: actions/download-artifact@v8 + with: + name: bindings-win32-x64-msvc + path: ${{ env.WORKING_DIR }}/artifacts/win32-x64-msvc + + - name: List artifacts + run: ls -la ${{ env.WORKING_DIR }}/artifacts/*/ + + - name: Move artifacts to npm directories + working-directory: ${{ env.WORKING_DIR }} + run: | + # Rename artifacts to match napi-rs naming convention + mv artifacts/linux-x64-gnu/*.node npm/linux-x64-gnu/js-host-api.linux-x64-gnu.node + mv artifacts/win32-x64-msvc/*.node npm/win32-x64-msvc/js-host-api.win32-x64-msvc.node + ls -la npm/linux-x64-gnu/ + ls -la npm/win32-x64-msvc/ + + - name: Set package versions + working-directory: ${{ env.WORKING_DIR }} + run: | + # Update main package version + npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version + + # Update platform package versions + cd npm/linux-x64-gnu && npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version + cd ../win32-x64-msvc && npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version + + - name: Update optionalDependencies versions + working-directory: ${{ env.WORKING_DIR }} + run: | + # Update only @hyperlight platform package versions (not other optionalDeps) + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); + for (const dep of Object.keys(pkg.optionalDependencies || {})) { + if (dep.startsWith('@hyperlight/js-host-api-')) { + pkg.optionalDependencies[dep] = '${{ inputs.version }}'; + } + } + fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); + " + cat package.json + + - name: Generate JS bindings (index.js and index.d.ts) + working-directory: ${{ env.WORKING_DIR }} + run: | + # napi prepublish generates index.js and index.d.ts from the .node artifacts + npx napi prepublish -t npm --skip-gh-release + ls -la index.js index.d.ts + + - name: Publish Linux package + if: ${{ !inputs.dry-run }} + working-directory: ${{ env.WORKING_DIR }}/npm/linux-x64-gnu + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish Windows package + if: ${{ !inputs.dry-run }} + working-directory: ${{ env.WORKING_DIR }}/npm/win32-x64-msvc + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish main package + if: ${{ !inputs.dry-run }} + working-directory: ${{ env.WORKING_DIR }} + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Dry run - show what would be published + if: ${{ inputs.dry-run }} + working-directory: ${{ env.WORKING_DIR }} + run: | + echo "=== DRY RUN - Would publish the following packages ===" + echo "" + echo "--- @hyperlight/js-host-api-linux-x64-gnu ---" + npm pack ./npm/linux-x64-gnu --dry-run + echo "" + echo "--- @hyperlight/js-host-api-win32-x64-msvc ---" + npm pack ./npm/win32-x64-msvc --dry-run + echo "" + echo "--- @hyperlight/js-host-api ---" + npm pack --dry-run diff --git a/src/js-host-api/.npmignore b/src/js-host-api/.npmignore new file mode 100644 index 0000000..100d0db --- /dev/null +++ b/src/js-host-api/.npmignore @@ -0,0 +1,32 @@ +# npm publish ignores - override .gitignore for package publishing +# Include generated files that are gitignored but needed in the package +!index.js +!index.d.ts + +# Exclude development files +node_modules/ +target/ +Cargo.lock +*.tgz +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.DS_Store + +# Exclude package-lock from published package +package-lock.json + +# Exclude test and dev files +tests/ +examples/ +*.config.js +*.config.mjs +.prettierrc +TYPE_NAMING.md +build.rs +src/ +Cargo.toml +test-examples.sh + +# Exclude artifacts directory (only used during CI) +artifacts/ diff --git a/src/js-host-api/README.md b/src/js-host-api/README.md index d3c2079..0bedfcf 100644 --- a/src/js-host-api/README.md +++ b/src/js-host-api/README.md @@ -600,3 +600,44 @@ just test-js-host-api release just build-all just test-all release ``` + +## Publishing to npm + +The package is published to npmjs.com as `@hyperlight/js-host-api` with platform-specific binary packages. + +### Automated Release + +Publishing happens automatically when a release is created via the `CreateRelease` workflow on a `release/vX.Y.Z` branch. + +### Manual Publishing + +You can also trigger the npm publish workflow manually: + +1. Go to **Actions** → **Publish npm packages** +2. Click **Run workflow** +3. Enter the version (e.g., `0.17.0`) +4. Optionally enable **dry-run** to test without publishing + +### Setup Requirements + +The following secret must be configured in the repository: + +| Secret | Description | +|--------|-------------| +| `NPM_TOKEN` | npm access token with publish permissions for the `@hyperlight` scope | + +To create an npm token: +1. Log in to [npmjs.com](https://www.npmjs.com/) +2. Go to **Access Tokens** → **Generate New Token** +3. Select **Automation** token type (for CI/CD) +4. Add the token as a repository secret named `NPM_TOKEN` + +### Package Structure + +The npm release consists of three packages: + +| Package | Description | +|---------|-------------| +| `@hyperlight/js-host-api` | Main package (installs correct binary automatically) | +| `@hyperlight/js-host-api-linux-x64-gnu` | Linux x86_64 native binary | +| `@hyperlight/js-host-api-win32-x64-msvc` | Windows x86_64 native binary | diff --git a/src/js-host-api/npm/linux-x64-gnu/package.json b/src/js-host-api/npm/linux-x64-gnu/package.json new file mode 100644 index 0000000..0fb3b9f --- /dev/null +++ b/src/js-host-api/npm/linux-x64-gnu/package.json @@ -0,0 +1,27 @@ +{ + "name": "@hyperlight/js-host-api-linux-x64-gnu", + "version": "0.17.0", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "main": "js-host-api.linux-x64-gnu.node", + "files": [ + "js-host-api.linux-x64-gnu.node" + ], + "description": "Node.js API bindings for Hyperlight JS - Linux x64 gnu", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/hyperlight-dev/hyperlight-js.git" + }, + "homepage": "https://github.com/hyperlight-dev/hyperlight-js#readme", + "bugs": { + "url": "https://github.com/hyperlight-dev/hyperlight-js/issues" + }, + "engines": { + "node": ">= 18" + } +} \ No newline at end of file diff --git a/src/js-host-api/npm/win32-x64-msvc/package.json b/src/js-host-api/npm/win32-x64-msvc/package.json new file mode 100644 index 0000000..3c8d5c5 --- /dev/null +++ b/src/js-host-api/npm/win32-x64-msvc/package.json @@ -0,0 +1,27 @@ +{ + "name": "@hyperlight/js-host-api-win32-x64-msvc", + "version": "0.17.0", + "os": [ + "win32" + ], + "cpu": [ + "x64" + ], + "main": "js-host-api.win32-x64-msvc.node", + "files": [ + "js-host-api.win32-x64-msvc.node" + ], + "description": "Node.js API bindings for Hyperlight JS - Windows x64 msvc", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/hyperlight-dev/hyperlight-js.git" + }, + "homepage": "https://github.com/hyperlight-dev/hyperlight-js#readme", + "bugs": { + "url": "https://github.com/hyperlight-dev/hyperlight-js/issues" + }, + "engines": { + "node": ">= 18" + } +} \ No newline at end of file diff --git a/src/js-host-api/package-lock.json b/src/js-host-api/package-lock.json index e72385c..138c96b 100644 --- a/src/js-host-api/package-lock.json +++ b/src/js-host-api/package-lock.json @@ -17,6 +17,10 @@ }, "engines": { "node": ">= 18" + }, + "optionalDependencies": { + "@hyperlight/js-host-api-linux-x64-gnu": "0.17.0", + "@hyperlight/js-host-api-win32-x64-msvc": "0.17.0" } }, "node_modules/@emnapi/core": { @@ -675,6 +679,12 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@hyperlight/js-host-api-linux-x64-gnu": { + "optional": true + }, + "node_modules/@hyperlight/js-host-api-win32-x64-msvc": { + "optional": true + }, "node_modules/@inquirer/ansi": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.2.tgz", diff --git a/src/js-host-api/package.json b/src/js-host-api/package.json index a03c46a..7186457 100644 --- a/src/js-host-api/package.json +++ b/src/js-host-api/package.json @@ -4,10 +4,26 @@ "description": "Node.js API bindings for Hyperlight JS", "main": "lib.js", "types": "index.d.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/hyperlight-dev/hyperlight-js.git" + }, + "homepage": "https://github.com/hyperlight-dev/hyperlight-js#readme", + "bugs": { + "url": "https://github.com/hyperlight-dev/hyperlight-js/issues" + }, "napi": { - "binaryName": "js-host-api" + "binaryName": "js-host-api", + "targets": [ + "x86_64-unknown-linux-gnu", + "x86_64-pc-windows-msvc" + ] }, "license": "Apache-2.0", + "optionalDependencies": { + "@hyperlight/js-host-api-linux-x64-gnu": "0.17.0", + "@hyperlight/js-host-api-win32-x64-msvc": "0.17.0" + }, "devDependencies": { "@eslint/js": "^10.0.1", "@napi-rs/cli": "^3.5.1", @@ -31,4 +47,4 @@ "artifacts": "napi artifacts", "version": "napi version" } -} +} \ No newline at end of file