Compare commits

...

42 Commits

Author SHA1 Message Date
Rui Chen
a06a81a03e release 2.5.0
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-12-01 01:02:34 -05:00
jj
7da8983734 feat: mark release as draft until all artifacts are uploaded (#692)
Previously, the releases were created and then artifacts (if any)
were added to them. This broke when GitHub released "immutable"
releases, which disallow changes after the release is published.

Make it so that releases are always marked as "draft" when being
worked on by the action, and unmarked as draft (if desired) once
the action is completed.

Fixes #653
2025-12-01 00:59:24 -05:00
dependabot[bot]
87973286a4 chore(deps): bump actions/checkout in the github-actions group (#689)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 5.0.0 to 5.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](08c6903cd8...93cb6efe18)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 00:56:27 -05:00
dependabot[bot]
1bfc62a71b chore(deps): bump the npm group across 1 directory with 5 updates (#697)
Bumps the npm group with 4 updates in the / directory: [glob](https://github.com/isaacs/node-glob), [mime-types](https://github.com/jshttp/mime-types), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8).


Updates `glob` from 11.0.3 to 13.0.0
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v11.0.3...v13.0.0)

Updates `mime-types` from 3.0.1 to 3.0.2
- [Release notes](https://github.com/jshttp/mime-types/releases)
- [Changelog](https://github.com/jshttp/mime-types/blob/master/HISTORY.md)
- [Commits](https://github.com/jshttp/mime-types/compare/v3.0.1...v3.0.2)

Updates `@types/node` from 20.19.24 to 20.19.25
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@vitest/coverage-v8` from 4.0.6 to 4.0.13
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.13/packages/coverage-v8)

Updates `vitest` from 4.0.6 to 4.0.13
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.13/packages/vitest)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: mime-types
  dependency-version: 3.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@types/node"
  dependency-version: 20.19.25
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 4.0.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: vitest
  dependency-version: 4.0.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 00:56:07 -05:00
Rui Chen
5be0e66d93 release 2.4.2
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-11-08 14:21:57 -05:00
Jens L.
af658b4d5d feat: Ensure generated release notes cannot be over 125000 characters (#684)
* Ensure generated release notes cannot be over 125000 characters

* simpler truncate, and always truncate even without generated
2025-11-08 14:20:00 -05:00
Rui Chen
237aaccf71 chore: bump node to 24.11.0
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-11-08 14:18:20 -05:00
dependabot[bot]
00362bea6f chore(deps): bump the npm group with 5 updates (#687)
Bumps the npm group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@octokit/plugin-retry](https://github.com/octokit/plugin-retry.js) | `8.0.2` | `8.0.3` |
| [@octokit/plugin-throttling](https://github.com/octokit/plugin-throttling.js) | `11.0.2` | `11.0.3` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `20.19.23` | `20.19.24` |
| [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `4.0.4` | `4.0.6` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.0.4` | `4.0.6` |


Updates `@octokit/plugin-retry` from 8.0.2 to 8.0.3
- [Release notes](https://github.com/octokit/plugin-retry.js/releases)
- [Commits](https://github.com/octokit/plugin-retry.js/compare/v8.0.2...v8.0.3)

Updates `@octokit/plugin-throttling` from 11.0.2 to 11.0.3
- [Release notes](https://github.com/octokit/plugin-throttling.js/releases)
- [Commits](https://github.com/octokit/plugin-throttling.js/compare/v11.0.2...v11.0.3)

Updates `@types/node` from 20.19.23 to 20.19.24
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@vitest/coverage-v8` from 4.0.4 to 4.0.6
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.6/packages/coverage-v8)

Updates `vitest` from 4.0.4 to 4.0.6
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.6/packages/vitest)

---
updated-dependencies:
- dependency-name: "@octokit/plugin-retry"
  dependency-version: 8.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@octokit/plugin-throttling"
  dependency-version: 11.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@types/node"
  dependency-version: 20.19.24
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 4.0.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: vitest
  dependency-version: 4.0.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-07 21:47:12 -05:00
dependabot[bot]
0adea5aa98 chore(deps): bump the npm group with 3 updates (#686)
Bumps the npm group with 3 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest).


Updates `@types/node` from 20.19.22 to 20.19.23
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@vitest/coverage-v8` from 3.2.4 to 4.0.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.4/packages/coverage-v8)

Updates `vitest` from 3.2.4 to 4.0.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.4/packages/vitest)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 20.19.23
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 4.0.4
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: vitest
  dependency-version: 4.0.4
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-30 14:36:32 -04:00
dependabot[bot]
aa05f9d779 chore(deps): bump actions/setup-node from 5.0.0 to 6.0.0 in the github-actions group (#683)
* chore(deps): bump actions/setup-node in the github-actions group

Bumps the github-actions group with 1 update: [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/setup-node` from 5.0.0 to 6.0.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](a0853c2454...2028fbc5c2)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>

* update comment

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Rui Chen <rui@chenrui.dev>
2025-10-23 22:23:58 -04:00
dependabot[bot]
bbaccb3a0c chore(deps): bump @types/node from 20.19.21 to 20.19.22 in the npm group (#682)
Bumps the npm group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@types/node` from 20.19.21 to 20.19.22
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 20.19.22
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 22:22:08 -04:00
dependabot[bot]
50fda3f773 chore(deps): bump vite from 7.1.5 to 7.1.11 (#681)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.5 to 7.1.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.11/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.11
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-23 22:21:57 -04:00
dependabot[bot]
5434409c2b chore(deps): bump @types/node from 20.19.19 to 20.19.21 in the npm group (#679)
Bumps the npm group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@types/node` from 20.19.19 to 20.19.21
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 20.19.21
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-16 18:49:13 -04:00
Rui Chen
6da8fa9354 release 2.4.1
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-10-11 11:40:52 -04:00
Copilot
f38efdea4c fix: gracefully fallback to body when body_path cannot be read (#671)
* Initial plan

* fix: gracefully fallback to body when body_path cannot be read

Co-authored-by: chenrui333 <1580956+chenrui333@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: chenrui333 <1580956+chenrui333@users.noreply.github.com>
2025-10-06 23:50:32 -04:00
Copilot
cec1a1113b fix(util): support brace expansion globs containing commas in parseInputFiles (#672)
* Initial plan

* fix(util): support brace expansion globs containing commas in parseInputFiles

Co-authored-by: chenrui333 <1580956+chenrui333@users.noreply.github.com>

* test(util): add comprehensive edge case coverage for brace expansion parsing

Co-authored-by: chenrui333 <1580956+chenrui333@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: chenrui333 <1580956+chenrui333@users.noreply.github.com>
2025-10-06 23:50:00 -04:00
Rui Chen
aec2ec56f9 release 2.4.0
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-10-06 22:53:32 -04:00
Stephen Way
4db716b167 feat: respect working_directory for files globs; add input and tests (#667) 2025-10-06 22:51:45 -04:00
dependabot[bot]
14820f2cee chore(deps): bump the npm group with 2 updates (#668)
Bumps the npm group with 2 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [typescript](https://github.com/microsoft/TypeScript).


Updates `@types/node` from 20.19.18 to 20.19.19
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `typescript` from 5.9.2 to 5.9.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.2...v5.9.3)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 20.19.19
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: typescript
  dependency-version: 5.9.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 22:48:36 -04:00
Rui Chen
62c96d0c4e release 2.3.4
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-10-03 14:45:26 -04:00
Stephen Way
7dc9b8ac0f fix(action): handle 422 already_exists race condition (#665)
- Add retry logic for 422 'already_exists' errors in race conditions
- Allow action to find and update existing releases instead of failing
- Add test to verify race condition handling works correctly
- Fixes regression that broke matrix workflows in v2.2.2+

closes #616
2025-10-03 14:34:31 -04:00
dependabot[bot]
0f0e0b98e9 chore(deps): bump the npm group with 3 updates (#666)
Bumps the npm group with 3 updates: [@octokit/plugin-retry](https://github.com/octokit/plugin-retry.js), [@octokit/plugin-throttling](https://github.com/octokit/plugin-throttling.js) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@octokit/plugin-retry` from 8.0.1 to 8.0.2
- [Release notes](https://github.com/octokit/plugin-retry.js/releases)
- [Commits](https://github.com/octokit/plugin-retry.js/compare/v8.0.1...v8.0.2)

Updates `@octokit/plugin-throttling` from 11.0.1 to 11.0.2
- [Release notes](https://github.com/octokit/plugin-throttling.js/releases)
- [Commits](https://github.com/octokit/plugin-throttling.js/compare/v11.0.1...v11.0.2)

Updates `@types/node` from 20.19.17 to 20.19.18
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@octokit/plugin-retry"
  dependency-version: 8.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@octokit/plugin-throttling"
  dependency-version: 11.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@types/node"
  dependency-version: 20.19.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-03 14:31:53 -04:00
dependabot[bot]
97d42c1b50 chore(deps): bump the npm group across 1 directory with 2 updates (#662)
Bumps the npm group with 2 updates in the / directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [@vercel/ncc](https://github.com/vercel/ncc).


Updates `@types/node` from 20.19.13 to 20.19.17
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@vercel/ncc` from 0.38.3 to 0.38.4
- [Release notes](https://github.com/vercel/ncc/releases)
- [Commits](https://github.com/vercel/ncc/compare/0.38.3...0.38.4)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 20.19.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@vercel/ncc"
  dependency-version: 0.38.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-18 23:26:39 -04:00
dependabot[bot]
19cd0bcd2b chore(deps): bump vite from 7.0.0 to 7.1.5 (#657)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.0.0 to 7.1.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-10 22:41:43 -04:00
dependabot[bot]
5d1b0b1164 chore(deps): bump @types/node from 20.19.11 to 20.19.13 in the npm group (#655)
Bumps the npm group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@types/node` from 20.19.11 to 20.19.13
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 20.19.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 23:36:06 -04:00
dependabot[bot]
f6021cf9a4 chore(deps): bump actions/setup-node in the github-actions group (#656)
Bumps the github-actions group with 1 update: [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/setup-node` from 4.4.0 to 5.0.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](49933ea528...a0853c2454)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 23:35:59 -04:00
Rui Chen
6cbd405e2c release 2.3.3
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-09-07 00:36:40 -04:00
Rui Chen
fbadcc90e8 update to use actions/checkout@v5
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-08-23 16:43:38 -04:00
dependabot[bot]
4a840061c4 chore(deps): bump @types/node from 20.19.10 to 20.19.11 in the npm group (#648)
Bumps the npm group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@types/node` from 20.19.10 to 20.19.11
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 20.19.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-23 16:37:50 -04:00
dependabot[bot]
7191749478 chore(deps): bump actions/checkout in the github-actions group (#649)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4.2.2 to 5.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](11bd71901b...08c6903cd8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-23 16:37:44 -04:00
dependabot[bot]
126b1e7093 chore(deps): bump @types/node from 20.19.9 to 20.19.10 in the npm group (#647)
Bumps the npm group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@types/node` from 20.19.9 to 20.19.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 20.19.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-17 22:22:51 -04:00
dependabot[bot]
f82d31e53e chore(deps): bump the npm group with 3 updates (#643)
Bumps the npm group with 3 updates: [@types/glob](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/glob), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [typescript](https://github.com/microsoft/TypeScript).


Updates `@types/glob` from 8.1.0 to 9.0.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/glob)

Updates `@types/node` from 20.19.7 to 20.19.9
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `typescript` from 5.8.3 to 5.9.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release-publish.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.8.3...v5.9.2)

---
updated-dependencies:
- dependency-name: "@types/glob"
  dependency-version: 9.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: "@types/node"
  dependency-version: 20.19.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: typescript
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-06 13:14:01 -04:00
dependabot[bot]
f2352b97da chore(deps): bump @types/node from 20.19.2 to 20.19.7 in the npm group (#640)
Bumps the npm group with 1 update: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@types/node` from 20.19.2 to 20.19.7
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 20.19.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-19 13:33:52 +08:00
dependabot[bot]
f0b3259de2 chore(deps): bump the npm group across 1 directory with 4 updates (#638)
Bumps the npm group with 3 updates in the / directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) and [prettier](https://github.com/prettier/prettier).


Updates `@types/node` from 20.19.1 to 20.19.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@vitest/coverage-v8` from 3.2.3 to 3.2.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v3.2.4/packages/coverage-v8)

Updates `prettier` from 3.5.3 to 3.6.2
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.5.3...3.6.2)

Updates `vitest` from 3.2.3 to 3.2.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v3.2.4/packages/vitest)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 20.19.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 3.2.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: prettier
  dependency-version: 3.6.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm
- dependency-name: vitest
  dependency-version: 3.2.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-10 21:36:54 +08:00
dependabot[bot]
f37a2f9143 chore(deps): bump the npm group with 2 updates (#635)
Bumps the npm group with 2 updates: [glob](https://github.com/isaacs/node-glob) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `glob` from 11.0.2 to 11.0.3
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v11.0.2...v11.0.3)

Updates `@types/node` from 20.19.0 to 20.19.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 11.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@types/node"
  dependency-version: 20.19.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-18 14:36:57 -04:00
dependabot[bot]
db560141c6 chore(deps): bump brace-expansion from 2.0.1 to 2.0.2 (#634)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 2.0.1 to 2.0.2.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v2.0.1...v2.0.2)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 2.0.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-11 19:04:49 -04:00
Rui Chen
40521a2029 chore: update dist/index.js
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-06-11 11:35:37 -04:00
Rui Chen
c8d8e91662 chore: add prettier config (#633)
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-06-11 11:34:43 -04:00
Adriano dos Santos Fernandes
605f567f95 feat: add input option overwrite_files. (#343)
* Add input option overwrite_files.

* Fix description.

* update test and run fmt/build

Signed-off-by: Rui Chen <rui@chenrui.dev>

---------

Signed-off-by: Rui Chen <rui@chenrui.dev>
Co-authored-by: Rui Chen <rui@chenrui.dev>
2025-06-11 01:54:42 -04:00
Rui Chen
5822334cb4 chore: swap node assert with vitest assert
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-06-10 21:02:08 -04:00
Rui Chen
72f2c25fcb release 2.3.2
Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-06-10 18:33:08 -04:00
Rui Chen
552dc5524b fix: revert fs:readableWebStream change (#632)
* Revert "fix: fix file closing issue (#629)"

This reverts commit 07a2257003.

* fix: revert `fh.readableWebStream` change

Signed-off-by: Rui Chen <rui@chenrui.dev>

---------

Signed-off-by: Rui Chen <rui@chenrui.dev>
2025-06-10 18:27:48 -04:00
16 changed files with 1349 additions and 1562 deletions

View File

@@ -12,13 +12,9 @@ updates:
- dependency-name: node-fetch - dependency-name: node-fetch
versions: versions:
- ">=3.0.0" - ">=3.0.0"
# ignore mime and @types/mime per https://github.com/softprops/action-gh-release/pull/475 - dependency-name: "@types/node"
- dependency-name: mime
versions: versions:
- ">=4.0.0" - ">=22.0.0"
- dependency-name: "@types/mime"
versions:
- ">=4.0.0"
commit-message: commit-message:
prefix: "chore(deps)" prefix: "chore(deps)"
- package-ecosystem: github-actions - package-ecosystem: github-actions

View File

@@ -8,9 +8,9 @@ jobs:
build: build:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
with: with:
node-version-file: ".tool-versions" node-version-file: ".tool-versions"
cache: "npm" cache: "npm"

16
.prettierignore Normal file
View File

@@ -0,0 +1,16 @@
# Build outputs
dist/
lib/
coverage/
# Dependencies
node_modules/
# Misc
.github/
*.log
.DS_Store
__tests__/release.txt
# Package files
package-lock.json

11
.prettierrc.js Normal file
View File

@@ -0,0 +1,11 @@
/**
* @type {import('prettier').Config}
*/
module.exports = {
trailingComma: 'all',
tabWidth: 2,
semi: true,
singleQuote: true,
printWidth: 100,
bracketSpacing: true,
};

View File

@@ -1 +1 @@
nodejs 24.2.0 nodejs 24.11.0

View File

@@ -1,3 +1,72 @@
## 2.5.0
## What's Changed
### Exciting New Features 🎉
* feat: mark release as draft until all artifacts are uploaded by @dumbmoron in https://github.com/softprops/action-gh-release/pull/692
### Other Changes 🔄
* dependency updates
## 2.4.2
## What's Changed
### Exciting New Features 🎉
* feat: Ensure generated release notes cannot be over 125000 characters by @BeryJu in https://github.com/softprops/action-gh-release/pull/684
### Other Changes 🔄
* dependency updates
## 2.4.1
## What's Changed
### Other Changes 🔄
* fix(util): support brace expansion globs containing commas in parseInputFiles by @Copilot in https://github.com/softprops/action-gh-release/pull/672
* fix: gracefully fallback to body when body_path cannot be read by @Copilot in https://github.com/softprops/action-gh-release/pull/671
## 2.4.0
## What's Changed
### Exciting New Features 🎉
* feat(action): respect working_directory for files globs by @stephenway in https://github.com/softprops/action-gh-release/pull/667
## 2.3.4
## What's Changed
### Bug fixes 🐛
* fix(action): handle 422 already_exists race condition by @stephenway in https://github.com/softprops/action-gh-release/pull/665
### Other Changes 🔄
- dependency updates
## 2.3.3
## What's Changed
### Exciting New Features 🎉
* feat: add input option `overwrite_files` by @asfernandes in https://github.com/softprops/action-gh-release/pull/343
### Other Changes 🔄
- dependency updates
## 2.3.2
* fix: revert fs `readableWebStream` change
## 2.3.1 ## 2.3.1
### Bug fixes 🐛 ### Bug fixes 🐛

View File

@@ -51,7 +51,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Release - name: Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
if: github.ref_type == 'tag' if: github.ref_type == 'tag'
@@ -72,7 +72,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Release - name: Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
``` ```
@@ -99,7 +99,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Build - name: Build
run: echo ${{ github.sha }} > Release.txt run: echo ${{ github.sha }} > Release.txt
- name: Test - name: Test
@@ -123,7 +123,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Build - name: Build
run: echo ${{ github.sha }} > Release.txt run: echo ${{ github.sha }} > Release.txt
- name: Test - name: Test
@@ -157,7 +157,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Generate Changelog - name: Generate Changelog
run: echo "# Good things have arrived" > ${{ github.workspace }}-CHANGELOG.txt run: echo "# Good things have arrived" > ${{ github.workspace }}-CHANGELOG.txt
- name: Release - name: Release
@@ -185,6 +185,7 @@ The following are optional as `step.with` keys
| `prerelease` | Boolean | Indicator of whether or not is a prerelease | | `prerelease` | Boolean | Indicator of whether or not is a prerelease |
| `preserve_order` | Boolean | Indicator of whether order of files should be preserved when uploading assets | | `preserve_order` | Boolean | Indicator of whether order of files should be preserved when uploading assets |
| `files` | String | Newline-delimited globs of paths to assets to upload for release | | `files` | String | Newline-delimited globs of paths to assets to upload for release |
| `overwrite_files` | Boolean | Indicator of whether files should be overwritten when they already exist. Defaults to true |
| `name` | String | Name of the release. defaults to tag name | | `name` | String | Name of the release. defaults to tag name |
| `tag_name` | String | Name of a tag. defaults to `github.ref_name` | | `tag_name` | String | Name of a tag. defaults to `github.ref_name` |
| `fail_on_unmatched_files` | Boolean | Indicator of whether to fail if any of the `files` globs match nothing | | `fail_on_unmatched_files` | Boolean | Indicator of whether to fail if any of the `files` globs match nothing |

View File

@@ -1,63 +1,64 @@
import * as assert from "assert";
import { import {
mimeOrDefault,
asset, asset,
Releaser,
Release,
findTagFromReleases, findTagFromReleases,
} from "../src/github"; mimeOrDefault,
release,
Release,
Releaser,
} from '../src/github';
import { describe, it } from "vitest"; import { assert, describe, it } from 'vitest';
describe("github", () => { describe('github', () => {
describe("mimeOrDefault", () => { describe('mimeOrDefault', () => {
it("returns a specific mime for common path", async () => { it('returns a specific mime for common path', async () => {
assert.equal(mimeOrDefault("foo.tar.gz"), "application/gzip"); assert.equal(mimeOrDefault('foo.tar.gz'), 'application/gzip');
}); });
it("returns default mime for uncommon path", async () => { it('returns default mime for uncommon path', async () => {
assert.equal(mimeOrDefault("foo.uncommon"), "application/octet-stream"); assert.equal(mimeOrDefault('foo.uncommon'), 'application/octet-stream');
}); });
}); });
describe("asset", () => { describe('asset', () => {
it("derives asset info from a path", async () => { it('derives asset info from a path', async () => {
const { name, mime, size } = asset("tests/data/foo/bar.txt"); const { name, mime, size } = asset('tests/data/foo/bar.txt');
assert.equal(name, "bar.txt"); assert.equal(name, 'bar.txt');
assert.equal(mime, "text/plain"); assert.equal(mime, 'text/plain');
assert.equal(size, 10); assert.equal(size, 10);
}); });
}); });
describe("findTagFromReleases", () => { describe('findTagFromReleases', () => {
const owner = "owner"; const owner = 'owner';
const repo = "repo"; const repo = 'repo';
const mockRelease: Release = { const mockRelease: Release = {
id: 1, id: 1,
upload_url: `https://api.github.com/repos/${owner}/${repo}/releases/1/assets`, upload_url: `https://api.github.com/repos/${owner}/${repo}/releases/1/assets`,
html_url: `https://github.com/${owner}/${repo}/releases/tag/v1.0.0`, html_url: `https://github.com/${owner}/${repo}/releases/tag/v1.0.0`,
tag_name: "v1.0.0", tag_name: 'v1.0.0',
name: "Test Release", name: 'Test Release',
body: "Test body", body: 'Test body',
target_commitish: "main", target_commitish: 'main',
draft: false, draft: false,
prerelease: false, prerelease: false,
assets: [], assets: [],
} as const; } as const;
const mockReleaser: Releaser = { const mockReleaser: Releaser = {
getReleaseByTag: () => Promise.reject("Not implemented"), getReleaseByTag: () => Promise.reject('Not implemented'),
createRelease: () => Promise.reject("Not implemented"), createRelease: () => Promise.reject('Not implemented'),
updateRelease: () => Promise.reject("Not implemented"), updateRelease: () => Promise.reject('Not implemented'),
finalizeRelease: () => Promise.reject('Not implemented'),
allReleases: async function* () { allReleases: async function* () {
yield { data: [mockRelease] }; yield { data: [mockRelease] };
}, },
} as const; } as const;
describe("when the tag_name is not an empty string", () => { describe('when the tag_name is not an empty string', () => {
const targetTag = "v1.0.0"; const targetTag = 'v1.0.0';
it("finds a matching release in first batch of results", async () => { it('finds a matching release in first batch of results', async () => {
const targetRelease = { const targetRelease = {
...mockRelease, ...mockRelease,
owner, owner,
@@ -68,7 +69,7 @@ describe("github", () => {
...mockRelease, ...mockRelease,
owner, owner,
repo, repo,
tag_name: "v1.0.1", tag_name: 'v1.0.1',
}; };
const releaser = { const releaser = {
@@ -79,17 +80,12 @@ describe("github", () => {
}, },
}; };
const result = await findTagFromReleases( const result = await findTagFromReleases(releaser, owner, repo, targetTag);
releaser,
owner,
repo,
targetTag,
);
assert.deepStrictEqual(result, targetRelease); assert.deepStrictEqual(result, targetRelease);
}); });
it("finds a matching release in second batch of results", async () => { it('finds a matching release in second batch of results', async () => {
const targetRelease = { const targetRelease = {
...mockRelease, ...mockRelease,
owner, owner,
@@ -100,7 +96,7 @@ describe("github", () => {
...mockRelease, ...mockRelease,
owner, owner,
repo, repo,
tag_name: "v1.0.1", tag_name: 'v1.0.1',
}; };
const releaser = { const releaser = {
@@ -111,21 +107,16 @@ describe("github", () => {
}, },
}; };
const result = await findTagFromReleases( const result = await findTagFromReleases(releaser, owner, repo, targetTag);
releaser,
owner,
repo,
targetTag,
);
assert.deepStrictEqual(result, targetRelease); assert.deepStrictEqual(result, targetRelease);
}); });
it("returns undefined when a release is not found in any batch", async () => { it('returns undefined when a release is not found in any batch', async () => {
const otherRelease = { const otherRelease = {
...mockRelease, ...mockRelease,
owner, owner,
repo, repo,
tag_name: "v1.0.1", tag_name: 'v1.0.1',
}; };
const releaser = { const releaser = {
...mockReleaser, ...mockReleaser,
@@ -135,17 +126,12 @@ describe("github", () => {
}, },
}; };
const result = await findTagFromReleases( const result = await findTagFromReleases(releaser, owner, repo, targetTag);
releaser,
owner,
repo,
targetTag,
);
assert.strictEqual(result, undefined); assert.strictEqual(result, undefined);
}); });
it("returns undefined when no releases are returned", async () => { it('returns undefined when no releases are returned', async () => {
const releaser = { const releaser = {
...mockReleaser, ...mockReleaser,
allReleases: async function* () { allReleases: async function* () {
@@ -153,21 +139,16 @@ describe("github", () => {
}, },
}; };
const result = await findTagFromReleases( const result = await findTagFromReleases(releaser, owner, repo, targetTag);
releaser,
owner,
repo,
targetTag,
);
assert.strictEqual(result, undefined); assert.strictEqual(result, undefined);
}); });
}); });
describe("when the tag_name is an empty string", () => { describe('when the tag_name is an empty string', () => {
const emptyTag = ""; const emptyTag = '';
it("finds a matching release in first batch of results", async () => { it('finds a matching release in first batch of results', async () => {
const targetRelease = { const targetRelease = {
...mockRelease, ...mockRelease,
owner, owner,
@@ -178,7 +159,7 @@ describe("github", () => {
...mockRelease, ...mockRelease,
owner, owner,
repo, repo,
tag_name: "v1.0.1", tag_name: 'v1.0.1',
}; };
const releaser = { const releaser = {
@@ -189,17 +170,12 @@ describe("github", () => {
}, },
}; };
const result = await findTagFromReleases( const result = await findTagFromReleases(releaser, owner, repo, emptyTag);
releaser,
owner,
repo,
emptyTag,
);
assert.deepStrictEqual(result, targetRelease); assert.deepStrictEqual(result, targetRelease);
}); });
it("finds a matching release in second batch of results", async () => { it('finds a matching release in second batch of results', async () => {
const targetRelease = { const targetRelease = {
...mockRelease, ...mockRelease,
owner, owner,
@@ -210,7 +186,7 @@ describe("github", () => {
...mockRelease, ...mockRelease,
owner, owner,
repo, repo,
tag_name: "v1.0.1", tag_name: 'v1.0.1',
}; };
const releaser = { const releaser = {
@@ -221,21 +197,16 @@ describe("github", () => {
}, },
}; };
const result = await findTagFromReleases( const result = await findTagFromReleases(releaser, owner, repo, emptyTag);
releaser,
owner,
repo,
emptyTag,
);
assert.deepStrictEqual(result, targetRelease); assert.deepStrictEqual(result, targetRelease);
}); });
it("returns undefined when a release is not found in any batch", async () => { it('returns undefined when a release is not found in any batch', async () => {
const otherRelease = { const otherRelease = {
...mockRelease, ...mockRelease,
owner, owner,
repo, repo,
tag_name: "v1.0.1", tag_name: 'v1.0.1',
}; };
const releaser = { const releaser = {
...mockReleaser, ...mockReleaser,
@@ -245,17 +216,12 @@ describe("github", () => {
}, },
}; };
const result = await findTagFromReleases( const result = await findTagFromReleases(releaser, owner, repo, emptyTag);
releaser,
owner,
repo,
emptyTag,
);
assert.strictEqual(result, undefined); assert.strictEqual(result, undefined);
}); });
it("returns undefined when no releases are returned", async () => { it('returns undefined when no releases are returned', async () => {
const releaser = { const releaser = {
...mockReleaser, ...mockReleaser,
allReleases: async function* () { allReleases: async function* () {
@@ -263,15 +229,82 @@ describe("github", () => {
}, },
}; };
const result = await findTagFromReleases( const result = await findTagFromReleases(releaser, owner, repo, emptyTag);
releaser,
owner,
repo,
emptyTag,
);
assert.strictEqual(result, undefined); assert.strictEqual(result, undefined);
}); });
}); });
}); });
describe('error handling', () => {
it('handles 422 already_exists error gracefully', async () => {
const mockReleaser: Releaser = {
getReleaseByTag: () => Promise.reject('Not implemented'),
createRelease: () =>
Promise.reject({
status: 422,
response: { data: { errors: [{ code: 'already_exists' }] } },
}),
updateRelease: () =>
Promise.resolve({
data: {
id: 1,
upload_url: 'test',
html_url: 'test',
tag_name: 'v1.0.0',
name: 'test',
body: 'test',
target_commitish: 'main',
draft: true,
prerelease: false,
assets: [],
},
}),
finalizeRelease: async () => {},
allReleases: async function* () {
yield {
data: [
{
id: 1,
upload_url: 'test',
html_url: 'test',
tag_name: 'v1.0.0',
name: 'test',
body: 'test',
target_commitish: 'main',
draft: false,
prerelease: false,
assets: [],
},
],
};
},
} as const;
const config = {
github_token: 'test-token',
github_ref: 'refs/tags/v1.0.0',
github_repository: 'owner/repo',
input_tag_name: undefined,
input_name: undefined,
input_body: undefined,
input_body_path: undefined,
input_files: [],
input_draft: undefined,
input_prerelease: undefined,
input_preserve_order: undefined,
input_overwrite_files: undefined,
input_fail_on_unmatched_files: false,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_append_body: false,
input_make_latest: undefined,
};
const result = await release(config, mockReleaser, 1);
assert.ok(result);
assert.equal(result.id, 1);
});
});
}); });

View File

@@ -1,56 +1,72 @@
import { import {
releaseBody, alignAssetName,
isTag, isTag,
paths,
parseConfig, parseConfig,
parseInputFiles, parseInputFiles,
paths,
releaseBody,
unmatchedPatterns, unmatchedPatterns,
uploadUrl, uploadUrl,
alignAssetName, } from '../src/util';
} from "../src/util";
import * as assert from "assert";
import { describe, it, expect } from "vitest"; import { assert, describe, expect, it } from 'vitest';
describe("util", () => { describe('util', () => {
describe("uploadUrl", () => { describe('uploadUrl', () => {
it("strips template", () => { it('strips template', () => {
assert.equal( assert.equal(
uploadUrl( uploadUrl(
"https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}", 'https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}',
), ),
"https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets", 'https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets',
); );
}); });
}); });
describe("parseInputFiles", () => { describe('parseInputFiles', () => {
it("parses empty strings", () => { it('parses empty strings', () => {
assert.deepStrictEqual(parseInputFiles(""), []); assert.deepStrictEqual(parseInputFiles(''), []);
}); });
it("parses comma-delimited strings", () => { it('parses comma-delimited strings', () => {
assert.deepStrictEqual(parseInputFiles("foo,bar"), ["foo", "bar"]); assert.deepStrictEqual(parseInputFiles('foo,bar'), ['foo', 'bar']);
}); });
it("parses newline and comma-delimited (and then some)", () => { it('parses newline and comma-delimited (and then some)', () => {
assert.deepStrictEqual( assert.deepStrictEqual(parseInputFiles('foo,bar\nbaz,boom,\n\ndoom,loom '), [
parseInputFiles("foo,bar\nbaz,boom,\n\ndoom,loom "), 'foo',
["foo", "bar", "baz", "boom", "doom", "loom"], 'bar',
); 'baz',
'boom',
'doom',
'loom',
]);
});
it('handles globs with brace groups containing commas', () => {
assert.deepStrictEqual(parseInputFiles('./**/*.{exe,deb,tar.gz}\nfoo,bar'), [
'./**/*.{exe,deb,tar.gz}',
'foo',
'bar',
]);
});
it('handles single-line brace pattern correctly', () => {
assert.deepStrictEqual(parseInputFiles('./**/*.{exe,deb,tar.gz}'), [
'./**/*.{exe,deb,tar.gz}',
]);
}); });
}); });
describe("releaseBody", () => { describe('releaseBody', () => {
it("uses input body", () => { it('uses input body', () => {
assert.equal( assert.equal(
"foo", 'foo',
releaseBody({ releaseBody({
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "", github_token: '',
input_body: "foo", input_body: 'foo',
input_body_path: undefined, input_body_path: undefined,
input_draft: false, input_draft: false,
input_prerelease: false, input_prerelease: false,
input_preserve_order: undefined, input_preserve_order: undefined,
input_files: [], input_files: [],
input_overwrite_files: undefined,
input_name: undefined, input_name: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_target_commitish: undefined, input_target_commitish: undefined,
@@ -60,19 +76,20 @@ describe("util", () => {
}), }),
); );
}); });
it("uses input body path", () => { it('uses input body path', () => {
assert.equal( assert.equal(
"bar", 'bar',
releaseBody({ releaseBody({
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "", github_token: '',
input_body: undefined, input_body: undefined,
input_body_path: "__tests__/release.txt", input_body_path: '__tests__/release.txt',
input_draft: false, input_draft: false,
input_prerelease: false, input_prerelease: false,
input_preserve_order: undefined, input_preserve_order: undefined,
input_files: [], input_files: [],
input_overwrite_files: undefined,
input_name: undefined, input_name: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_target_commitish: undefined, input_target_commitish: undefined,
@@ -82,19 +99,20 @@ describe("util", () => {
}), }),
); );
}); });
it("defaults to body path when both body and body path are provided", () => { it('defaults to body path when both body and body path are provided', () => {
assert.equal( assert.equal(
"bar", 'bar',
releaseBody({ releaseBody({
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "", github_token: '',
input_body: "foo", input_body: 'foo',
input_body_path: "__tests__/release.txt", input_body_path: '__tests__/release.txt',
input_draft: false, input_draft: false,
input_prerelease: false, input_prerelease: false,
input_preserve_order: undefined, input_preserve_order: undefined,
input_files: [], input_files: [],
input_overwrite_files: undefined,
input_name: undefined, input_name: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_target_commitish: undefined, input_target_commitish: undefined,
@@ -104,9 +122,55 @@ describe("util", () => {
}), }),
); );
}); });
it('falls back to body when body_path is missing', () => {
assert.equal(
releaseBody({
github_ref: '',
github_repository: '',
github_token: '',
input_body: 'fallback-body',
input_body_path: '__tests__/does-not-exist.txt',
input_draft: false,
input_prerelease: false,
input_files: [],
input_overwrite_files: undefined,
input_preserve_order: undefined,
input_name: undefined,
input_tag_name: undefined,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_make_latest: undefined,
}),
'fallback-body',
);
});
it('returns undefined when body_path is missing and body is not provided', () => {
assert.equal(
releaseBody({
github_ref: '',
github_repository: '',
github_token: '',
input_body: undefined,
input_body_path: '__tests__/does-not-exist.txt',
input_draft: false,
input_prerelease: false,
input_files: [],
input_overwrite_files: undefined,
input_preserve_order: undefined,
input_name: undefined,
input_tag_name: undefined,
input_target_commitish: undefined,
input_discussion_category_name: undefined,
input_generate_release_notes: false,
input_make_latest: undefined,
}),
undefined,
);
});
}); });
describe("parseConfig", () => { describe('parseConfig', () => {
it("parses basic config", () => { it('parses basic config', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
parseConfig({ parseConfig({
// note: inputs declared in actions.yml, even when declared not required, // note: inputs declared in actions.yml, even when declared not required,
@@ -115,13 +179,14 @@ describe("util", () => {
// as an empty string !== undefined in terms of what we pass to the api // as an empty string !== undefined in terms of what we pass to the api
// so we cover that in a test case here to ensure undefined values are actually // so we cover that in a test case here to ensure undefined values are actually
// resolved as undefined and not empty strings // resolved as undefined and not empty strings
INPUT_TARGET_COMMITISH: "", INPUT_TARGET_COMMITISH: '',
INPUT_DISCUSSION_CATEGORY_NAME: "", INPUT_DISCUSSION_CATEGORY_NAME: '',
}), }),
{ {
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "", github_token: '',
input_working_directory: undefined,
input_append_body: false, input_append_body: false,
input_body: undefined, input_body: undefined,
input_body_path: undefined, input_body_path: undefined,
@@ -129,6 +194,7 @@ describe("util", () => {
input_prerelease: undefined, input_prerelease: undefined,
input_preserve_order: undefined, input_preserve_order: undefined,
input_files: [], input_files: [],
input_overwrite_files: undefined,
input_name: undefined, input_name: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_fail_on_unmatched_files: false, input_fail_on_unmatched_files: false,
@@ -140,41 +206,44 @@ describe("util", () => {
); );
}); });
it("parses basic config with commitish", () => { it('parses basic config with commitish', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
parseConfig({ parseConfig({
INPUT_TARGET_COMMITISH: "affa18ef97bc9db20076945705aba8c516139abd", INPUT_TARGET_COMMITISH: 'affa18ef97bc9db20076945705aba8c516139abd',
}), }),
{ {
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "", github_token: '',
input_working_directory: undefined,
input_append_body: false, input_append_body: false,
input_body: undefined, input_body: undefined,
input_body_path: undefined, input_body_path: undefined,
input_draft: undefined, input_draft: undefined,
input_prerelease: undefined, input_prerelease: undefined,
input_files: [], input_files: [],
input_overwrite_files: undefined,
input_preserve_order: undefined, input_preserve_order: undefined,
input_name: undefined, input_name: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_fail_on_unmatched_files: false, input_fail_on_unmatched_files: false,
input_target_commitish: "affa18ef97bc9db20076945705aba8c516139abd", input_target_commitish: 'affa18ef97bc9db20076945705aba8c516139abd',
input_discussion_category_name: undefined, input_discussion_category_name: undefined,
input_generate_release_notes: false, input_generate_release_notes: false,
input_make_latest: undefined, input_make_latest: undefined,
}, },
); );
}); });
it("supports discussion category names", () => { it('supports discussion category names', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
parseConfig({ parseConfig({
INPUT_DISCUSSION_CATEGORY_NAME: "releases", INPUT_DISCUSSION_CATEGORY_NAME: 'releases',
}), }),
{ {
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "", github_token: '',
input_working_directory: undefined,
input_append_body: false, input_append_body: false,
input_body: undefined, input_body: undefined,
input_body_path: undefined, input_body_path: undefined,
@@ -183,25 +252,27 @@ describe("util", () => {
input_files: [], input_files: [],
input_preserve_order: undefined, input_preserve_order: undefined,
input_name: undefined, input_name: undefined,
input_overwrite_files: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_fail_on_unmatched_files: false, input_fail_on_unmatched_files: false,
input_target_commitish: undefined, input_target_commitish: undefined,
input_discussion_category_name: "releases", input_discussion_category_name: 'releases',
input_generate_release_notes: false, input_generate_release_notes: false,
input_make_latest: undefined, input_make_latest: undefined,
}, },
); );
}); });
it("supports generating release notes", () => { it('supports generating release notes', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
parseConfig({ parseConfig({
INPUT_GENERATE_RELEASE_NOTES: "true", INPUT_GENERATE_RELEASE_NOTES: 'true',
}), }),
{ {
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "", github_token: '',
input_working_directory: undefined,
input_append_body: false, input_append_body: false,
input_body: undefined, input_body: undefined,
input_body_path: undefined, input_body_path: undefined,
@@ -209,6 +280,7 @@ describe("util", () => {
input_prerelease: undefined, input_prerelease: undefined,
input_preserve_order: undefined, input_preserve_order: undefined,
input_files: [], input_files: [],
input_overwrite_files: undefined,
input_name: undefined, input_name: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_fail_on_unmatched_files: false, input_fail_on_unmatched_files: false,
@@ -220,19 +292,20 @@ describe("util", () => {
); );
}); });
it("prefers GITHUB_TOKEN over token input for backwards compatibility", () => { it('prefers GITHUB_TOKEN over token input for backwards compatibility', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
parseConfig({ parseConfig({
INPUT_DRAFT: "false", INPUT_DRAFT: 'false',
INPUT_PRERELEASE: "true", INPUT_PRERELEASE: 'true',
INPUT_PRESERVE_ORDER: "true", INPUT_PRESERVE_ORDER: 'true',
GITHUB_TOKEN: "env-token", GITHUB_TOKEN: 'env-token',
INPUT_TOKEN: "input-token", INPUT_TOKEN: 'input-token',
}), }),
{ {
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "env-token", github_token: 'env-token',
input_working_directory: undefined,
input_append_body: false, input_append_body: false,
input_body: undefined, input_body: undefined,
input_body_path: undefined, input_body_path: undefined,
@@ -240,6 +313,7 @@ describe("util", () => {
input_prerelease: true, input_prerelease: true,
input_preserve_order: true, input_preserve_order: true,
input_files: [], input_files: [],
input_overwrite_files: undefined,
input_name: undefined, input_name: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_fail_on_unmatched_files: false, input_fail_on_unmatched_files: false,
@@ -250,17 +324,18 @@ describe("util", () => {
}, },
); );
}); });
it("uses input token as the source of GITHUB_TOKEN by default", () => { it('uses input token as the source of GITHUB_TOKEN by default', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
parseConfig({ parseConfig({
INPUT_DRAFT: "false", INPUT_DRAFT: 'false',
INPUT_PRERELEASE: "true", INPUT_PRERELEASE: 'true',
INPUT_TOKEN: "input-token", INPUT_TOKEN: 'input-token',
}), }),
{ {
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "input-token", github_token: 'input-token',
input_working_directory: undefined,
input_append_body: false, input_append_body: false,
input_body: undefined, input_body: undefined,
input_body_path: undefined, input_body_path: undefined,
@@ -268,6 +343,7 @@ describe("util", () => {
input_prerelease: true, input_prerelease: true,
input_preserve_order: undefined, input_preserve_order: undefined,
input_files: [], input_files: [],
input_overwrite_files: undefined,
input_name: undefined, input_name: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_fail_on_unmatched_files: false, input_fail_on_unmatched_files: false,
@@ -278,16 +354,17 @@ describe("util", () => {
}, },
); );
}); });
it("parses basic config with draft and prerelease", () => { it('parses basic config with draft and prerelease', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
parseConfig({ parseConfig({
INPUT_DRAFT: "false", INPUT_DRAFT: 'false',
INPUT_PRERELEASE: "true", INPUT_PRERELEASE: 'true',
}), }),
{ {
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "", github_token: '',
input_working_directory: undefined,
input_append_body: false, input_append_body: false,
input_body: undefined, input_body: undefined,
input_body_path: undefined, input_body_path: undefined,
@@ -295,6 +372,7 @@ describe("util", () => {
input_prerelease: true, input_prerelease: true,
input_preserve_order: undefined, input_preserve_order: undefined,
input_files: [], input_files: [],
input_overwrite_files: undefined,
input_name: undefined, input_name: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_fail_on_unmatched_files: false, input_fail_on_unmatched_files: false,
@@ -305,15 +383,16 @@ describe("util", () => {
}, },
); );
}); });
it("parses basic config where make_latest is passed", () => { it('parses basic config where make_latest is passed', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
parseConfig({ parseConfig({
INPUT_MAKE_LATEST: "false", INPUT_MAKE_LATEST: 'false',
}), }),
{ {
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "", github_token: '',
input_working_directory: undefined,
input_append_body: false, input_append_body: false,
input_body: undefined, input_body: undefined,
input_body_path: undefined, input_body_path: undefined,
@@ -322,24 +401,26 @@ describe("util", () => {
input_preserve_order: undefined, input_preserve_order: undefined,
input_files: [], input_files: [],
input_name: undefined, input_name: undefined,
input_overwrite_files: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_fail_on_unmatched_files: false, input_fail_on_unmatched_files: false,
input_target_commitish: undefined, input_target_commitish: undefined,
input_discussion_category_name: undefined, input_discussion_category_name: undefined,
input_generate_release_notes: false, input_generate_release_notes: false,
input_make_latest: "false", input_make_latest: 'false',
}, },
); );
}); });
it("parses basic config with append_body", () => { it('parses basic config with append_body', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
parseConfig({ parseConfig({
INPUT_APPEND_BODY: "true", INPUT_APPEND_BODY: 'true',
}), }),
{ {
github_ref: "", github_ref: '',
github_repository: "", github_repository: '',
github_token: "", github_token: '',
input_working_directory: undefined,
input_append_body: true, input_append_body: true,
input_body: undefined, input_body: undefined,
input_body_path: undefined, input_body_path: undefined,
@@ -347,6 +428,7 @@ describe("util", () => {
input_prerelease: undefined, input_prerelease: undefined,
input_preserve_order: undefined, input_preserve_order: undefined,
input_files: [], input_files: [],
input_overwrite_files: undefined,
input_name: undefined, input_name: undefined,
input_tag_name: undefined, input_tag_name: undefined,
input_fail_on_unmatched_files: false, input_fail_on_unmatched_files: false,
@@ -358,46 +440,86 @@ describe("util", () => {
); );
}); });
}); });
describe("isTag", () => { describe('isTag', () => {
it("returns true for tags", async () => { it('returns true for tags', async () => {
assert.equal(isTag("refs/tags/foo"), true); assert.equal(isTag('refs/tags/foo'), true);
}); });
it("returns false for other kinds of refs", async () => { it('returns false for other kinds of refs', async () => {
assert.equal(isTag("refs/heads/master"), false); assert.equal(isTag('refs/heads/master'), false);
}); });
}); });
describe("paths", () => { describe('paths', () => {
it("resolves files given a set of paths", async () => { it('resolves files given a set of paths', async () => {
assert.deepStrictEqual( assert.deepStrictEqual(paths(['tests/data/**/*', 'tests/data/does/not/exist/*']), [
paths(["tests/data/**/*", "tests/data/does/not/exist/*"]), 'tests/data/foo/bar.txt',
["tests/data/foo/bar.txt"], ]);
); });
it('resolves files relative to working_directory', async () => {
assert.deepStrictEqual(paths(['data/**/*'], 'tests'), ['tests/data/foo/bar.txt']);
}); });
}); });
describe("unmatchedPatterns", () => { describe('unmatchedPatterns', () => {
it("returns the patterns that don't match any files", async () => { it("returns the patterns that don't match any files", async () => {
assert.deepStrictEqual( assert.deepStrictEqual(
unmatchedPatterns(["tests/data/**/*", "tests/data/does/not/exist/*"]), unmatchedPatterns(['tests/data/**/*', 'tests/data/does/not/exist/*']),
["tests/data/does/not/exist/*"], ['tests/data/does/not/exist/*'],
); );
}); });
it('resolves unmatched relative to working_directory', async () => {
assert.deepStrictEqual(unmatchedPatterns(['data/does/not/exist/*'], 'tests'), [
'data/does/not/exist/*',
]);
});
}); });
describe("replaceSpacesWithDots", () => { describe('replaceSpacesWithDots', () => {
it("replaces all spaces with dots", () => { it('replaces all spaces with dots', () => {
expect(alignAssetName("John Doe.bla")).toBe("John.Doe.bla"); expect(alignAssetName('John Doe.bla')).toBe('John.Doe.bla');
}); });
it("handles names with multiple spaces", () => { it('handles names with multiple spaces', () => {
expect(alignAssetName("John William Doe.bla")).toBe( expect(alignAssetName('John William Doe.bla')).toBe('John.William.Doe.bla');
"John.William.Doe.bla",
);
}); });
it("returns the same string if there are no spaces", () => { it('returns the same string if there are no spaces', () => {
expect(alignAssetName("JohnDoe")).toBe("JohnDoe"); expect(alignAssetName('JohnDoe')).toBe('JohnDoe');
}); });
}); });
}); });
describe('parseInputFiles edge cases', () => {
it('handles multiple brace groups on same line', () => {
assert.deepStrictEqual(parseInputFiles('./**/*.{exe,deb},./dist/**/*.{zip,tar.gz}'), [
'./**/*.{exe,deb}',
'./dist/**/*.{zip,tar.gz}',
]);
});
it('handles nested braces', () => {
assert.deepStrictEqual(parseInputFiles('path/{a,{b,c}}/file.txt'), ['path/{a,{b,c}}/file.txt']);
});
it('handles empty comma-separated values', () => {
assert.deepStrictEqual(parseInputFiles('foo,,bar'), ['foo', 'bar']);
});
it('handles commas with spaces around braces', () => {
assert.deepStrictEqual(parseInputFiles(' ./**/*.{exe,deb} , file.txt '), [
'./**/*.{exe,deb}',
'file.txt',
]);
});
it('handles mixed newlines and commas with braces', () => {
assert.deepStrictEqual(parseInputFiles('file1.txt\n./**/*.{exe,deb},file2.txt\nfile3.txt'), [
'file1.txt',
'./**/*.{exe,deb}',
'file2.txt',
'file3.txt',
]);
});
});

View File

@@ -27,6 +27,13 @@ inputs:
files: files:
description: "Newline-delimited list of path globs for asset files to upload" description: "Newline-delimited list of path globs for asset files to upload"
required: false required: false
working_directory:
description: "Base directory to resolve 'files' globs against (defaults to job working-directory)"
required: false
overwrite_files:
description: "Overwrite existing files with the same name. Defaults to true"
required: false
default: 'true'
fail_on_unmatched_files: fail_on_unmatched_files:
description: "Fails if any of the `files` globs match nothing. Defaults to false" description: "Fails if any of the `files` globs match nothing. Defaults to false"
required: false required: false

8
dist/index.js vendored

File diff suppressed because one or more lines are too long

1712
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "action-gh-release", "name": "action-gh-release",
"version": "2.3.1", "version": "2.5.0",
"private": true, "private": true,
"description": "GitHub Action for creating GitHub Releases", "description": "GitHub Action for creating GitHub Releases",
"main": "lib/main.js", "main": "lib/main.js",
@@ -24,21 +24,21 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1", "@actions/core": "^1.11.1",
"@actions/github": "^6.0.1", "@actions/github": "^6.0.1",
"@octokit/plugin-retry": "^8.0.1", "@octokit/plugin-retry": "^8.0.3",
"@octokit/plugin-throttling": "^11.0.1", "@octokit/plugin-throttling": "^11.0.3",
"glob": "^11.0.2", "glob": "^13.0.0",
"mime-types": "^3.0.1" "mime-types": "^3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/glob": "^8.1.0", "@types/glob": "^9.0.0",
"@types/mime-types": "^3.0.1", "@types/mime-types": "^3.0.1",
"@types/node": "^22.15.21", "@types/node": "^20.19.25",
"@vercel/ncc": "^0.38.3", "@vercel/ncc": "^0.38.4",
"@vitest/coverage-v8": "^3.1.4", "@vitest/coverage-v8": "^4.0.13",
"prettier": "3.5.3", "prettier": "3.6.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.8.3", "typescript": "^5.9.3",
"typescript-formatter": "^7.2.2", "typescript-formatter": "^7.2.2",
"vitest": "^3.1.4" "vitest": "^4.0.4"
} }
} }

View File

@@ -1,9 +1,9 @@
import { GitHub } from "@actions/github/lib/utils"; import { GitHub } from '@actions/github/lib/utils';
import { Config, isTag, releaseBody, alignAssetName } from "./util"; import { statSync } from 'fs';
import { statSync } from "fs"; import { open } from 'fs/promises';
import { open } from "fs/promises"; import { lookup } from 'mime-types';
import { lookup } from "mime-types"; import { basename } from 'path';
import { basename } from "path"; import { alignAssetName, Config, isTag, releaseBody } from './util';
type GitHub = InstanceType<typeof GitHub>; type GitHub = InstanceType<typeof GitHub>;
@@ -27,11 +27,7 @@ export interface Release {
} }
export interface Releaser { export interface Releaser {
getReleaseByTag(params: { getReleaseByTag(params: { owner: string; repo: string; tag: string }): Promise<{ data: Release }>;
owner: string;
repo: string;
tag: string;
}): Promise<{ data: Release }>;
createRelease(params: { createRelease(params: {
owner: string; owner: string;
@@ -44,7 +40,7 @@ export interface Releaser {
target_commitish: string | undefined; target_commitish: string | undefined;
discussion_category_name: string | undefined; discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined; generate_release_notes: boolean | undefined;
make_latest: "true" | "false" | "legacy" | undefined; make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }>; }): Promise<{ data: Release }>;
updateRelease(params: { updateRelease(params: {
@@ -59,13 +55,16 @@ export interface Releaser {
prerelease: boolean | undefined; prerelease: boolean | undefined;
discussion_category_name: string | undefined; discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined; generate_release_notes: boolean | undefined;
make_latest: "true" | "false" | "legacy" | undefined; make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }>; }): Promise<{ data: Release }>;
allReleases(params: { finalizeRelease(params: {
owner: string; owner: string;
repo: string; repo: string;
}): AsyncIterableIterator<{ data: Release[] }>; release_id: number;
}): Promise<{ data: Release }>;
allReleases(params: { owner: string; repo: string }): AsyncIterableIterator<{ data: Release[] }>;
} }
export class GitHubReleaser implements Releaser { export class GitHubReleaser implements Releaser {
@@ -82,7 +81,27 @@ export class GitHubReleaser implements Releaser {
return this.github.rest.repos.getReleaseByTag(params); return this.github.rest.repos.getReleaseByTag(params);
} }
createRelease(params: { async getReleaseNotes(params: {
owner: string;
repo: string;
tag_name: string;
target_commitish: string | undefined;
}): Promise<{
data: {
name: string;
body: string;
};
}> {
return await this.github.rest.repos.generateReleaseNotes(params);
}
truncateReleaseNotes(input: string): string {
// release notes can be a maximum of 125000 characters
const githubNotesMaxCharLength = 125000;
return input.substring(0, githubNotesMaxCharLength - 1);
}
async createRelease(params: {
owner: string; owner: string;
repo: string; repo: string;
tag_name: string; tag_name: string;
@@ -93,19 +112,28 @@ export class GitHubReleaser implements Releaser {
target_commitish: string | undefined; target_commitish: string | undefined;
discussion_category_name: string | undefined; discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined; generate_release_notes: boolean | undefined;
make_latest: "true" | "false" | "legacy" | undefined; make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }> { }): Promise<{ data: Release }> {
if ( if (
typeof params.make_latest === "string" && typeof params.make_latest === 'string' &&
!["true", "false", "legacy"].includes(params.make_latest) !['true', 'false', 'legacy'].includes(params.make_latest)
) { ) {
params.make_latest = undefined; params.make_latest = undefined;
} }
if (params.generate_release_notes) {
const releaseNotes = await this.getReleaseNotes(params);
params.generate_release_notes = false;
if (params.body) {
params.body = `${params.body}\n\n${releaseNotes.data.body}`;
} else {
params.body = releaseNotes.data.body;
}
}
params.body = params.body ? this.truncateReleaseNotes(params.body) : undefined;
return this.github.rest.repos.createRelease(params); return this.github.rest.repos.createRelease(params);
} }
updateRelease(params: { async updateRelease(params: {
owner: string; owner: string;
repo: string; repo: string;
release_id: number; release_id: number;
@@ -117,22 +145,37 @@ export class GitHubReleaser implements Releaser {
prerelease: boolean | undefined; prerelease: boolean | undefined;
discussion_category_name: string | undefined; discussion_category_name: string | undefined;
generate_release_notes: boolean | undefined; generate_release_notes: boolean | undefined;
make_latest: "true" | "false" | "legacy" | undefined; make_latest: 'true' | 'false' | 'legacy' | undefined;
}): Promise<{ data: Release }> { }): Promise<{ data: Release }> {
if ( if (
typeof params.make_latest === "string" && typeof params.make_latest === 'string' &&
!["true", "false", "legacy"].includes(params.make_latest) !['true', 'false', 'legacy'].includes(params.make_latest)
) { ) {
params.make_latest = undefined; params.make_latest = undefined;
} }
if (params.generate_release_notes) {
const releaseNotes = await this.getReleaseNotes(params);
params.generate_release_notes = false;
if (params.body) {
params.body = `${params.body}\n\n${releaseNotes.data.body}`;
} else {
params.body = releaseNotes.data.body;
}
}
params.body = params.body ? this.truncateReleaseNotes(params.body) : undefined;
return this.github.rest.repos.updateRelease(params); return this.github.rest.repos.updateRelease(params);
} }
allReleases(params: { async finalizeRelease(params: { owner: string; repo: string; release_id: number }) {
owner: string; return await this.github.rest.repos.updateRelease({
repo: string; owner: params.owner,
}): AsyncIterableIterator<{ data: Release[] }> { repo: params.repo,
release_id: params.release_id,
draft: false,
});
}
allReleases(params: { owner: string; repo: string }): AsyncIterableIterator<{ data: Release[] }> {
const updatedParams = { per_page: 100, ...params }; const updatedParams = { per_page: 100, ...params };
return this.github.paginate.iterator( return this.github.paginate.iterator(
this.github.rest.repos.listReleases.endpoint.merge(updatedParams), this.github.rest.repos.listReleases.endpoint.merge(updatedParams),
@@ -149,7 +192,7 @@ export const asset = (path: string): ReleaseAsset => {
}; };
export const mimeOrDefault = (path: string): string => { export const mimeOrDefault = (path: string): string => {
return lookup(path) || "application/octet-stream"; return lookup(path) || 'application/octet-stream';
}; };
export const upload = async ( export const upload = async (
@@ -159,7 +202,7 @@ export const upload = async (
path: string, path: string,
currentAssets: Array<{ id: number; name: string }>, currentAssets: Array<{ id: number; name: string }>,
): Promise<any> => { ): Promise<any> => {
const [owner, repo] = config.github_repository.split("/"); const [owner, repo] = config.github_repository.split('/');
const { name, mime, size } = asset(path); const { name, mime, size } = asset(path);
const currentAsset = currentAssets.find( const currentAsset = currentAssets.find(
// note: GitHub renames asset filenames that have special characters, non-alphanumeric characters, and leading or trailing periods. The "List release assets" endpoint lists the renamed filenames. // note: GitHub renames asset filenames that have special characters, non-alphanumeric characters, and leading or trailing periods. The "List release assets" endpoint lists the renamed filenames.
@@ -168,28 +211,32 @@ export const upload = async (
({ name: currentName }) => currentName == alignAssetName(name), ({ name: currentName }) => currentName == alignAssetName(name),
); );
if (currentAsset) { if (currentAsset) {
console.log(`♻️ Deleting previously uploaded asset ${name}...`); if (config.input_overwrite_files === false) {
await github.rest.repos.deleteReleaseAsset({ console.log(`Asset ${name} already exists and overwrite_files is false...`);
asset_id: currentAsset.id || 1, return null;
owner, } else {
repo, console.log(`♻️ Deleting previously uploaded asset ${name}...`);
}); await github.rest.repos.deleteReleaseAsset({
asset_id: currentAsset.id || 1,
owner,
repo,
});
}
} }
console.log(`⬆️ Uploading ${name}...`); console.log(`⬆️ Uploading ${name}...`);
const endpoint = new URL(url); const endpoint = new URL(url);
endpoint.searchParams.append("name", name); endpoint.searchParams.append('name', name);
const fh = await open(path); const fh = await open(path);
try { try {
const stream = fh.readableWebStream();
const resp = await github.request({ const resp = await github.request({
method: "POST", method: 'POST',
url: endpoint.toString(), url: endpoint.toString(),
headers: { headers: {
"content-length": `${size}`, 'content-length': `${size}`,
"content-type": mime, 'content-type': mime,
authorization: `token ${config.github_token}`, authorization: `token ${config.github_token}`,
}, },
data: stream, data: fh.readableWebStream({ type: 'bytes' }),
}); });
const json = resp.data; const json = resp.data;
if (resp.status !== 201) { if (resp.status !== 201) {
@@ -202,7 +249,7 @@ export const upload = async (
console.log(`✅ Uploaded ${name}`); console.log(`✅ Uploaded ${name}`);
return json; return json;
} finally { } finally {
await fh.close().catch(() => {}); await fh.close();
} }
}; };
@@ -213,25 +260,18 @@ export const release = async (
): Promise<Release> => { ): Promise<Release> => {
if (maxRetries <= 0) { if (maxRetries <= 0) {
console.log(`❌ Too many retries. Aborting...`); console.log(`❌ Too many retries. Aborting...`);
throw new Error("Too many retries."); throw new Error('Too many retries.');
} }
const [owner, repo] = config.github_repository.split("/"); const [owner, repo] = config.github_repository.split('/');
const tag = const tag =
config.input_tag_name || config.input_tag_name ||
(isTag(config.github_ref) (isTag(config.github_ref) ? config.github_ref.replace('refs/tags/', '') : '');
? config.github_ref.replace("refs/tags/", "")
: "");
const discussion_category_name = config.input_discussion_category_name; const discussion_category_name = config.input_discussion_category_name;
const generate_release_notes = config.input_generate_release_notes; const generate_release_notes = config.input_generate_release_notes;
try { try {
const _release: Release | undefined = await findTagFromReleases( const _release: Release | undefined = await findTagFromReleases(releaser, owner, repo, tag);
releaser,
owner,
repo,
tag,
);
if (_release === undefined) { if (_release === undefined) {
return await createRelease( return await createRelease(
@@ -247,9 +287,7 @@ export const release = async (
} }
let existingRelease: Release = _release!; let existingRelease: Release = _release!;
console.log( console.log(`Found release ${existingRelease.name} (with id=${existingRelease.id})`);
`Found release ${existingRelease.name} (with id=${existingRelease.id})`,
);
const release_id = existingRelease.id; const release_id = existingRelease.id;
let target_commitish: string; let target_commitish: string;
@@ -271,23 +309,17 @@ export const release = async (
// body parts as a release gets updated. some users will likely want this while // body parts as a release gets updated. some users will likely want this while
// others won't previously this was duplicating content for most which // others won't previously this was duplicating content for most which
// no one wants // no one wants
const workflowBody = releaseBody(config) || ""; const workflowBody = releaseBody(config) || '';
const existingReleaseBody = existingRelease.body || ""; const existingReleaseBody = existingRelease.body || '';
let body: string; let body: string;
if (config.input_append_body && workflowBody && existingReleaseBody) { if (config.input_append_body && workflowBody && existingReleaseBody) {
body = existingReleaseBody + "\n" + workflowBody; body = existingReleaseBody + '\n' + workflowBody;
} else { } else {
body = workflowBody || existingReleaseBody; body = workflowBody || existingReleaseBody;
} }
const draft =
config.input_draft !== undefined
? config.input_draft
: existingRelease.draft;
const prerelease = const prerelease =
config.input_prerelease !== undefined config.input_prerelease !== undefined ? config.input_prerelease : existingRelease.prerelease;
? config.input_prerelease
: existingRelease.prerelease;
const make_latest = config.input_make_latest; const make_latest = config.input_make_latest;
@@ -299,7 +331,7 @@ export const release = async (
target_commitish, target_commitish,
name, name,
body, body,
draft, draft: existingRelease.draft,
prerelease, prerelease,
discussion_category_name, discussion_category_name,
generate_release_notes, generate_release_notes,
@@ -327,6 +359,45 @@ export const release = async (
} }
}; };
/**
* Finalizes a release by unmarking it as "draft" (if relevant)
* after all artifacts have been uploaded.
*
* @param config - Release configuration as specified by user
* @param releaser - The GitHub API wrapper for release operations
* @param release - The existing release to be finalized
* @param maxRetries - The maximum number of attempts to finalize the release
*/
export const finalizeRelease = async (
config: Config,
releaser: Releaser,
release: Release,
maxRetries: number = 3,
): Promise<Release> => {
if (config.input_draft === true) {
return release;
}
if (maxRetries <= 0) {
console.log(`❌ Too many retries. Aborting...`);
throw new Error('Too many retries.');
}
const [owner, repo] = config.github_repository.split('/');
try {
const { data } = await releaser.finalizeRelease({
owner,
repo,
release_id: release.id,
});
return data;
} catch {
console.log(`retrying... (${maxRetries - 1} retries remaining)`);
return finalizeRelease(config, releaser, release, maxRetries - 1);
}
};
/** /**
* Finds a release by tag name from all a repository's releases. * Finds a release by tag name from all a repository's releases.
* *
@@ -367,17 +438,14 @@ async function createRelease(
const tag_name = tag; const tag_name = tag;
const name = config.input_name || tag; const name = config.input_name || tag;
const body = releaseBody(config); const body = releaseBody(config);
const draft = config.input_draft;
const prerelease = config.input_prerelease; const prerelease = config.input_prerelease;
const target_commitish = config.input_target_commitish; const target_commitish = config.input_target_commitish;
const make_latest = config.input_make_latest; const make_latest = config.input_make_latest;
let commitMessage: string = ""; let commitMessage: string = '';
if (target_commitish) { if (target_commitish) {
commitMessage = ` using commit "${target_commitish}"`; commitMessage = ` using commit "${target_commitish}"`;
} }
console.log( console.log(`👩‍🏭 Creating new GitHub release for tag ${tag_name}${commitMessage}...`);
`👩‍🏭 Creating new GitHub release for tag ${tag_name}${commitMessage}...`,
);
try { try {
let release = await releaser.createRelease({ let release = await releaser.createRelease({
owner, owner,
@@ -385,7 +453,7 @@ async function createRelease(
tag_name, tag_name,
name, name,
body, body,
draft, draft: true,
prerelease, prerelease,
target_commitish, target_commitish,
discussion_category_name, discussion_category_name,
@@ -401,17 +469,27 @@ async function createRelease(
switch (error.status) { switch (error.status) {
case 403: case 403:
console.log( console.log(
"Skip retry — your GitHub token/PAT does not have the required permission to create a release", 'Skip retry — your GitHub token/PAT does not have the required permission to create a release',
); );
throw error; throw error;
case 404: case 404:
console.log("Skip retry - discussion category mismatch"); console.log('Skip retry - discussion category mismatch');
throw error; throw error;
case 422: case 422:
console.log("Skip retry - validation failed"); // Check if this is a race condition with "already_exists" error
throw error; const errorData = error.response?.data;
if (errorData?.errors?.[0]?.code === 'already_exists') {
console.log(
'⚠️ Release already exists (race condition detected), retrying to find and update existing release...',
);
// Don't throw - allow retry to find existing release
} else {
console.log('Skip retry - validation failed');
throw error;
}
break;
} }
console.log(`retrying... (${maxRetries - 1} retries remaining)`); console.log(`retrying... (${maxRetries - 1} retries remaining)`);

View File

@@ -1,28 +1,18 @@
import { import { setFailed, setOutput } from '@actions/core';
paths, import { getOctokit } from '@actions/github';
parseConfig, import { GitHubReleaser, release, finalizeRelease, upload } from './github';
isTag, import { isTag, parseConfig, paths, unmatchedPatterns, uploadUrl } from './util';
unmatchedPatterns,
uploadUrl,
} from "./util";
import { release, upload, GitHubReleaser } from "./github";
import { getOctokit } from "@actions/github";
import { setFailed, setOutput } from "@actions/core";
import { env } from "process"; import { env } from 'process';
async function run() { async function run() {
try { try {
const config = parseConfig(env); const config = parseConfig(env);
if ( if (!config.input_tag_name && !isTag(config.github_ref) && !config.input_draft) {
!config.input_tag_name &&
!isTag(config.github_ref) &&
!config.input_draft
) {
throw new Error(`⚠️ GitHub Releases requires a tag`); throw new Error(`⚠️ GitHub Releases requires a tag`);
} }
if (config.input_files) { if (config.input_files) {
const patterns = unmatchedPatterns(config.input_files); const patterns = unmatchedPatterns(config.input_files, config.input_working_directory);
patterns.forEach((pattern) => { patterns.forEach((pattern) => {
if (config.input_fail_on_unmatched_files) { if (config.input_fail_on_unmatched_files) {
throw new Error(`⚠️ Pattern '${pattern}' does not match any files.`); throw new Error(`⚠️ Pattern '${pattern}' does not match any files.`);
@@ -44,9 +34,7 @@ async function run() {
//new oktokit( //new oktokit(
throttle: { throttle: {
onRateLimit: (retryAfter, options) => { onRateLimit: (retryAfter, options) => {
console.warn( console.warn(`Request quota exhausted for request ${options.method} ${options.url}`);
`Request quota exhausted for request ${options.method} ${options.url}`,
);
if (options.request.retryCount === 0) { if (options.request.retryCount === 0) {
// only retries once // only retries once
console.log(`Retrying after ${retryAfter} seconds!`); console.log(`Retrying after ${retryAfter} seconds!`);
@@ -55,56 +43,53 @@ async function run() {
}, },
onAbuseLimit: (retryAfter, options) => { onAbuseLimit: (retryAfter, options) => {
// does not retry, only logs a warning // does not retry, only logs a warning
console.warn( console.warn(`Abuse detected for request ${options.method} ${options.url}`);
`Abuse detected for request ${options.method} ${options.url}`,
);
}, },
}, },
}); });
//); //);
const rel = await release(config, new GitHubReleaser(gh)); const releaser = new GitHubReleaser(gh);
let rel = await release(config, releaser);
if (config.input_files && config.input_files.length > 0) { if (config.input_files && config.input_files.length > 0) {
const files = paths(config.input_files); const files = paths(config.input_files, config.input_working_directory);
if (files.length == 0) { if (files.length == 0) {
if (config.input_fail_on_unmatched_files) { if (config.input_fail_on_unmatched_files) {
throw new Error( throw new Error(`⚠️ ${config.input_files} does not include a valid file.`);
`⚠️ ${config.input_files} does not include a valid file.`,
);
} else { } else {
console.warn( console.warn(`🤔 ${config.input_files} does not include a valid file.`);
`🤔 ${config.input_files} does not include a valid file.`,
);
} }
} }
const currentAssets = rel.assets; const currentAssets = rel.assets;
const uploadFile = async (path) => { const uploadFile = async (path) => {
const json = await upload( const json = await upload(config, gh, uploadUrl(rel.upload_url), path, currentAssets);
config, if (json) {
gh, delete json.uploader;
uploadUrl(rel.upload_url), }
path,
currentAssets,
);
delete json.uploader;
return json; return json;
}; };
let assets; let results: (any | null)[];
if (!config.input_preserve_order) { if (!config.input_preserve_order) {
assets = await Promise.all(files.map(uploadFile)); results = await Promise.all(files.map(uploadFile));
} else { } else {
assets = []; results = [];
for (const path of files) { for (const path of files) {
assets.push(await uploadFile(path)); results.push(await uploadFile(path));
} }
} }
setOutput("assets", assets);
const assets = results.filter(Boolean);
setOutput('assets', assets);
} }
console.log('Finalizing release...');
rel = await finalizeRelease(config, releaser, rel);
console.log(`🎉 Release ready at ${rel.html_url}`); console.log(`🎉 Release ready at ${rel.html_url}`);
setOutput("url", rel.html_url); setOutput('url', rel.html_url);
setOutput("id", rel.id.toString()); setOutput('id', rel.id.toString());
setOutput("upload_url", rel.upload_url); setOutput('upload_url', rel.upload_url);
} catch (error) { } catch (error) {
setFailed(error.message); setFailed(error.message);
} }

View File

@@ -1,5 +1,6 @@
import * as glob from "glob"; import * as glob from 'glob';
import { statSync, readFileSync } from "fs"; import { statSync, readFileSync } from 'fs';
import * as pathLib from 'path';
export interface Config { export interface Config {
github_token: string; github_token: string;
@@ -12,6 +13,8 @@ export interface Config {
input_body?: string; input_body?: string;
input_body_path?: string; input_body_path?: string;
input_files?: string[]; input_files?: string[];
input_working_directory?: string;
input_overwrite_files?: boolean;
input_draft?: boolean; input_draft?: boolean;
input_preserve_order?: boolean; input_preserve_order?: boolean;
input_prerelease?: boolean; input_prerelease?: boolean;
@@ -20,11 +23,11 @@ export interface Config {
input_discussion_category_name?: string; input_discussion_category_name?: string;
input_generate_release_notes?: boolean; input_generate_release_notes?: boolean;
input_append_body?: boolean; input_append_body?: boolean;
input_make_latest: "true" | "false" | "legacy" | undefined; input_make_latest: 'true' | 'false' | 'legacy' | undefined;
} }
export const uploadUrl = (url: string): string => { export const uploadUrl = (url: string): string => {
const templateMarkerPos = url.indexOf("{"); const templateMarkerPos = url.indexOf('{');
if (templateMarkerPos > -1) { if (templateMarkerPos > -1) {
return url.substring(0, templateMarkerPos); return url.substring(0, templateMarkerPos);
} }
@@ -32,87 +35,123 @@ export const uploadUrl = (url: string): string => {
}; };
export const releaseBody = (config: Config): string | undefined => { export const releaseBody = (config: Config): string | undefined => {
return ( if (config.input_body_path) {
(config.input_body_path && try {
readFileSync(config.input_body_path).toString("utf8")) || const contents = readFileSync(config.input_body_path, 'utf8');
config.input_body return contents;
); } catch (err: any) {
console.warn(
`⚠️ Failed to read body_path "${config.input_body_path}" (${err?.code ?? 'ERR'}). Falling back to 'body' input.`,
);
}
}
return config.input_body;
}; };
type Env = { [key: string]: string | undefined }; type Env = { [key: string]: string | undefined };
const smartSplit = (input: string): string[] => {
const result: string[] = [];
let current = '';
let braceDepth = 0;
for (const ch of input) {
if (ch === '{') {
braceDepth++;
}
if (ch === '}') {
braceDepth--;
}
if (ch === ',' && braceDepth === 0) {
if (current.trim()) {
result.push(current.trim());
}
current = '';
} else {
current += ch;
}
}
if (current.trim()) {
result.push(current.trim());
}
return result;
};
export const parseInputFiles = (files: string): string[] => { export const parseInputFiles = (files: string): string[] => {
return files.split(/\r?\n/).reduce<string[]>( return files
(acc, line) => .split(/\r?\n/)
acc .flatMap((line) => smartSplit(line))
.concat(line.split(",")) .filter((pat) => pat.trim() !== '');
.filter((pat) => pat)
.map((pat) => pat.trim()),
[],
);
}; };
export const parseConfig = (env: Env): Config => { export const parseConfig = (env: Env): Config => {
return { return {
github_token: env.GITHUB_TOKEN || env.INPUT_TOKEN || "", github_token: env.GITHUB_TOKEN || env.INPUT_TOKEN || '',
github_ref: env.GITHUB_REF || "", github_ref: env.GITHUB_REF || '',
github_repository: env.INPUT_REPOSITORY || env.GITHUB_REPOSITORY || "", github_repository: env.INPUT_REPOSITORY || env.GITHUB_REPOSITORY || '',
input_name: env.INPUT_NAME, input_name: env.INPUT_NAME,
input_tag_name: env.INPUT_TAG_NAME?.trim(), input_tag_name: env.INPUT_TAG_NAME?.trim(),
input_body: env.INPUT_BODY, input_body: env.INPUT_BODY,
input_body_path: env.INPUT_BODY_PATH, input_body_path: env.INPUT_BODY_PATH,
input_files: parseInputFiles(env.INPUT_FILES || ""), input_files: parseInputFiles(env.INPUT_FILES || ''),
input_draft: env.INPUT_DRAFT ? env.INPUT_DRAFT === "true" : undefined, input_working_directory: env.INPUT_WORKING_DIRECTORY || undefined,
input_preserve_order: env.INPUT_PRESERVE_ORDER input_overwrite_files: env.INPUT_OVERWRITE_FILES
? env.INPUT_PRESERVE_ORDER == "true" ? env.INPUT_OVERWRITE_FILES == 'true'
: undefined, : undefined,
input_prerelease: env.INPUT_PRERELEASE input_draft: env.INPUT_DRAFT ? env.INPUT_DRAFT === 'true' : undefined,
? env.INPUT_PRERELEASE == "true" input_preserve_order: env.INPUT_PRESERVE_ORDER ? env.INPUT_PRESERVE_ORDER == 'true' : undefined,
: undefined, input_prerelease: env.INPUT_PRERELEASE ? env.INPUT_PRERELEASE == 'true' : undefined,
input_fail_on_unmatched_files: env.INPUT_FAIL_ON_UNMATCHED_FILES == "true", input_fail_on_unmatched_files: env.INPUT_FAIL_ON_UNMATCHED_FILES == 'true',
input_target_commitish: env.INPUT_TARGET_COMMITISH || undefined, input_target_commitish: env.INPUT_TARGET_COMMITISH || undefined,
input_discussion_category_name: input_discussion_category_name: env.INPUT_DISCUSSION_CATEGORY_NAME || undefined,
env.INPUT_DISCUSSION_CATEGORY_NAME || undefined, input_generate_release_notes: env.INPUT_GENERATE_RELEASE_NOTES == 'true',
input_generate_release_notes: env.INPUT_GENERATE_RELEASE_NOTES == "true", input_append_body: env.INPUT_APPEND_BODY == 'true',
input_append_body: env.INPUT_APPEND_BODY == "true",
input_make_latest: parseMakeLatest(env.INPUT_MAKE_LATEST), input_make_latest: parseMakeLatest(env.INPUT_MAKE_LATEST),
}; };
}; };
const parseMakeLatest = ( const parseMakeLatest = (value: string | undefined): 'true' | 'false' | 'legacy' | undefined => {
value: string | undefined, if (value === 'true' || value === 'false' || value === 'legacy') {
): "true" | "false" | "legacy" | undefined => {
if (value === "true" || value === "false" || value === "legacy") {
return value; return value;
} }
return undefined; return undefined;
}; };
export const paths = (patterns: string[]): string[] => { export const paths = (patterns: string[], cwd?: string): string[] => {
return patterns.reduce((acc: string[], pattern: string): string[] => { return patterns.reduce((acc: string[], pattern: string): string[] => {
return acc.concat( const matches = glob.sync(pattern, { cwd, dot: true, absolute: false });
glob const resolved = matches
.sync(pattern) .map((p) => (cwd ? pathLib.join(cwd, p) : p))
.filter((path) => statSync(path).isFile()) .filter((p) => {
.map((path) => path.replace(/\\/g, "/")), try {
); return statSync(p).isFile();
} catch {
return false;
}
});
return acc.concat(resolved);
}, []); }, []);
}; };
export const unmatchedPatterns = (patterns: string[]): string[] => { export const unmatchedPatterns = (patterns: string[], cwd?: string): string[] => {
return patterns.reduce((acc: string[], pattern: string): string[] => { return patterns.reduce((acc: string[], pattern: string): string[] => {
return acc.concat( const matches = glob.sync(pattern, { cwd, dot: true, absolute: false });
glob.sync(pattern).filter((path) => statSync(path).isFile()).length == 0 const files = matches.filter((p) => {
? [pattern] try {
: [], const full = cwd ? pathLib.join(cwd, p) : p;
); return statSync(full).isFile();
} catch {
return false;
}
});
return acc.concat(files.length == 0 ? [pattern] : []);
}, []); }, []);
}; };
export const isTag = (ref: string): boolean => { export const isTag = (ref: string): boolean => {
return ref.startsWith("refs/tags/"); return ref.startsWith('refs/tags/');
}; };
export const alignAssetName = (assetName: string): string => { export const alignAssetName = (assetName: string): string => {
return assetName.replace(/ /g, "."); return assetName.replace(/ /g, '.');
}; };