Skip to content

Commit 9651ff8

Browse files
committed
feat(node-gha,github-action): support Node.js GitHub Action releases
1 parent 25a1820 commit 9651ff8

12 files changed

Lines changed: 549 additions & 33 deletions

File tree

‎README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ A simple tool to automate version bumps, changelogs, and releases using [Convent
2727
| [`@simple-release/config`](packages/config#readme) | A simple-release config loader. | [![NPM version][config-npm]][config-npm-url] | [![Dependencies status][config-deps]][config-deps-url] |
2828
| [`@simple-release/npm`](packages/npm#readme) | A npm addon for simple-release. | [![NPM version][npm-npm]][npm-npm-url] | [![Dependencies status][npm-deps]][npm-deps-url] |
2929
| [`@simple-release/pnpm`](packages/pnpm#readme) | A pnpm addon for simple-release. | [![NPM version][pnpm-npm]][pnpm-npm-url] | [![Dependencies status][pnpm-deps]][pnpm-deps-url] |
30+
| [`@simple-release/node-gha`](packages/node-gha#readme) | A Node.js GitHub Actions addon for simple-release. | [![NPM version][node-gha-npm]][node-gha-npm-url] | [![Dependencies status][node-gha-deps]][node-gha-deps-url] |
3031
| [`@simple-release/github`](packages/github#readme) | A github release addon for simple-release. | [![NPM version][github-npm]][github-npm-url] | [![Dependencies status][github-deps]][github-deps-url] |
3132
| [`@simple-release/github-action`](packages/github-action#readme) | A simple-release api for github action. | [![NPM version][github-action-npm]][github-action-npm-url] | [![Dependencies status][github-action-deps]][github-action-deps-url] |
3233
| [`simple-github-release`](packages/simple-github-release#readme) | A simple tool to create GitHub releases. | [![NPM version][simple-github-release-npm]][simple-github-release-npm-url] | [![Dependencies status][simple-github-release-deps]][simple-github-release-deps-url] |
@@ -63,6 +64,14 @@ A simple tool to automate version bumps, changelogs, and releases using [Convent
6364
[pnpm-deps]: https://img.shields.io/librariesio/release/npm/@simple-release/pnpm
6465
[pnpm-deps-url]: https://libraries.io/npm/@simple-release%2Fpnpm
6566

67+
<!-- node-gha -->
68+
69+
[node-gha-npm]: https://img.shields.io/npm/v/@simple-release/node-gha.svg
70+
[node-gha-npm-url]: https://www.npmjs.com/package/@simple-release/node-gha
71+
72+
[node-gha-deps]: https://img.shields.io/librariesio/release/npm/@simple-release/node-gha
73+
[node-gha-deps-url]: https://libraries.io/npm/@simple-release%2Fnode-gha
74+
6675
<!-- github -->
6776

6877
[github-npm]: https://img.shields.io/npm/v/@simple-release/github.svg

‎packages/github-action/src/releaser.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ export class ReleaserGithubAction<P extends Project = Project> extends Releaser<
140140
.maintenanceBranch()
141141
.tag()
142142
.push()
143-
.release()
144143
.publish()
144+
.release()
145145
.run(check ? ifReleaseCommit : undefined)
146146
}
147147

‎packages/node-gha/README.md‎

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# @simple-release/node-gha
2+
3+
[![ESM-only package][package]][package-url]
4+
[![NPM version][npm]][npm-url]
5+
[![Node version][node]][node-url]
6+
[![Dependencies status][deps]][deps-url]
7+
[![Install size][size]][size-url]
8+
[![Build status][build]][build-url]
9+
[![Coverage status][coverage]][coverage-url]
10+
11+
[package]: https://img.shields.io/badge/package-ESM--only-ffe536.svg
12+
[package-url]: https://nodejs.org/api/esm.html
13+
14+
[npm]: https://img.shields.io/npm/v/@simple-release/node-gha.svg
15+
[npm-url]: https://www.npmjs.com/package/@simple-release/node-gha
16+
17+
[node]: https://img.shields.io/node/v/@simple-release/node-gha.svg
18+
[node-url]: https://nodejs.org
19+
20+
[deps]: https://img.shields.io/librariesio/release/npm/@simple-release/node-gha
21+
[deps-url]: https://libraries.io/npm/@simple-release%2Fnode-gha
22+
23+
[size]: https://packagephobia.com/badge?p=@simple-release/node-gha
24+
[size-url]: https://packagephobia.com/result?p=@simple-release/node-gha
25+
26+
[build]: https://img.shields.io/github/actions/workflow/status/TrigenSoftware/simple-release/tests.yml?branch=main
27+
[build-url]: https://github.com/TrigenSoftware/simple-release/actions
28+
29+
[coverage]: https://coveralls.io/repos/github/TrigenSoftware/simple-release/badge.svg?branch=main
30+
[coverage-url]: https://coveralls.io/github/TrigenSoftware/simple-release?branch=main
31+
32+
A Node.js GitHub Actions addon for simple-release.
33+
34+
## Install
35+
36+
```bash
37+
# pnpm
38+
pnpm add @simple-release/node-gha
39+
# yarn
40+
yarn add @simple-release/node-gha
41+
# npm
42+
npm i @simple-release/node-gha
43+
```
44+
45+
## Usage
46+
47+
```js
48+
import { Releaser } from '@simple-release/core'
49+
import { NodeGhaProject } from '@simple-release/node-gha'
50+
51+
await new Releaser({
52+
project: new NodeGhaProject()
53+
})
54+
.publish({
55+
build: 'pnpm install --prod',
56+
files: [
57+
'node_modules',
58+
'dist'
59+
]
60+
})
61+
.run()
62+
```
63+
64+
`NodeGhaProject` publishes a built Node.js GitHub Action state by creating or updating:
65+
66+
- `latest` branch from the current release commit.
67+
- `v{major}` branch from the built `latest` state.
68+
- `v{version}` tag from the built `latest` state.
69+
70+
If `build` is provided, the command runs on the `latest` branch. After the command completes, files from the publish option `files` are force-added and the release commit is amended before refs are pushed. If `files` is not provided, package.json `files` is used.
71+
72+
## Options
73+
74+
### NodeGhaProject
75+
76+
#### `path`
77+
78+
Path to the `package.json` manifest file. Defaults to `'package.json'`.
79+
80+
#### `changelogFile`
81+
82+
Path to the changelog file. Defaults to `'CHANGELOG.md'`.
83+
84+
#### `compose`
85+
86+
Function to compose the main manifest with secondaries. It can be needed if you have some secondary manifests where version also should be updated. Optional.
87+
88+
### publish
89+
90+
Publish options for `NodeGhaProject`.
91+
92+
#### `skip`
93+
94+
If true, skip publishing. Optional.
95+
96+
#### `latestBranch`
97+
98+
Branch that points to the latest built GitHub Action state. Defaults to `'latest'`.
99+
100+
#### `majorBranchPrefix`
101+
102+
Prefix for the major branch. Defaults to `'v'`.
103+
104+
#### `tagPrefix`
105+
106+
Prefix for version tags. Defaults to `'v'`.
107+
108+
#### `build`
109+
110+
Build or install command to run before publishing refs. Optional.
111+
112+
#### `files`
113+
114+
Files to stage after `build`. Defaults to package.json `files`.
115+
116+
#### `env`
117+
118+
Environment variables to set before running build commands. Defaults to `process.env`.

‎packages/node-gha/oxlint.config.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { defineConfig } from '@trigen/oxlint'
2+
import testConfig from '@trigen/oxlint-config/test'
3+
import tsTypeCheckedConfig from '@trigen/oxlint-config/typescript-type-checked'
4+
import rootConfig from '../../oxlint.config.ts'
5+
6+
export default defineConfig({
7+
extends: [
8+
rootConfig,
9+
tsTypeCheckedConfig,
10+
testConfig
11+
]
12+
})

‎packages/node-gha/package.json‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"name": "@simple-release/node-gha",
3+
"type": "module",
4+
"version": "2.3.0",
5+
"description": "A Node.js GitHub Actions addon for simple-release.",
6+
"author": {
7+
"name": "Dan Onoshko",
8+
"email": "danon0404@gmail.com",
9+
"url": "https://github.com/dangreen"
10+
},
11+
"license": "MIT",
12+
"homepage": "https://github.com/TrigenSoftware/simple-release/tree/main/packages/node-gha#readme",
13+
"funding": "https://ko-fi.com/dangreen",
14+
"repository": {
15+
"type": "git",
16+
"url": "https://github.com/TrigenSoftware/simple-release.git",
17+
"directory": "packages/node-gha"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/TrigenSoftware/simple-release/issues"
21+
},
22+
"keywords": [
23+
"simple-release",
24+
"github-actions",
25+
"release",
26+
"publish",
27+
"changelog"
28+
],
29+
"engines": {
30+
"node": ">=22"
31+
},
32+
"exports": "./src/index.ts",
33+
"publishConfig": {
34+
"exports": {
35+
"types": "./dist/index.d.ts",
36+
"import": "./dist/index.js"
37+
},
38+
"directory": "package",
39+
"linkDirectory": false
40+
},
41+
"files": [
42+
"dist"
43+
],
44+
"scripts": {
45+
"clear:package": "del ./package",
46+
"clear:dist": "del ./dist",
47+
"clear": "del ./package ./dist ./coverage",
48+
"prepublishOnly": "run build clear:package clean-publish",
49+
"postpublish": "pnpm clear:package",
50+
"build": "tsgo -p tsconfig.build.json",
51+
"lint": "oxlint",
52+
"test:unit": "vitest run --coverage",
53+
"test:types": "tsgo --noEmit",
54+
"test": "run -p lint test:unit test:types"
55+
},
56+
"dependencies": {
57+
"@simple-libs/child-process-utils": "^2.0.0",
58+
"@simple-release/core": "workspace:^",
59+
"semver": "^7.5.2"
60+
},
61+
"devDependencies": {
62+
"@types/semver": "^7.5.8",
63+
"test": "workspace:^"
64+
}
65+
}

‎packages/node-gha/src/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './project.js'
2+
export * from './publish.js'

‎packages/node-gha/src/project.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {
2+
type PackageJsonProjectOptions,
3+
type ProjectBumpOptions,
4+
PackageJsonProject
5+
} from '@simple-release/core'
6+
import {
7+
type PublishOptions,
8+
publish
9+
} from './publish.js'
10+
11+
export type NodeGhaProjectOptions = PackageJsonProjectOptions
12+
13+
export type NodeGhaProjectBumpOptions = ProjectBumpOptions
14+
15+
export type NodeGhaProjectPublishOptions = PublishOptions
16+
17+
/**
18+
* A Node.js GitHub Actions project that publishes version refs to git branches and tags.
19+
*/
20+
export class NodeGhaProject extends PackageJsonProject {
21+
override async publish(options: NodeGhaProjectPublishOptions = {}): Promise<void> {
22+
if (options.skip) {
23+
options.logger?.info('Skipping publish')
24+
return
25+
}
26+
27+
await publish(this, options)
28+
}
29+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { join } from 'path'
2+
import {
3+
describe,
4+
expect,
5+
it
6+
} from 'vitest'
7+
import {
8+
createDirectory,
9+
forkProject,
10+
packageJsonProject
11+
} from 'test'
12+
import { NodeGhaProject } from './project.js'
13+
14+
function refHash(output: string) {
15+
return output.split(/\s+/)[0]
16+
}
17+
18+
describe('node-gha', () => {
19+
describe('publish', () => {
20+
it('should publish GitHub Action refs from the built latest branch', async () => {
21+
const { cwd } = await forkProject('node-gha-publish', packageJsonProject({
22+
version: '2.5.0',
23+
files: [
24+
'dist'
25+
]
26+
}))
27+
const remote = await createDirectory('node-gha-publish-remote')
28+
const project = new NodeGhaProject({
29+
path: join(cwd, 'package.json')
30+
})
31+
32+
await project.gitClient.exec('-C', remote, 'init', '--bare')
33+
await project.gitClient.exec('remote', 'set-url', 'origin', remote)
34+
35+
await project.publish({
36+
build: 'node -e "const fs=require(\'fs\');fs.mkdirSync(\'dist\',{recursive:true});fs.writeFileSync(\'dist/index.js\',\'built\\n\')"'
37+
})
38+
39+
const latestRef = await project.gitClient.exec('ls-remote', '--heads', 'origin', 'latest')
40+
const majorRef = await project.gitClient.exec('ls-remote', '--heads', 'origin', 'v2')
41+
const tagRef = await project.gitClient.exec('ls-remote', '--tags', 'origin', 'v2.5.0')
42+
43+
expect(latestRef).toContain('refs/heads/latest')
44+
expect(majorRef).toContain('refs/heads/v2')
45+
expect(tagRef).toContain('refs/tags/v2.5.0')
46+
expect(refHash(latestRef)).toBe(refHash(majorRef))
47+
expect(refHash(latestRef)).toBe(refHash(tagRef))
48+
expect(await project.gitClient.exec('show', 'v2.5.0:dist/index.js')).toBe('built')
49+
expect(await project.gitClient.getCurrentBranch()).toBe('master')
50+
})
51+
52+
it('should use custom ref names and explicit files', async () => {
53+
const { cwd } = await forkProject('node-gha-publish-custom-refs', packageJsonProject({
54+
version: '3.0.0'
55+
}))
56+
const remote = await createDirectory('node-gha-publish-custom-refs-remote')
57+
const project = new NodeGhaProject({
58+
path: join(cwd, 'package.json')
59+
})
60+
61+
await project.gitClient.exec('-C', remote, 'init', '--bare')
62+
await project.gitClient.exec('remote', 'set-url', 'origin', remote)
63+
64+
await project.publish({
65+
latestBranch: 'stable',
66+
majorBranchPrefix: 'release-',
67+
tagPrefix: 'action-v',
68+
build: 'node -e "const fs=require(\'fs\');fs.mkdirSync(\'lib\',{recursive:true});fs.writeFileSync(\'lib/action.js\',\'custom\\n\')"',
69+
files: [
70+
'lib/action.js'
71+
]
72+
})
73+
74+
const latestRef = await project.gitClient.exec('ls-remote', '--heads', 'origin', 'stable')
75+
const majorRef = await project.gitClient.exec('ls-remote', '--heads', 'origin', 'release-3')
76+
const tagRef = await project.gitClient.exec('ls-remote', '--tags', 'origin', 'action-v3.0.0')
77+
78+
expect(latestRef).toContain('refs/heads/stable')
79+
expect(majorRef).toContain('refs/heads/release-3')
80+
expect(tagRef).toContain('refs/tags/action-v3.0.0')
81+
expect(refHash(latestRef)).toBe(refHash(majorRef))
82+
expect(refHash(latestRef)).toBe(refHash(tagRef))
83+
expect(await project.gitClient.exec('show', 'action-v3.0.0:lib/action.js')).toBe('custom')
84+
})
85+
})
86+
})

0 commit comments

Comments
 (0)