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
9 changes: 9 additions & 0 deletions .github/workflows/__shared-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,12 @@ jobs:
issues: read
security-events: write
secrets: inherit

test-workflow-release:
name: Test workflow "release"
needs: linter
uses: ./.github/workflows/__test-workflow-release.yml
permissions:
contents: read
packages: write
id-token: write
81 changes: 70 additions & 11 deletions .github/workflows/__test-action-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
package-tarball-artifact-id-npm: ${{ steps.package-tarball-artifact.outputs.package-tarball-artifact-id-npm }}
package-tarball-artifact-id-pnpm: ${{ steps.package-tarball-artifact.outputs.package-tarball-artifact-id-pnpm }}
package-tarball-artifact-id-pnpm-package-manager: ${{ steps.package-tarball-artifact.outputs.package-tarball-artifact-id-pnpm-package-manager }}
package-tarball-artifact-id-yarn: ${{ steps.package-tarball-artifact.outputs.package-tarball-artifact-id-yarn }}
strategy:
matrix:
include:
- working-directory: tests/npm
artifact-suffix: npm
install-command: npm install --force --legacy-peer-deps --no-audit --no-fund --loglevel=warn
- working-directory: tests/pnpm
artifact-suffix: pnpm
install-command: pnpm install
- working-directory: tests/pnpm-package-manager
artifact-suffix: pnpm-package-manager
install-command: pnpm install
- working-directory: tests/yarn
artifact-suffix: yarn
install-command: yarn add
steps:
- name: Arrange - Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand All @@ -35,7 +36,7 @@ jobs:
uses: ./actions/package
with:
working-directory: ${{ matrix.working-directory }}
artifact-name: package-tarball-${{ matrix.artifact-suffix }}
artifact-name: package-tarball-${{ matrix.artifact-suffix }}-${{ github.run_id }}-${{ github.run_attempt }}

- name: Assert - Check "package" outputs
env:
Expand All @@ -57,6 +58,42 @@ jobs:
exit 1
fi

- id: package-tarball-artifact
name: Arrange - Export package tarball artifact ID
env:
ARTIFACT_SUFFIX: ${{ matrix.artifact-suffix }}
PACKAGE_TARBALL_ARTIFACT_ID: ${{ steps.act-package.outputs.package-tarball-artifact-id }}
run: echo "package-tarball-artifact-id-${ARTIFACT_SUFFIX}=${PACKAGE_TARBALL_ARTIFACT_ID}" >> "$GITHUB_OUTPUT"

assert-package-tarball:
name: Assert package tarball (${{ matrix.working-directory }})
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
include:
- working-directory: tests/npm
artifact-suffix: npm
install-command: npm install --force --legacy-peer-deps --no-audit --no-fund --loglevel=warn
package-tarball-artifact-id: ${{ needs.test.outputs['package-tarball-artifact-id-npm'] }}
- working-directory: tests/pnpm
artifact-suffix: pnpm
install-command: pnpm install
package-tarball-artifact-id: ${{ needs.test.outputs['package-tarball-artifact-id-pnpm'] }}
- working-directory: tests/pnpm-package-manager
artifact-suffix: pnpm-package-manager
install-command: pnpm install
package-tarball-artifact-id: ${{ needs.test.outputs['package-tarball-artifact-id-pnpm-package-manager'] }}
- working-directory: tests/yarn
artifact-suffix: yarn
install-command: yarn add
package-tarball-artifact-id: ${{ needs.test.outputs['package-tarball-artifact-id-yarn'] }}
steps:
- name: Arrange - Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Arrange - Configure Node.js version
run: echo "lts/*" > .nvmrc
working-directory: ${{ matrix.working-directory }}
Expand All @@ -67,22 +104,44 @@ jobs:
with:
working-directory: ${{ matrix.working-directory }}

- name: Assert - Check package tarball artifact ID
env:
PACKAGE_TARBALL_ARTIFACT_ID: ${{ matrix.package-tarball-artifact-id }}
run: |
if [ -z "$PACKAGE_TARBALL_ARTIFACT_ID" ]; then
echo "package-tarball-artifact-id output is empty"
exit 1
fi

- name: Assert - Download package tarball artifact by ID
id: download-package-tarball
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ steps.act-package.outputs.package-tarball-artifact-id }}
artifact-ids: ${{ matrix.package-tarball-artifact-id }}
path: ${{ runner.temp }}
skip-decompress: true
github-token: ${{ github.token }}

- name: Assert - Check downloaded package tarball artifact
env:
TARBALL_PATH: ${{ steps.act-package.outputs.package-tarball-path }}
DOWNLOAD_PATH: ${{ steps.download-package-tarball.outputs.download-path }}
INSTALL_COMMAND: ${{ matrix.install-command }}
working-directory: ${{ runner.temp }}
run: |
tarball_name="$(basename "$TARBALL_PATH")"
tarball_path="$DOWNLOAD_PATH"

if [ -d "$tarball_path" ]; then
tarball_matches="$(find "$tarball_path" -maxdepth 1 -type f -name '*.tgz')"
tarball_count="$(printf '%s\n' "$tarball_matches" | sed '/^$/d' | wc -l)"

if [ "$tarball_count" -ne 1 ]; then
echo "Expected exactly one downloaded package tarball, found $tarball_count"
exit 1
fi

tarball_path="$tarball_matches"
fi

if [ ! -f "./$tarball_name" ]; then
if [ ! -f "$tarball_path" ]; then
echo "Downloaded package tarball artifact does not exist"
exit 1
fi
Expand All @@ -93,4 +152,4 @@ jobs:
fi

# Install the tarball to verify it's a valid npm package
$INSTALL_COMMAND "./$tarball_name"
$INSTALL_COMMAND "$tarball_path"
38 changes: 38 additions & 0 deletions .github/workflows/__test-workflow-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Internal - Tests for "release" workflow

on:
workflow_call:

permissions: {}

jobs:
package:
name: Arrange package tarball
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
package-tarball-artifact-id: ${{ steps.package.outputs.package-tarball-artifact-id }}
steps:
- name: Arrange - Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- id: package
name: Arrange - Create package tarball
uses: ./actions/package
with:
working-directory: tests/npm
artifact-name: release-package-tarball-${{ github.run_id }}-${{ github.run_attempt }}

release:
name: Act - Run "release" workflow
needs: package
permissions:
contents: read
id-token: write
packages: write
uses: ./.github/workflows/release.yml
with:
package-tarball-artifact-id: ${{ needs.package.outputs.package-tarball-artifact-id }}
dry-run: true
provenance: false
205 changes: 205 additions & 0 deletions .github/workflows/release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
<!-- header:start -->

# GitHub Reusable Workflow: Node.js Release

<div align="center">
<img src="https://opengraph.githubassets.com/0e54b99e7f052e2a353659bcb048b76224cb355f919e9772252049df8eec3976/hoverkraft-tech/ci-github-nodejs" width="60px" align="center" alt="Node.js Release" />
</div>

---

<!-- header:end -->

<!-- badges:start -->

[![Release](https://img.shields.io/github/v/release/hoverkraft-tech/ci-github-nodejs)](https://github.com/hoverkraft-tech/ci-github-nodejs/releases)
[![License](https://img.shields.io/github/license/hoverkraft-tech/ci-github-nodejs)](http://choosealicense.com/licenses/mit/)
[![Stars](https://img.shields.io/github/stars/hoverkraft-tech/ci-github-nodejs?style=social)](https://img.shields.io/github/stars/hoverkraft-tech/ci-github-nodejs?style=social)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/hoverkraft-tech/ci-github-nodejs/blob/main/CONTRIBUTING.md)

<!-- badges:end -->

<!-- overview:start -->

## Overview

Workflow to publish the exact Node.js package tarball produced and verified by
CI.

The workflow downloads a raw `.tgz` artifact by immutable artifact ID, verifies
that exactly one tarball is present, configures Node.js for the target registry,
and runs `npm publish` against that tarball.

### Permissions

- **`contents`**: `read`
- **`id-token`**: `write` (required for provenance)
- **`packages`**: `write`

<!-- overview:end -->

<!-- usage:start -->

## Usage

### Publish a CI Package Tarball

```yaml
name: Release

on:
push:
tags: ["*"]

permissions: {}

jobs:
ci:
uses: ./.github/workflows/__shared-ci.yml
permissions:
contents: read
id-token: write
packages: read
secrets: inherit

release:
needs: ci
uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/release.yml@main
permissions:
contents: read
packages: write
id-token: write
secrets:
registry-token: ${{ secrets.NPM_TOKEN }}
with:
package-tarball-artifact-id: ${{ needs.ci.outputs.package-tarball-artifact-id }}
```

<!-- usage:end -->

<!-- markdownlint-disable MD013 -->

<!-- inputs:start -->

## Inputs

### Workflow Call Inputs

| **Input** | **Description** | **Required** | **Type** | **Default** |
| --------------------------------- | ---------------------------------------------------------------------------------- | ------------ | ----------- | ---------------------------- |
| **`runs-on`** | JSON array of runner(s) to use. | **false** | **string** | `["ubuntu-latest"]` |
| | See <https://docs.github.com/en/actions/using-jobs/choosing-the-runner-for-a-job>. | | | |
| **`package-tarball-artifact-id`** | Artifact ID of the package tarball produced by CI. | **true** | **string** | - |
| **`registry-url`** | Registry URL used by npm publish. | **false** | **string** | `https://registry.npmjs.org` |
| **`access`** | Package access level passed to npm publish. | **false** | **string** | `public` |
| | Leave empty to use npm defaults. | | | |
| **`tag`** | npm distribution tag for the published package. | **false** | **string** | `latest` |
| | Common values: `latest`, `next`, `canary`. | | | |
| **`provenance`** | Whether to generate npm provenance for npmjs.org publishes. | **false** | **boolean** | `true` |
| **`dry-run`** | Whether to run npm publish without publishing the package. | **false** | **boolean** | `false` |

<!-- inputs:end -->

<!-- markdownlint-enable MD013 -->

<!-- secrets:start -->

## Secrets

| **Secret** | **Description** | **Required** |
| -------------------- | --------------------------------------------------------- | ------------ |
| **`registry-token`** | Authentication token for token-based registry publishing. | **false** |

<!-- secrets:end -->

<!-- examples:start -->

## Examples

### Publish Tested Tarball to npm

```yaml
name: Release

on:
push:
tags: ["*"]

permissions: {}

jobs:
ci:
uses: ./.github/workflows/__shared-ci.yml
secrets: inherit
permissions:
contents: read
id-token: write
packages: read

release:
needs: ci
uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/release.yml@main
permissions:
contents: read
packages: write
id-token: write
secrets:
registry-token: ${{ secrets.NPM_TOKEN }}
with:
package-tarball-artifact-id: ${{ needs.ci.outputs.package-tarball-artifact-id }}
```

### Dry Run

```yaml
name: Release dry run

on:
workflow_dispatch:
inputs:
package-tarball-artifact-id:
description: Package tarball artifact ID from a previous CI run
required: true
type: string

permissions: {}

jobs:
dry-run:
uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/release.yml@main
permissions:
contents: read
packages: write
id-token: write
with:
package-tarball-artifact-id: ${{ inputs.package-tarball-artifact-id }}
dry-run: true
provenance: false
```

<!-- examples:end -->

<!-- contributing:start -->

## Contributing

Contributions are welcome! Please see the [contributing guidelines](https://github.com/hoverkraft-tech/ci-github-nodejs/blob/main/CONTRIBUTING.md) for more details.

<!-- contributing:end -->

<!-- security:start -->
<!-- security:end -->

<!-- license:start -->

## License

This project is licensed under the MIT License.

SPDX-License-Identifier: MIT

Copyright © 2025 hoverkraft-tech

For more details, see the [license](http://choosealicense.com/licenses/mit/).

<!-- license:end -->
Loading
Loading