Skip to content

Commit 56b60f9

Browse files
Copilotneilime
andcommitted
feat(release): add support for build artifacts, tarballs, and access control
Co-authored-by: neilime <[email protected]>
1 parent c498ecc commit 56b60f9

File tree

2 files changed

+144
-29
lines changed

2 files changed

+144
-29
lines changed

.github/workflows/release.md

Lines changed: 74 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@
2525

2626
Workflow to release Node.js packages with support for:
2727

28-
- Generating documentation (optional)
2928
- Publishing to various registries (npm, GitHub Packages)
29+
- Publishing from build artifacts or source code
30+
- Publishing pre-built package tarballs
31+
- Generating documentation (optional)
3032
- Provenance attestation for npm packages
3133
- Distribution tags for versioning
34+
- Scoped package access control
3235

3336
### Permissions
3437

@@ -42,6 +45,8 @@ Workflow to release Node.js packages with support for:
4245

4346
## Usage
4447

48+
### Basic Release from Source
49+
4550
```yaml
4651
name: Release
4752

@@ -59,43 +64,77 @@ jobs:
5964
packages: write
6065
id-token: write
6166
secrets:
62-
# Authentication token for the registry.
63-
# For npm: Use an npm access token with publish permissions.
64-
# For GitHub Packages: Use `GITHUB_TOKEN` or a PAT with `packages:write` permission.
67+
registry-token: ${{ secrets.NPM_TOKEN }}
68+
```
69+
70+
### Release with Build Artifacts from CI
71+
72+
```yaml
73+
name: Release
74+
75+
on:
76+
push:
77+
tags: ["*"]
78+
79+
permissions: {}
80+
81+
jobs:
82+
ci:
83+
uses: ./.github/workflows/__shared-ci.yml
84+
permissions:
85+
contents: read
86+
id-token: write
87+
packages: read
88+
secrets: inherit
89+
90+
release:
91+
needs: ci
92+
uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/release.yml@main
93+
permissions:
94+
contents: read
95+
packages: write
96+
id-token: write
97+
secrets:
6598
registry-token: ${{ secrets.NPM_TOKEN }}
6699
with:
67-
# JSON array of runner(s) to use.
68-
# Default: `["ubuntu-latest"]`
69-
runs-on: '["ubuntu-latest"]'
100+
# Download build artifacts from CI job
101+
build-artifact-id: ${{ needs.ci.outputs.build-artifact-id }}
102+
access: public
103+
```
70104
71-
# Documentation generation parameters.
72-
# Set to empty string or `false` to disable.
73-
docs: ""
105+
### Release Pre-built Tarball
74106
75-
# Registry configuration.
76-
# Use `npm`, `github`, or a URL/JSON object.
77-
# Default: `npm`
78-
registry: npm
107+
```yaml
108+
name: Release
79109

80-
# Command to run for publishing.
81-
# Default: `publish`
82-
publish-command: publish
110+
on:
111+
push:
112+
tags: ["*"]
83113

84-
# npm distribution tag.
85-
# Default: `latest`
86-
tag: latest
114+
permissions: {}
87115

88-
# Whether to perform a dry run.
89-
# Default: `false`
90-
dry-run: false
116+
jobs:
117+
ci:
118+
uses: ./.github/workflows/__shared-ci.yml
119+
permissions:
120+
contents: read
121+
id-token: write
122+
secrets: inherit
91123

92-
# Whether to generate provenance attestation.
93-
# Default: `true`
124+
release:
125+
needs: ci
126+
uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/release.yml@main
127+
permissions:
128+
contents: read
129+
packages: write
130+
id-token: write
131+
secrets:
132+
registry-token: ${{ secrets.NPM_TOKEN }}
133+
with:
134+
build-artifact-id: ${{ needs.ci.outputs.package-tarball-artifact-id }}
135+
package-tarball: "*.tgz"
136+
access: public
94137
provenance: true
95-
96-
# Working directory where the package is located.
97-
# Default: `.`
98-
working-directory: .
99138
```
100139
101140
<!-- usage:end -->
@@ -112,6 +151,12 @@ jobs:
112151
| ----------------------- | ---------------------------------------------------------------------------------- | ------------ | ----------- | ------------------- |
113152
| **`runs-on`** | JSON array of runner(s) to use. | **false** | **string** | `["ubuntu-latest"]` |
114153
| | See <https://docs.github.com/en/actions/using-jobs/choosing-the-runner-for-a-job>. | | | |
154+
| **`build-artifact-id`** | Build artifact ID from CI to download before publishing. | **false** | **string** | - |
155+
| | Contains built package or tarball from a previous job. | | | |
156+
| **`package-tarball`** | Path/pattern to pre-built tarball to publish (e.g., `*.tgz`). | **false** | **string** | - |
157+
| | Use when publishing a specific tarball instead of from source. | | | |
158+
| **`access`** | Package access level: `public` or `restricted`. | **false** | **string** | - |
159+
| | Leave empty to use package.json default. | | | |
115160
| **`docs`** | Documentation generation parameters. | **false** | **string** | - |
116161
| | Set to empty string or `false` to disable. | | | |
117162
| | Set to `true` for default command (`docs`). | | | |

.github/workflows/release.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,31 @@ on:
1515
type: string
1616
default: '["ubuntu-latest"]'
1717
required: false
18+
build-artifact-id:
19+
description: |
20+
Build artifact ID from a previous CI job to download before publishing.
21+
This artifact typically contains the built package or tarball.
22+
If not provided, the workflow will publish from source.
23+
type: string
24+
required: false
25+
default: ""
26+
package-tarball:
27+
description: |
28+
Path to a pre-built package tarball to publish (e.g., `my-package-1.0.0.tgz`).
29+
Use this when publishing a specific tarball file instead of running npm publish from source.
30+
Supports glob patterns to match tarball files.
31+
type: string
32+
required: false
33+
default: ""
34+
access:
35+
description: |
36+
Package access level for npm publish.
37+
- `public` — Publicly accessible package
38+
- `restricted` — Scoped package with restricted access
39+
Leave empty to use package.json default.
40+
type: string
41+
required: false
42+
default: ""
1843
docs:
1944
description: |
2045
Documentation generation parameters.
@@ -253,6 +278,13 @@ jobs:
253278
working-directory: ${{ inputs.working-directory }}
254279
registry-url: ${{ needs.prepare.outputs.registry-url }}
255280

281+
- name: Download build artifacts
282+
if: inputs.build-artifact-id != ''
283+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
284+
with:
285+
artifact-ids: ${{ inputs.build-artifact-id }}
286+
path: /
287+
256288
- id: package-info
257289
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
258290
env:
@@ -345,6 +377,8 @@ jobs:
345377
env:
346378
NODE_AUTH_TOKEN: ${{ secrets.registry-token }}
347379
PUBLISH_COMMAND: ${{ inputs.publish-command }}
380+
PACKAGE_TARBALL: ${{ inputs.package-tarball }}
381+
ACCESS: ${{ inputs.access }}
348382
TAG: ${{ inputs.tag }}
349383
DRY_RUN: ${{ inputs.dry-run }}
350384
PROVENANCE: ${{ inputs.provenance }}
@@ -355,6 +389,7 @@ jobs:
355389
script: |
356390
const fs = require('node:fs');
357391
const path = require('node:path');
392+
const { glob } = require('glob');
358393
359394
let workingDirectory = process.env.WORKING_DIRECTORY || '.';
360395
if (!path.isAbsolute(workingDirectory)) {
@@ -366,15 +401,50 @@ jobs:
366401
}
367402
368403
const publishCommand = process.env.PUBLISH_COMMAND;
404+
const packageTarball = process.env.PACKAGE_TARBALL;
405+
const access = process.env.ACCESS;
369406
const tag = process.env.TAG;
370407
const dryRun = process.env.DRY_RUN === 'true';
371408
const provenance = process.env.PROVENANCE === 'true';
372409
const runScriptCommand = process.env.RUN_SCRIPT_COMMAND;
373410
const registryUrl = process.env.REGISTRY_URL;
374411
412+
// Determine what to publish
413+
let publishTarget = null;
414+
415+
if (packageTarball) {
416+
// Publishing a specific tarball file
417+
const tarballPattern = path.isAbsolute(packageTarball)
418+
? packageTarball
419+
: path.join(workingDirectory, packageTarball);
420+
421+
const matches = await glob(tarballPattern, { nodir: true });
422+
423+
if (matches.length === 0) {
424+
return core.setFailed(`No tarball found matching pattern: ${packageTarball}`);
425+
}
426+
427+
if (matches.length > 1) {
428+
core.warning(`Multiple tarballs found: ${matches.join(', ')}. Using first match.`);
429+
}
430+
431+
publishTarget = matches[0];
432+
core.info(`Publishing tarball: ${publishTarget}`);
433+
}
434+
375435
// Build publish arguments
376436
const args = [publishCommand];
377437
438+
// Add tarball target if specified
439+
if (publishTarget) {
440+
args.push(publishTarget);
441+
}
442+
443+
// Add access flag
444+
if (access) {
445+
args.push('--access', access);
446+
}
447+
378448
// Add tag
379449
if (tag) {
380450
args.push('--tag', tag);

0 commit comments

Comments
 (0)