-
-
Notifications
You must be signed in to change notification settings - Fork 0
Reusable action for publish and test #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,54 @@ | ||||||||||||||||||||||||
| name: Publish to npm | ||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||
| workflow_call: | ||||||||||||||||||||||||
| secrets: | ||||||||||||||||||||||||
| NPM_PUBLISH: | ||||||||||||||||||||||||
| required: true | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||
| id-token: write | ||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||
| install: | ||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||
| environment: publish | ||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||
| id-token: write | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||
| - name: Checkout | ||||||||||||||||||||||||
| uses: actions/checkout@v6 | ||||||||||||||||||||||||
|
Comment on lines
+20
to
+22
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Obviously we need to use
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Absolutely! Nothing against that - in the contrary it will be way better. As this is not yet setup, I only included the base workflow |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - name: Setup Node.js | ||||||||||||||||||||||||
| uses: actions/setup-node@v6 | ||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||
| node-version: 24 | ||||||||||||||||||||||||
| registry-url: https://registry.npmjs.org | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - name: Version | ||||||||||||||||||||||||
| if: github.event_name == 'release' && github.event.action == 'created' | ||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||
| VERSION=${{ github.event.release.tag_name }} | ||||||||||||||||||||||||
| VERSION=${VERSION:1} | ||||||||||||||||||||||||
| CURRENT_VERSION=$(npm pkg get version | tr -d '"') | ||||||||||||||||||||||||
| if [ "$CURRENT_VERSION" != "$VERSION" ]; then | ||||||||||||||||||||||||
| npm version $VERSION --no-git-tag-version | ||||||||||||||||||||||||
| else | ||||||||||||||||||||||||
| echo "Version already set to $VERSION, skipping npm version command" | ||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - name: Wait for 2FA | ||||||||||||||||||||||||
| uses: step-security/wait-for-secrets@v1 | ||||||||||||||||||||||||
| id: wait-for-secrets | ||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||
| secrets: | | ||||||||||||||||||||||||
| OTP: | ||||||||||||||||||||||||
| name: 'OTP to publish package' | ||||||||||||||||||||||||
| description: 'NPM 2FA' | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - name: publish | ||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||
| export NODE_AUTH_TOKEN=${{ secrets.NPM_PUBLISH }} | ||||||||||||||||||||||||
| npm publish --otp ${{ steps.wait-for-secrets.outputs.OTP }} --access public | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,11 @@ | ||
| # ci-workflows | ||
| Repository containing shared set of reusable workflows for the Expressjs organization | ||
| # Shared GitHub Workflows | ||
|
|
||
| This repository contains reusable GitHub Actions workflows for the Express.js organization shared across multiple repositories. | ||
|
|
||
| The purpose of this repository is to centralize common automation logic, reduce duplication, and ensure consistent CI/CD practices. | ||
|
|
||
| Each workflow is designed to be generic and reusable. Detailed documentation for individual workflows is provided in dedicated files inside the `docs` folder. | ||
|
|
||
| ## Available workflows | ||
|
|
||
| - [Publish](./docs/publish.md) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| # Publish | ||
|
|
||
| This workflow publishes a Node.js package to the npm registry. | ||
| It is designed to be reused across repositories via `workflow_call`. | ||
|
|
||
| [workflow definition](../.github/workflows/publish.yml) | ||
|
|
||
| ## Purpose | ||
|
|
||
| The workflow provides a standardized and secure way to publish packages to npm, including: | ||
|
|
||
| - Node.js setup | ||
| - Optional version alignment with a GitHub release tag | ||
| - Support for npm 2FA using a one-time password (OTP) | ||
|
|
||
| ## Trigger | ||
|
|
||
| This workflow is triggered using `workflow_call` from another repository. | ||
|
|
||
| ## Required secrets | ||
|
|
||
| | Secret name | Description | Required | | ||
| |-----------------|--------------------------------------|----------| | ||
| | `NPM_PUBLISH` | npm token with publish permissions | Yes | | ||
|
|
||
| ## Permissions | ||
|
|
||
| The workflow requires the following permissions: | ||
|
|
||
| - `contents: read` – to read repository contents | ||
| - `id-token: write` – to support secure authentication flows | ||
|
|
||
| ## npm token requirements | ||
|
|
||
| For security reasons, the npm token used by this workflow must follow these rules: | ||
|
|
||
| - Use a **granular npm token** scoped only to the package(s) being published | ||
| - The token must have **publish-only permissions** | ||
| - The token should be **added shortly before each publish** | ||
| - The token must be **revoked immediately after the deployment completes** | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally this needs to be automated (high chances we forget as local logout) but the CLI requires the ID:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be great to not need a specific PAT token for that :) I am still hoping that we can have tokens that are deleted automatically after a short time or after a number of usage (1 would be perfect). For this workflows, I am not sure about the case to have a token to delete a token, and then delete the token used to delete the token 😅 |
||
|
|
||
| ## Environment protection | ||
|
|
||
| This workflow is executed in the `publish` environment. | ||
|
|
||
| Using a dedicated environment allows: | ||
|
|
||
| - Restricting access to sensitive secrets | ||
| - Enforcing manual approvals before publishing | ||
| - Applying environment-specific security rules | ||
|
|
||
| Secrets required for publishing must be scoped to this environment. | ||
|
|
||
| ## Environment secret setup | ||
|
|
||
| To configure the `publish` environment and its secrets in GitHub: | ||
|
|
||
| 1. Go to the repository **Settings** | ||
| 2. Navigate to **Environments** | ||
| 3. Click **New environment** | ||
| 4. Create an environment named `publish` | ||
| 5. Under **Environment secrets**, add: | ||
| - `NPM_PUBLISH` with the npm publish token | ||
| 6. Configure **Deployment branch restrictions** | ||
| 7. (Optional) Configure: | ||
| - Required reviewers | ||
| - Wait timers | ||
|
|
||
| Only workflows explicitly targeting `environment: publish` will be able to access these secrets. | ||
|
|
||
| ## Behavior | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are some repos (probably more in the future) that might not work with this approach as they require a build step before release like codemod: (https://github.com/expressjs/codemod/blob/35e5d273b5530b4a1e2352cc849612ee39d929b6/package.json#L29)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking of allowing the call of the action with a specific npm script to run for that. Would you have anything against ? Or do you see a specific way?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don’t need to worry about codemod, because it has a different setup for publishing (see https://github.com/expressjs/codemod/blob/main/.github/workflows/publish.yml and expressjs/codemod#100), which we’re going to start using soon. |
||
|
|
||
| 1. **Checkout repository** | ||
| - Retrieves the package source code. | ||
|
|
||
| 2. **Setup Node.js** | ||
| - Uses Node.js version `24` | ||
| - Configures the npm registry to `https://registry.npmjs.org` | ||
|
|
||
| 3. **Version synchronization (optional)** | ||
| - If triggered by a GitHub release creation: | ||
| - Extracts the version from the release tag (expects `vX.Y.Z`) | ||
| - Updates `package.json` if the version differs | ||
|
|
||
| 4. **Wait for npm 2FA** | ||
| - Pauses execution until a one-time password (OTP) is provided | ||
| - Required when npm 2FA is enabled for publishing | ||
|
|
||
| 5. **Publish** | ||
| - Publishes the package to npm using: | ||
| - The provided npm token | ||
| - The supplied OTP | ||
| - Public access | ||
|
|
||
| ## Example usage | ||
|
|
||
| ```yaml | ||
| name: Publish package to npm | ||
| on: | ||
| release: | ||
| types: [created] | ||
|
|
||
| permissions: | ||
| id-token: write | ||
| contents: read | ||
|
|
||
| jobs: | ||
| publish: | ||
| uses: expressjs/ci-workflows/.github/workflows/release.yml@main | ||
| secrets: | ||
| NPM_PUBLISH: ${{ secrets.NPM_PUBLISH }} | ||
| ``` | ||
|
|
||
| ## Security overview | ||
|
|
||
| ## npm token configuration and lifecycle management | ||
|
|
||
| Securing the npm publishing process is a critical aspect of modernizing Express’ supply-chain security. Publishing credentials represent a high-value attack surface, particularly when used in automated CI/CD environments. | ||
|
|
||
| To mitigate this risk, npm tokens used for publication must follow a strict lifecycle and configuration model. Granular tokens are scoped to a single package, enforce mandatory two-factor authentication, and are issued only for the shortest valid duration required to complete a release. Tokens are added immediately before publication and revoked as soon as the release process completes, ensuring no long-lived credentials persist beyond their intended use. | ||
|
|
||
| This approach significantly reduces the blast radius of potential CI compromises and aligns Express’ release process with modern supply-chain security best practices, while maintaining a practical and auditable workflow for maintainers. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitely will like to pin dependencies here as our tokens are used in this workflow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixing that in a couple minutes !