Compare commits

..

33 Commits

Author SHA1 Message Date
claude-ceo-assistant a10bdc03bd chore: remove upstream CI workflows (mirror inertization per internal#233) 2026-05-10 14:28:25 +00:00
claude-ceo-assistant 091978f914 chore: remove upstream CI workflows (mirror inertization per internal#233) 2026-05-10 14:28:24 +00:00
claude-ceo-assistant 3a213a9eee chore: remove upstream CI workflows (mirror inertization per internal#233) 2026-05-10 14:28:24 +00:00
claude-ceo-assistant b7bfa13b25 chore: remove upstream CI workflows (mirror inertization per internal#233) 2026-05-10 14:28:23 +00:00
dependabot[bot] c9aabb8372 build(deps-dev): bump yaml from 2.8.3 to 2.8.4 in the development-dependencies group (#371)
Bumps the development-dependencies group with 1 update:
[yaml](https://github.com/eemeli/yaml).

Updates `yaml` from 2.8.3 to 2.8.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/eemeli/yaml/releases">yaml's
releases</a>.</em></p>
<blockquote>
<h2>v2.8.4</h2>
<ul>
<li>Disable alias resolution with <code>maxAliasCount:0</code> (<a
href="https://redirect.github.com/eemeli/yaml/issues/677">#677</a>)</li>
<li>Handle invalid unicode escapes (e1a1a77)</li>
<li>Apply <code>minFractionDigits</code> only to decimal strings (<a
href="https://redirect.github.com/eemeli/yaml/issues/676">#676</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/eemeli/yaml/commit/ccdf7439587544f64223429498a1d9ec514eaac1"><code>ccdf743</code></a>
2.8.4</li>
<li><a
href="https://github.com/eemeli/yaml/commit/f625789dbd971c936ff66fe5c49e368062ae7b41"><code>f625789</code></a>
fix: Disable alias resolution with maxAliasCount:0 (<a
href="https://redirect.github.com/eemeli/yaml/issues/677">#677</a>)</li>
<li><a
href="https://github.com/eemeli/yaml/commit/e1a1a7735ff2e9717b87af36795bcd280f85f55d"><code>e1a1a77</code></a>
fix: Handle invalid unicode escapes</li>
<li><a
href="https://github.com/eemeli/yaml/commit/a163ea009c57ab9f1054ca39b24b6ef4c1e9fdbe"><code>a163ea0</code></a>
style: Satify Prettier</li>
<li><a
href="https://github.com/eemeli/yaml/commit/b2a5a6c615673056917aaa04d657802945e81425"><code>b2a5a6c</code></a>
fix: Apply minFractionDigits only to decimal strings (<a
href="https://redirect.github.com/eemeli/yaml/issues/676">#676</a>)</li>
<li><a
href="https://github.com/eemeli/yaml/commit/93c951b3478b4bb061d7b5227fd64f46d3f9df7f"><code>93c951b</code></a>
chore: Bump JSR version to v2.8.3 (<a
href="https://redirect.github.com/eemeli/yaml/issues/673">#673</a>)</li>
<li><a
href="https://github.com/eemeli/yaml/commit/0f226a3f9dfe31dca0b165e0601d233d9a642527"><code>0f226a3</code></a>
docs: Add trailingComma ToString option</li>
<li>See full diff in <a
href="https://github.com/eemeli/yaml/compare/v2.8.3...v2.8.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=yaml&package-manager=npm_and_yarn&previous-version=2.8.3&new-version=2.8.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-08 14:51:43 -07:00
dependabot[bot] e02e816e55 build(deps-dev): bump undici from 7.24.6 to 8.2.0 (#366)
Bumps [undici](https://github.com/nodejs/undici) from 7.24.6 to 8.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/nodejs/undici/releases">undici's
releases</a>.</em></p>
<blockquote>
<h2>v8.2.0</h2>
<h2>What's Changed</h2>
<ul>
<li>chore: use native addAbortListener by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5021">nodejs/undici#5021</a></li>
<li>fix: fix the logic for the UNDICI_NO_WASM_SIMD environment variable
by <a
href="https://github.com/ShenHongFei"><code>@​ShenHongFei</code></a> in
<a
href="https://redirect.github.com/nodejs/undici/pull/5026">nodejs/undici#5026</a></li>
<li>fix(http2): send body for non-expectsPayload methods with content by
<a href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5030">nodejs/undici#5030</a></li>
<li>fix(fetch): correct 'navigator' typo to 'navigate' in fetchFinale by
<a
href="https://github.com/deepview-autofix"><code>@​deepview-autofix</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5044">nodejs/undici#5044</a></li>
<li>fix(webidl): correct signed integer bounds in ConvertToInt by <a
href="https://github.com/deepview-autofix"><code>@​deepview-autofix</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5038">nodejs/undici#5038</a></li>
<li>fix(fetch): use || for CRLF check in multipart formdata-parser by <a
href="https://github.com/deepview-autofix"><code>@​deepview-autofix</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5049">nodejs/undici#5049</a></li>
<li>fix(websocket): correct argument order in WebSocketStream UTF-8
failure by <a
href="https://github.com/deepview-autofix"><code>@​deepview-autofix</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5050">nodejs/undici#5050</a></li>
<li>fix(pool): propagate useH2c to connector when connections &gt; 1 by
<a href="https://github.com/SAY-5"><code>@​SAY-5</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5031">nodejs/undici#5031</a></li>
<li>fix(cache): return immutable staleAt in milliseconds by <a
href="https://github.com/deepview-autofix"><code>@​deepview-autofix</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5048">nodejs/undici#5048</a></li>
<li>fix(socks5-proxy-agent): use per-origin pools to prevent
cross-origin routing by <a
href="https://github.com/deepview-autofix"><code>@​deepview-autofix</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5041">nodejs/undici#5041</a></li>
<li>fix(cache): evict oldest entries first in SqliteCacheStore prune by
<a
href="https://github.com/deepview-autofix"><code>@​deepview-autofix</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5039">nodejs/undici#5039</a></li>
<li>fix(socks5): correctly expand IPv6 '::' compressed notation by <a
href="https://github.com/deepview-autofix"><code>@​deepview-autofix</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5046">nodejs/undici#5046</a></li>
<li>Remove unused func and unnecessary shim by <a
href="https://github.com/tsctx"><code>@​tsctx</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5053">nodejs/undici#5053</a></li>
<li>fix: reject malformed content-length request headers by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5060">nodejs/undici#5060</a></li>
<li>fix(request): reject NaN highWaterMark during option validation by
<a href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5062">nodejs/undici#5062</a></li>
<li>docs: fix broken links in docsify sidebar by <a
href="https://github.com/maruthang"><code>@​maruthang</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5065">nodejs/undici#5065</a></li>
<li>fix(fetch): prefer filename* over filename in multipart form-data by
<a href="https://github.com/maruthang"><code>@​maruthang</code></a> in
<a
href="https://redirect.github.com/nodejs/undici/pull/5068">nodejs/undici#5068</a></li>
<li>fix(http2): reject websocket upgrades on non-200 responses by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5072">nodejs/undici#5072</a></li>
<li>feat: support username-only proxy authentication in ProxyAgent by <a
href="https://github.com/rossilor95"><code>@​rossilor95</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/4935">nodejs/undici#4935</a></li>
<li>build(deps): bump uWebSockets.js from v20.58.0 to v20.64.0 in
/benchmarks by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/nodejs/undici/pull/5083">nodejs/undici#5083</a></li>
<li>fix(client-h2): stop double-decrementing kOpenStreams on stream
timeout by <a href="https://github.com/SAY-5"><code>@​SAY-5</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5076">nodejs/undici#5076</a></li>
<li>fix(http2): reject upgrade streams closed before response headers by
<a href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5069">nodejs/undici#5069</a></li>
<li>fix(http2): allow GET and HEAD request bodies over h2 by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5058">nodejs/undici#5058</a></li>
<li>fix(cache): include query in cache key when opts.path is undefined
by <a href="https://github.com/maruthang"><code>@​maruthang</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5081">nodejs/undici#5081</a></li>
<li>fix: avoid premature cleanup of dispatcher in Agent by <a
href="https://github.com/bienzaaron"><code>@​bienzaaron</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5034">nodejs/undici#5034</a></li>
<li>fix(http2): record ping failures on the socket by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5075">nodejs/undici#5075</a></li>
<li>add undici security policy by <a
href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5056">nodejs/undici#5056</a></li>
<li>fix(mock): make filterCalls AND operator actually intersect results
by <a
href="https://github.com/deepview-autofix"><code>@​deepview-autofix</code></a>
in <a
href="https://redirect.github.com/nodejs/undici/pull/5045">nodejs/undici#5045</a></li>
<li>fix(socks5): enforce authenticated state before CONNECT by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5097">nodejs/undici#5097</a></li>
<li>fix(cache): skip expired sqlite vary entries during lookup by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5095">nodejs/undici#5095</a></li>
<li>fix: enforce maxCachedSessions in TLS session cache by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5102">nodejs/undici#5102</a></li>
<li>fix(socks5): encode embedded IPv4 tails in IPv6 literals correctly
by <a href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5099">nodejs/undici#5099</a></li>
<li>fix: handle invalid HTTP/2 connection headers (<a
href="https://redirect.github.com/nodejs/undici/issues/4356">#4356</a>)
by <a href="https://github.com/mcollina"><code>@​mcollina</code></a> in
<a
href="https://redirect.github.com/nodejs/undici/pull/5101">nodejs/undici#5101</a></li>
<li>fix(interceptor): add throwOnMaxRedirect to types and interceptor
opts by <a
href="https://github.com/maruthang"><code>@​maruthang</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5066">nodejs/undici#5066</a></li>
<li>fix(websocket): avoid double-closing canceled stream readers by <a
href="https://github.com/colinaaa"><code>@​colinaaa</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5105">nodejs/undici#5105</a></li>
<li>fix(cache): persist vary when updating sqlite cache entries by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5109">nodejs/undici#5109</a></li>
<li>refactor(h1): track HEAD keep-alive override as boolean by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5110">nodejs/undici#5110</a></li>
<li>client: cache llhttp wasm buffer view by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5115">nodejs/undici#5115</a></li>
<li>deps: update llhttp to 9.3.1 by <a
href="https://github.com/mcollina"><code>@​mcollina</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5113">nodejs/undici#5113</a></li>
<li>fix(http2): preserve accepted streams after GOAWAY by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5090">nodejs/undici#5090</a></li>
<li>fix: reuse parser WeakRef for timeout callbacks by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5125">nodejs/undici#5125</a></li>
<li>fix: stop buffering data after SOCKS5 connect by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5118">nodejs/undici#5118</a></li>
<li>perf(http2): avoid response header reserialization by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5085">nodejs/undici#5085</a></li>
<li>fix(cache): enforce sqlite maxCount after insert by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5112">nodejs/undici#5112</a></li>
<li>perf: reduce EventSourceStream parser allocations by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5032">nodejs/undici#5032</a></li>
<li>types(dispatcher): use OutgoingHttpHeaders for request headers by <a
href="https://github.com/maruthang"><code>@​maruthang</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5067">nodejs/undici#5067</a></li>
<li>cleanup: delete redundant .gitkeep file by <a
href="https://github.com/shivarm"><code>@​shivarm</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5133">nodejs/undici#5133</a></li>
<li>fix(http2): respect peer max concurrent streams by <a
href="https://github.com/trivikr"><code>@​trivikr</code></a> in <a
href="https://redirect.github.com/nodejs/undici/pull/5135">nodejs/undici#5135</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/nodejs/undici/commit/bf684f7de01616708a33a5d1c092177622394442"><code>bf684f7</code></a>
Bumped v8.2.0 (<a
href="https://redirect.github.com/nodejs/undici/issues/5152">#5152</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/0ca054aa8524ed52709ebf15e94e187f4a18ebcf"><code>0ca054a</code></a>
fix: replace stale pool clients under connection limit (<a
href="https://redirect.github.com/nodejs/undici/issues/5145">#5145</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/7af90e9721f882bb5fc97606e3f95217ca849994"><code>7af90e9</code></a>
perf: avoid redundant scans in BalancedPool dispatcher selection (<a
href="https://redirect.github.com/nodejs/undici/issues/5146">#5146</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/abb9d06ba7b882d336129898f9e89176f1c8f1c3"><code>abb9d06</code></a>
fix: validate H2CClient maxConcurrentStreams option (<a
href="https://redirect.github.com/nodejs/undici/issues/5143">#5143</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/72a75913138bc6197bf0620a1291f7c2e764c430"><code>72a7591</code></a>
perf(http2): avoid cloning headers when removing status (<a
href="https://redirect.github.com/nodejs/undici/issues/5127">#5127</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/96fd5e9da9b19a4d49eb9e67534f73f4cfa1c677"><code>96fd5e9</code></a>
fix(cache): allow streamed entries at maxEntrySize limit (<a
href="https://redirect.github.com/nodejs/undici/issues/5129">#5129</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/f41e53f112659ec37b6fa8db4e4f39388b9e6ca6"><code>f41e53f</code></a>
perf: use byteLength property for binary body chunks (<a
href="https://redirect.github.com/nodejs/undici/issues/5126">#5126</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/bec49615931e3df3df4f75956b35c3f7f1fd05ca"><code>bec4961</code></a>
chore(deps): add lockfile (<a
href="https://redirect.github.com/nodejs/undici/issues/5139">#5139</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/86f1242cfc86364fe5c30d7e0fc1deec8d52954b"><code>86f1242</code></a>
perf(http2): reduce writeH2 per-request callback allocations (<a
href="https://redirect.github.com/nodejs/undici/issues/5138">#5138</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/cad3f70a86b95966a3e368f9f7118377858b8c3e"><code>cad3f70</code></a>
perf(client): parse h1 content-length statelessly (<a
href="https://redirect.github.com/nodejs/undici/issues/5124">#5124</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/nodejs/undici/compare/v7.24.6...v8.2.0">compare
view</a></li>
</ul>
</details>
<br />

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Parker Brown <17183625+parkerbxyz@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-08 14:40:33 -07:00
dependabot[bot] 8d835bfd37 build(deps-dev): bump esbuild from 0.27.4 to 0.28.0 in the development-dependencies group (#365)
Bumps the development-dependencies group with 1 update:
[esbuild](https://github.com/evanw/esbuild).

Updates `esbuild` from 0.27.4 to 0.28.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/evanw/esbuild/releases">esbuild's
releases</a>.</em></p>
<blockquote>
<h2>v0.28.0</h2>
<ul>
<li>
<p>Add support for <code>with { type: 'text' }</code> imports (<a
href="https://redirect.github.com/evanw/esbuild/issues/4435">#4435</a>)</p>
<p>The <a href="https://github.com/tc39/proposal-import-text">import
text</a> proposal has reached stage 3 in the TC39 process, which means
that it's recommended for implementation. It has also already been
implemented by <a
href="https://docs.deno.com/examples/importing_text/">Deno</a> and <a
href="https://bun.com/docs/guides/runtime/import-html">Bun</a>. So with
this release, esbuild also adds support for it. This behaves exactly the
same as esbuild's existing <a
href="https://esbuild.github.io/content-types/#text"><code>text</code>
loader</a>. Here's an example:</p>
<pre lang="js"><code>import string from './example.txt' with { type:
'text' }
console.log(string)
</code></pre>
</li>
<li>
<p>Add integrity checks to fallback download path (<a
href="https://redirect.github.com/evanw/esbuild/issues/4343">#4343</a>)</p>
<p>Installing esbuild via npm is somewhat complicated with several
different edge cases (see <a
href="https://esbuild.github.io/getting-started/#additional-npm-flags">esbuild's
documentation</a> for details). If the regular installation of esbuild's
platform-specific package fails, esbuild's install script attempts to
download the platform-specific package itself (first with the
<code>npm</code> command, and then with a HTTP request to
<code>registry.npmjs.org</code> as a last resort).</p>
<p>This last resort path previously didn't have any integrity checks.
With this release, esbuild will now verify that the hash of the
downloaded binary matches the expected hash for the current release.
This means the hashes for all of esbuild's platform-specific binary
packages will now be embedded in the top-level <code>esbuild</code>
package. Hopefully this should work without any problems. But just in
case, this change is being done as a breaking change release.</p>
</li>
<li>
<p>Update the Go compiler from 1.25.7 to 1.26.1</p>
<p>This upgrade should not affect anything. However, there have been
some significant internal changes to the Go compiler, so esbuild could
potentially behave differently in certain edge cases:</p>
<ul>
<li>It now uses the <a
href="https://go.dev/doc/go1.26#new-garbage-collector">new garbage
collector</a> that comes with Go 1.26.</li>
<li>The Go compiler is now more aggressive with allocating memory on the
stack.</li>
<li>The executable format that the Go linker uses has undergone several
changes.</li>
<li>The WebAssembly build now unconditionally makes use of the sign
extension and non-trapping floating-point to integer conversion
instructions.</li>
</ul>
<p>You can read the <a href="https://go.dev/doc/go1.26">Go 1.26 release
notes</a> for more information.</p>
</li>
</ul>
<h2>v0.27.7</h2>
<ul>
<li>
<p>Fix lowering of define semantics for TypeScript parameter properties
(<a
href="https://redirect.github.com/evanw/esbuild/issues/4421">#4421</a>)</p>
<p>The previous release incorrectly generated class fields for
TypeScript parameter properties even when the configured target
environment does not support class fields. With this release, the
generated class fields will now be correctly lowered in this case:</p>
<pre lang="ts"><code>// Original code
class Foo {
  constructor(public x = 1) {}
  y = 2
}
<p>// Old output (with --loader=ts --target=es2021)<br />
class Foo {<br />
constructor(x = 1) {<br />
this.x = x;<br />
__publicField(this, &quot;y&quot;, 2);<br />
}<br />
x;<br />
}</p>
<p>// New output (with --loader=ts --target=es2021)<br />
class Foo {<br />
</code></pre></p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/evanw/esbuild/blob/main/CHANGELOG.md">esbuild's
changelog</a>.</em></p>
<blockquote>
<h2>0.28.0</h2>
<ul>
<li>
<p>Add support for <code>with { type: 'text' }</code> imports (<a
href="https://redirect.github.com/evanw/esbuild/issues/4435">#4435</a>)</p>
<p>The <a href="https://github.com/tc39/proposal-import-text">import
text</a> proposal has reached stage 3 in the TC39 process, which means
that it's recommended for implementation. It has also already been
implemented by <a
href="https://docs.deno.com/examples/importing_text/">Deno</a> and <a
href="https://bun.com/docs/guides/runtime/import-html">Bun</a>. So with
this release, esbuild also adds support for it. This behaves exactly the
same as esbuild's existing <a
href="https://esbuild.github.io/content-types/#text"><code>text</code>
loader</a>. Here's an example:</p>
<pre lang="js"><code>import string from './example.txt' with { type:
'text' }
console.log(string)
</code></pre>
</li>
<li>
<p>Add integrity checks to fallback download path (<a
href="https://redirect.github.com/evanw/esbuild/issues/4343">#4343</a>)</p>
<p>Installing esbuild via npm is somewhat complicated with several
different edge cases (see <a
href="https://esbuild.github.io/getting-started/#additional-npm-flags">esbuild's
documentation</a> for details). If the regular installation of esbuild's
platform-specific package fails, esbuild's install script attempts to
download the platform-specific package itself (first with the
<code>npm</code> command, and then with a HTTP request to
<code>registry.npmjs.org</code> as a last resort).</p>
<p>This last resort path previously didn't have any integrity checks.
With this release, esbuild will now verify that the hash of the
downloaded binary matches the expected hash for the current release.
This means the hashes for all of esbuild's platform-specific binary
packages will now be embedded in the top-level <code>esbuild</code>
package. Hopefully this should work without any problems. But just in
case, this change is being done as a breaking change release.</p>
</li>
<li>
<p>Update the Go compiler from 1.25.7 to 1.26.1</p>
<p>This upgrade should not affect anything. However, there have been
some significant internal changes to the Go compiler, so esbuild could
potentially behave differently in certain edge cases:</p>
<ul>
<li>It now uses the <a
href="https://go.dev/doc/go1.26#new-garbage-collector">new garbage
collector</a> that comes with Go 1.26.</li>
<li>The Go compiler is now more aggressive with allocating memory on the
stack.</li>
<li>The executable format that the Go linker uses has undergone several
changes.</li>
<li>The WebAssembly build now unconditionally makes use of the sign
extension and non-trapping floating-point to integer conversion
instructions.</li>
</ul>
<p>You can read the <a href="https://go.dev/doc/go1.26">Go 1.26 release
notes</a> for more information.</p>
</li>
</ul>
<h2>0.27.7</h2>
<ul>
<li>
<p>Fix lowering of define semantics for TypeScript parameter properties
(<a
href="https://redirect.github.com/evanw/esbuild/issues/4421">#4421</a>)</p>
<p>The previous release incorrectly generated class fields for
TypeScript parameter properties even when the configured target
environment does not support class fields. With this release, the
generated class fields will now be correctly lowered in this case:</p>
<pre lang="ts"><code>// Original code
class Foo {
  constructor(public x = 1) {}
  y = 2
}
<p>// Old output (with --loader=ts --target=es2021)<br />
class Foo {<br />
constructor(x = 1) {<br />
this.x = x;<br />
__publicField(this, &quot;y&quot;, 2);<br />
}<br />
x;<br />
}</p>
<p></code></pre></p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/evanw/esbuild/commit/6a794dff68e6a43539f6da671e3080efdf11ca70"><code>6a794df</code></a>
publish 0.28.0 to npm</li>
<li><a
href="https://github.com/evanw/esbuild/commit/64ee0ea63b2ff303caafc9610c388dc72c882c23"><code>64ee0ea</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4435">#4435</a>:
support <code>with { type: text }</code> imports</li>
<li><a
href="https://github.com/evanw/esbuild/commit/ef65aeeaacdb71eade186f888975b1de89574314"><code>ef65aee</code></a>
fix sort order in <code>snapshots_packagejson.txt</code></li>
<li><a
href="https://github.com/evanw/esbuild/commit/1a26a8ecbc39aaf1379c524a0274a08fbcbed655"><code>1a26a8e</code></a>
try to fix <code>test-old-ts</code>, also shuffle CI tasks</li>
<li><a
href="https://github.com/evanw/esbuild/commit/556ce6c1fc00d7c0917fbfada01ed8e5251bc510"><code>556ce6c</code></a>
use <code>''</code> instead of <code>null</code> to omit build
hashes</li>
<li><a
href="https://github.com/evanw/esbuild/commit/8e675a81a473ea69a46a69792f1386bb110dd877"><code>8e675a8</code></a>
ci: allow missing binary hashes for tests</li>
<li><a
href="https://github.com/evanw/esbuild/commit/7067763b904fe8a522fa840a4a48c5fbd4c395e0"><code>7067763</code></a>
Reapply &quot;update go 1.25.7 =&gt; 1.26.1&quot;</li>
<li><a
href="https://github.com/evanw/esbuild/commit/39473a952ab3b450d0578b698a8b8d2a02332e0d"><code>39473a9</code></a>
fix <a
href="https://redirect.github.com/evanw/esbuild/issues/4343">#4343</a>:
integrity check for binary download</li>
<li><a
href="https://github.com/evanw/esbuild/commit/2025c9ff6ab15ba6b0f9d074fd732250cc46e4a3"><code>2025c9f</code></a>
publish 0.27.7 to npm</li>
<li><a
href="https://github.com/evanw/esbuild/commit/c6b586e4904f47e8d5f783a2813660c13e2672e7"><code>c6b586e</code></a>
fix typo in <code>Makefile</code> for
<code>@esbuild/win32-x64</code></li>
<li>Additional commits viewable in <a
href="https://github.com/evanw/esbuild/compare/v0.27.4...v0.28.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=esbuild&package-manager=npm_and_yarn&previous-version=0.27.4&new-version=0.28.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-08 12:30:30 -07:00
Stefan 952a2a7073 feat: add support for enterprise-level GitHub Apps (#263)
This pull request adds support for generating GitHub App installation
tokens for enterprise-level installations.

### What changed

- Added a new `enterprise` input to `action.yml`.
- Wired `enterprise` through `main.js` and `lib/main.js`.
- Added validation so `enterprise` cannot be combined with `owner` or
`repositories`.
- Implemented enterprise installation lookup using the direct GitHub API
route `GET /enterprises/{enterprise}/installation`, then used the
returned installation ID to mint an installation token through
`@octokit/auth-app`.
- Updated `README.md` with enterprise installation usage and input
documentation.
- Updated `dist/main.cjs` for the bundled action.
- Shared token creation retry behavior across repository, owner, and
enterprise paths so server errors and transient network errors are
retried, while client errors fail immediately.

### Tests

Added focused test coverage for:

- enterprise token creation
- enterprise token creation with explicit permissions
- enterprise installation not found
- mutual exclusivity with `owner`
- mutual exclusivity with `repositories`
- owner installation client errors are not retried
- transient network errors are retried during token creation

### Notes

- This keeps the existing repository-scoped token behavior unchanged.
- Owner, repository, and enterprise token creation now share the same
retry policy: server errors and recognized transient network errors are
retried, while client errors fail immediately. This intentionally fixes
the previous owner-path behavior that retried client errors.

Refs:
-
https://github.blog/changelog/2025-07-01-enterprise-level-access-for-github-apps-and-installation-automation-apis/
-
https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-an-enterprise-installation-for-the-authenticated-app

---------

Co-authored-by: Parker Brown <17183625+parkerbxyz@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-08 11:56:11 -07:00
dependabot[bot] 43e5c345bf fix(deps): bump @actions/core from 3.0.0 to 3.0.1 in the production-dependencies group (#364)
Bumps the production-dependencies group with 1 update:
[@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core).

Updates `@actions/core` from 3.0.0 to 3.0.1
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md"><code>@​actions/core</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>3.0.1</h2>
<ul>
<li>Bump <code>undici</code> from <code>6.23.0</code> to
<code>6.24.1</code> <a
href="https://redirect.github.com/actions/toolkit/pull/2348">#2348</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/actions/toolkit/commits/HEAD/packages/core">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@actions/core&package-manager=npm_and_yarn&previous-version=3.0.0&new-version=3.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-07 19:09:31 -07:00
Parker Brown 6dbbbb1419 ci: use existing release tag format (#369)
## Summary

This updates the release-please manifest configuration to use the
repository's existing `vX.Y.Z` tag format. Without this setting,
manifest mode looks for component-prefixed tags like
`create-github-app-token-v3.1.1`, which do not exist in this repository
and can cause release-please to include already-released commits in a
new release PR.

## Changes

- Sets `include-component-in-tag` to `false` for stable releases.
- Sets `include-component-in-tag` to `false` for beta prereleases.

This should make release-please use tags like `v3.1.1` as the previous
release boundary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-07 18:58:55 -07:00
Parker Brown 6be28c6ca7 ci: migrate releases to release-please (#367)
## Summary

This updates release automation so the repository can enforce signed
commits. Release version and changelog updates now happen through
release-please PRs instead of direct workflow commits, which allows them
to use the repository's normal signed-commit path.

## Changes

- Replaces semantic-release configuration with release-please manifest
configuration for stable releases and beta prereleases.
- Builds committed `dist/**` assets from the trusted release workflow
after release-please creates or updates a release PR, then commits those
assets back to the release PR branch.
- Keeps floating major version tags, such as `v3`, updated with the
GitHub API after a release is created.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-07 18:13:10 -07:00
Josh Johanning 7989d206df docs: use APP_ prefix instead of reserved GITHUB_ prefix (#363)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-04-21 09:56:11 -07:00
semantic-release-bot 1b10c78c78 build(release): 3.1.1 [skip ci]
## [3.1.1](https://github.com/actions/create-github-app-token/compare/v3.1.0...v3.1.1) (2026-04-11)

### Bug Fixes

* improve error message when app identifier is empty ([#362](https://github.com/actions/create-github-app-token/issues/362)) ([07e2b76](https://github.com/actions/create-github-app-token/commit/07e2b760664f080c40eec4eacf7477256582db36)), closes [#249](https://github.com/actions/create-github-app-token/issues/249)
2026-04-11 06:43:26 +00:00
Parker Brown 07e2b76066 fix: improve error message when app identifier is empty (#362)
When `client-id` (or the deprecated `app-id`) resolves to an empty
string, for example because a secret or variable is not available in the
workflow context, the error message from `@octokit/auth-app` is not very
helpful:

```
[@octokit/auth-app] appId option is required
```

A validation check was added recently to catch this earlier, but its
message could be more informative:

```
Either 'client-id' or 'app-id' input must be set
```

This updates the message to clarify that the value resolved to empty and
nudges users toward checking their secret or variable availability:

```
The 'client-id' input must be set to a non-empty string. If using a secret or variable, ensure it is available in this workflow context.
```

Closes #249

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-10 23:42:53 -07:00
Parker Brown ea0121618b ci: remove publish-immutable-action workflow (#361)
The `publish-immutable-action` workflow uses `actions/publish-immutable-action@v0.0.4`. The Immutable Actions Publishing feature (OCI-based) has been paused with no GA timeline. This removes the workflow; it can be re-added when the feature becomes generally available.

To fully address #352, the repository-level **immutable releases** setting has also be enabled. This [feature](https://github.blog/changelog/2025-10-28-immutable-releases-are-now-generally-available/) locks release tags and assets after publication, directly preventing the class of attack described in [GHSA-mrrh-fwg8-r2c3](https://github.com/advisories/GHSA-mrrh-fwg8-r2c3).

The existing release workflow is compatible with immutable releases. Build artifacts are committed via `@semantic-release/git` before the tag is created, and major version tags (`v3`, etc.) are plain git tags (not releases) so they remain updatable.
2026-04-10 23:01:59 -07:00
semantic-release-bot 7bd0371149 build(release): 3.1.0 [skip ci]
# [3.1.0](https://github.com/actions/create-github-app-token/compare/v3.0.0...v3.1.0) (2026-04-11)

### Bug Fixes

* **deps:** bump p-retry from 7.1.1 to 8.0.0 ([#357](https://github.com/actions/create-github-app-token/issues/357)) ([3bbe07d](https://github.com/actions/create-github-app-token/commit/3bbe07d928e2d6c30bf3e37c6b89edbc4045facf))

### Features

* add `client-id` input and deprecate `app-id` ([#353](https://github.com/actions/create-github-app-token/issues/353)) ([e6bd4e6](https://github.com/actions/create-github-app-token/commit/e6bd4e6970172bed9fe138b2eaf4cbffa4cca8f9))
* update permission inputs ([#358](https://github.com/actions/create-github-app-token/issues/358)) ([076e948](https://github.com/actions/create-github-app-token/commit/076e9480ca6e9633bff412d05eff0fc2f1e7d2be))
2026-04-11 00:39:23 +00:00
Copilot e6bd4e6970 feat: add client-id input and deprecate app-id (#353)
GitHub now recommends using a GitHub App's Client ID for authentication.
This PR adds a first-class `client-id` input, keeps `app-id` available
for compatibility, and makes the migration path explicit in both runtime
behavior and documentation.

### Action inputs

- Adds a new `client-id` input
- Removes `required` from `app-id`
- Marks `app-id` as deprecated in `action.yml`

### Runtime behavior

- Updates input parsing to prefer `client-id`
- Falls back to `app-id` for existing workflows
- Adds a clear error when neither `client-id` nor `app-id` is provided

### Docs

- Updates the README to recommend `client-id`
- Switches usage examples to `client-id`
- Documents that `app-id` is deprecated and that `client-id` takes
precedence if both are set

### Regression coverage

- Adds a focused test proving a client-ID-shaped value works through the
new `client-id` input
- Adds coverage for the missing-ID validation path
- Updates snapshots to lock in the new metadata and runtime behavior

### Resulting usage

Users can migrate to the new input name directly:

```yaml
- uses: actions/create-github-app-token@v3
  with:
    client-id: ${{ vars.GITHUB_APP_CLIENT_ID }}
    private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }}
```

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: parkerbxyz <17183625+parkerbxyz@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 17:36:03 -07:00
dependabot[bot] 076e9480ca feat: update permission inputs (#358)
Bumps [@octokit/openapi](https://github.com/octokit/openapi) from 21.0.0
to 22.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/octokit/openapi/releases"><code>@​octokit/openapi</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v22.0.0</h2>
<h1><a
href="https://github.com/octokit/openapi/compare/v21.0.0...v22.0.0">22.0.0</a>
(2025-12-09)</h1>
<h3>Features</h3>
<ul>
<li>drop projects-classic endpoints, add GitHub API endpoints: cache
limits (retention &amp; storage) for repos/orgs/enterprises, billing
budgets &amp; usage, artifacts deployment metadata, and projectsV2
drafts &amp; fields (<a
href="https://redirect.github.com/octokit/openapi/issues/518">#518</a>)
(<a
href="https://github.com/octokit/openapi/commit/b0c44a4ab1b07a5524890cef1e8321cfc430bebb">b0c44a4</a>)</li>
</ul>
<h3>BREAKING CHANGES</h3>
<ul>
<li>Removed <code>/orgs/{org}/projects</code></li>
<li>Removed <code>/orgs/{org}/settings/billing/actions</code></li>
<li>Removed <code>/orgs/{org}/settings/billing/packages</code></li>
<li>Removed
<code>/orgs/{org}/settings/billing/shared-storage</code></li>
<li>Removed <code>/orgs/{org}/teams/{team_slug}/projects</code></li>
<li>Removed
<code>/orgs/{org}/teams/{team_slug}/projects/{project_id}</code></li>
<li>Removed <code>/projects/columns/{column_id}</code></li>
<li>Removed <code>/projects/columns/{column_id}/moves</code></li>
<li>Removed <code>/projects/{project_id}</code></li>
<li>Removed <code>/projects/{project_id}/collaborators</code></li>
<li>Removed
<code>/projects/{project_id}/collaborators/{username}</code></li>
<li>Removed
<code>/projects/{project_id}/collaborators/{username}/permission</code></li>
<li>Removed <code>/repos/{owner}/{repo}/projects</code></li>
<li>Removed <code>/teams/{team_id}/projects</code></li>
<li>Removed <code>/teams/{team_id}/projects/{project_id}</code></li>
<li>Removed <code>/user/projects</code></li>
<li>Removed <code>/users/{username}/projects</code></li>
<li>Removed <code>/users/{username}/settings/billing/actions</code></li>
<li>Removed
<code>/users/{username}/settings/billing/packages</code></li>
<li>Removed
<code>/users/{username}/settings/billing/shared-storage</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/octokit/openapi/commit/6f63b86ab7d2057cb62574681918a34b3d43f66b"><code>6f63b86</code></a>
build(deps): lock file maintenance (<a
href="https://redirect.github.com/octokit/openapi/issues/520">#520</a>)</li>
<li><a
href="https://github.com/octokit/openapi/commit/b0c44a4ab1b07a5524890cef1e8321cfc430bebb"><code>b0c44a4</code></a>
feat: drop projects-classic endpoints, add GitHub API endpoints: cache
limits...</li>
<li><a
href="https://github.com/octokit/openapi/commit/a8043eb055618a1a9a779b6807bba796d9664604"><code>a8043eb</code></a>
ci(action): update actions/checkout action to v6 (<a
href="https://redirect.github.com/octokit/openapi/issues/519">#519</a>)</li>
<li><a
href="https://github.com/octokit/openapi/commit/af315cd293aac70c81874623769bdb091da614be"><code>af315cd</code></a>
build(deps): lock file maintenance (<a
href="https://redirect.github.com/octokit/openapi/issues/514">#514</a>)</li>
<li><a
href="https://github.com/octokit/openapi/commit/170f3965b9432f4171117aacb6b88339d5c2a937"><code>170f396</code></a>
build(deps-dev): bump js-yaml from 4.1.0 to 4.1.1 (<a
href="https://redirect.github.com/octokit/openapi/issues/516">#516</a>)</li>
<li><a
href="https://github.com/octokit/openapi/commit/077a1b94a2e77bf56fa07ed8dc112055958b97ab"><code>077a1b9</code></a>
build(deps): lock file maintenance (<a
href="https://redirect.github.com/octokit/openapi/issues/508">#508</a>)</li>
<li><a
href="https://github.com/octokit/openapi/commit/cfca956d308018be25c1405b52c6a4b8c924bdd6"><code>cfca956</code></a>
ci(action): update github/codeql-action action to v4 (<a
href="https://redirect.github.com/octokit/openapi/issues/510">#510</a>)</li>
<li><a
href="https://github.com/octokit/openapi/commit/f15da93d54d4de07c1025b0984c5613a8ddd8acd"><code>f15da93</code></a>
ci(action): update peter-evans/create-or-update-comment action to v5 (<a
href="https://redirect.github.com/octokit/openapi/issues/509">#509</a>)</li>
<li><a
href="https://github.com/octokit/openapi/commit/64bef332f5e1b11ead74082d8aaf0376409de9d0"><code>64bef33</code></a>
chore(deps): update dependency map-obj to v6 (<a
href="https://redirect.github.com/octokit/openapi/issues/507">#507</a>)</li>
<li><a
href="https://github.com/octokit/openapi/commit/4e8e223e564f467a455d7f39de15a0fb233f189e"><code>4e8e223</code></a>
chore(deps): update dependency github-enterprise-server-versions to v3
(<a
href="https://redirect.github.com/octokit/openapi/issues/511">#511</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/octokit/openapi/compare/v21.0.0...v22.0.0">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by [GitHub Actions](<a
href="https://www.npmjs.com/~GitHub">https://www.npmjs.com/~GitHub</a>
Actions), a new releaser for <code>@​octokit/openapi</code> since your
current version.</p>
</details>
<br />

Resolves https://github.com/github/gh-aw/issues/18921.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 17:35:53 -07:00
dependabot[bot] 3bbe07d928 fix(deps): bump p-retry from 7.1.1 to 8.0.0 (#357)
Bumps [p-retry](https://github.com/sindresorhus/p-retry) from 7.1.1 to
8.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sindresorhus/p-retry/releases">p-retry's
releases</a>.</em></p>
<blockquote>
<h2>v8.0.0</h2>
<h3>Breaking</h3>
<ul>
<li>Require Node.js 22  85cdece</li>
<li>Change retry callback order  616306e
<ul>
<li>Callback order changed from <code>onFailedAttempt</code> →
<code>shouldConsumeRetry</code> → <code>shouldRetry</code> to
<code>shouldConsumeRetry</code> → <code>onFailedAttempt</code> →
<code>shouldRetry</code>.</li>
<li>Consumption decisions are now made before failure notifications and
retry decisions.</li>
</ul>
</li>
</ul>
<h3>Improvements</h3>
<ul>
<li>Add <a
href="https://github.com/sindresorhus/p-retry#retrydelay"><code>retryDelay</code></a>
to <code>onFailedAttempt</code> context (<a
href="https://redirect.github.com/sindresorhus/p-retry/issues/66">#66</a>)
96cce98</li>
<li>Fix <code>TypeError</code> retry handling  f011d2e</li>
<li>Harden retry timing and callback validation  9d47b60</li>
</ul>
<hr />
<p><a
href="https://github.com/sindresorhus/p-retry/compare/v7.1.1...v8.0.0">https://github.com/sindresorhus/p-retry/compare/v7.1.1...v8.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sindresorhus/p-retry/commit/35681f6c70f8ca2bdcb9542281147679184269fa"><code>35681f6</code></a>
8.0.0</li>
<li><a
href="https://github.com/sindresorhus/p-retry/commit/9d47b60e2c8fb324d35cce1987b8591464de24fe"><code>9d47b60</code></a>
Harden retry timing and callback validation</li>
<li><a
href="https://github.com/sindresorhus/p-retry/commit/f011d2e837166f1bea3e739e59754caed4a2dde6"><code>f011d2e</code></a>
Fix <code>TypeError</code> retry handling</li>
<li><a
href="https://github.com/sindresorhus/p-retry/commit/85cdece1c48f3c3fe09d995d86bf59c0d0e4b44f"><code>85cdece</code></a>
Require Node.js 22</li>
<li><a
href="https://github.com/sindresorhus/p-retry/commit/616306ee84f828ffa17f0f02ae4e589815d4f767"><code>616306e</code></a>
Change retry callback order</li>
<li><a
href="https://github.com/sindresorhus/p-retry/commit/96cce98ea2f95c78a4abd780498b2d6af32ac7a4"><code>96cce98</code></a>
Add <code>retryDelay</code> to <code>onFailedAttempt</code> context</li>
<li>See full diff in <a
href="https://github.com/sindresorhus/p-retry/compare/v7.1.1...v8.0.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=p-retry&package-manager=npm_and_yarn&previous-version=7.1.1&new-version=8.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 17:35:43 -07:00
dependabot[bot] 28a99e369c build(deps-dev): bump c8 from 10.1.3 to 11.0.0
Bumps [c8](https://github.com/bcoe/c8) from 10.1.3 to 11.0.0.
- [Release notes](https://github.com/bcoe/c8/releases)
- [Changelog](https://github.com/bcoe/c8/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bcoe/c8/compare/v10.1.3...v11.0.0)

---
updated-dependencies:
- dependency-name: c8
  dependency-version: 11.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 15:51:15 -07:00
dependabot[bot] 4df50600ef build(deps-dev): bump open-cli from 8.0.0 to 9.0.0
Bumps [open-cli](https://github.com/sindresorhus/open-cli) from 8.0.0 to 9.0.0.
- [Release notes](https://github.com/sindresorhus/open-cli/releases)
- [Commits](https://github.com/sindresorhus/open-cli/compare/v8.0.0...v9.0.0)

---
updated-dependencies:
- dependency-name: open-cli
  dependency-version: 9.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 15:44:18 -07:00
dependabot[bot] 4843c538d9 build(deps-dev): bump the development-dependencies group with 3 updates
Bumps the development-dependencies group with 3 updates: [esbuild](https://github.com/evanw/esbuild), [undici](https://github.com/nodejs/undici) and [yaml](https://github.com/eemeli/yaml).


Updates `esbuild` from 0.27.3 to 0.27.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.3...v0.27.4)

Updates `undici` from 7.24.1 to 7.24.6
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v7.24.1...v7.24.6)

Updates `yaml` from 2.8.2 to 2.8.3
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v2.8.2...v2.8.3)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.27.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: undici
  dependency-version: 7.24.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: yaml
  dependency-version: 2.8.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 15:42:14 -07:00
semantic-release-bot f8d387b68d build(release): 3.0.0 [skip ci]
# [3.0.0](https://github.com/actions/create-github-app-token/compare/v2.2.2...v3.0.0) (2026-03-14)

* feat!: node 24 support ([#275](https://github.com/actions/create-github-app-token/issues/275)) ([2e564a0](https://github.com/actions/create-github-app-token/commit/2e564a0bb8e7cc2b907b2401a2afe177882d4325))
* fix!: require `NODE_USE_ENV_PROXY` for proxy support ([#342](https://github.com/actions/create-github-app-token/issues/342)) ([4451bcb](https://github.com/actions/create-github-app-token/commit/4451bcbc139f8124b0bf04f968ea2586b17df458))

### Bug Fixes

* remove custom proxy handling ([#143](https://github.com/actions/create-github-app-token/issues/143)) ([dce0ab0](https://github.com/actions/create-github-app-token/commit/dce0ab05f36f30b22fd14289fd36655c618e4e8e))

### BREAKING CHANGES

* Custom proxy handling has been removed. If you use HTTP_PROXY or HTTPS_PROXY, you must now also set NODE_USE_ENV_PROXY=1 on the action step.
* Requires [Actions Runner v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) or later if you are using a self-hosted runner.
2026-03-14 00:22:10 +00:00
Parker Brown d2129bd463 style: remove extra blank line in release workflow
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-13 17:21:42 -07:00
Parker Brown 77b94efc3e build: refresh generated artifacts
Regenerate dist bundles and package-lock.json after replaying the release-v3 changes onto next.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-13 17:21:42 -07:00
Parker Brown 3ab4c66898 chore: move undici to devDependencies
Remove unused dotenv.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

(cherry picked from commit adb92d10e9c33600f78e8d1670c5f24c078447f8)
2026-03-13 17:21:42 -07:00
Parker Brown 739cf66feb docs: update README action versions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
(cherry picked from commit b87c921a8e196a5881f0387a9685c0fa784ebde0)
2026-03-13 17:21:42 -07:00
Parker Brown db40289976 build(deps): bump actions versions in test.yml
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
(cherry picked from commit e295338c8bf0bfa10348b7311d00869c5c272447)
2026-03-13 17:21:42 -07:00
Parker Brown 496a7ac4eb test: migrate from AVA to Node.js native test runner (#346)
AVA stores snapshots in a binary format (`.snap`), which produces no
meaningful diffs and bloats Git history. This replaces AVA with the
built-in `node:test` module, whose snapshot support generates
human-readable text files that are easy to diff and review in pull
requests.

The migration also replaces `@sinonjs/fake-timers` and `execa` with
Node.js built-ins (`node:test` mock timers and `node:child_process`),
removing three dev dependencies total.

- **`tests/index.js`**: Rewritten to use `node:test` with a custom
snapshot serializer that renders strings with actual newlines. Uses
subtests for labeled `stderr`/`stdout` snapshots, and only snapshots
non-empty output.
- **`tests/main-repo-skew.test.js`**: Replace `@sinonjs/fake-timers`
with `mock.timers.enable()` from `node:test`.
- **`tests/README.md`**: Updated documentation to reflect `node --test`
and the new snapshot file.
- **`package.json`**: Remove `ava`, `@sinonjs/fake-timers`, and `execa`
from devDependencies. Update test script to `c8 --100 node --test
tests/index.js`.
- **`tests/index.js.snapshot`**: New text-based snapshot file replacing
binary `tests/snapshots/index.js.snap`.
- **`tests/snapshots/`**: Deleted.

All 22 test scenarios (66 subtests) pass with 100% code coverage.

Closes #344

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
(cherry picked from commit f863ba5554)
2026-03-13 17:21:42 -07:00
Parker Brown 3870dc3051 Rename end-to-end proxy job in test workflow
(cherry picked from commit c2937b00bd)
2026-03-13 17:21:42 -07:00
Parker Brown 4451bcbc13 fix!: require NODE_USE_ENV_PROXY for proxy support (#342)
BREAKING CHANGE: Custom proxy handling has been removed. If you use HTTP_PROXY or HTTPS_PROXY, you must now also set NODE_USE_ENV_PROXY=1 on the action step.
2026-03-13 17:21:42 -07:00
Parker Brown dce0ab05f3 fix: remove custom proxy handling (#143)
Undici has added native support for proxy handling, so it is no longer necessary for us to have our own custom proxy handling.

Reverts #102 and resolves #134.

(cherry picked from commit cda91bf2b9)
2026-03-13 17:21:42 -07:00
Salman Chishti 2e564a0bb8 feat!: node 24 support (#275)
BREAKING CHANGE: Requires [Actions Runner v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) or later if you are using a self-hosted runner.

---------

Co-authored-by: Parker Brown <17183625+parkerbxyz@users.noreply.github.com>
(cherry picked from commit 61789386cb)
2026-03-13 17:21:42 -07:00
37 changed files with 1454 additions and 1209 deletions
@@ -1,17 +0,0 @@
name: 'Publish Immutable Action'
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
- uses: actions/checkout@v6
- name: Publish Immutable Action
uses: actions/publish-immutable-action@v0.0.4
-40
View File
@@ -1,40 +0,0 @@
name: release
on:
push:
branches:
- "*.x"
- main
- beta
permissions:
contents: write
issues: write
pull-requests: write
jobs:
release:
name: release
runs-on: ubuntu-latest
steps:
# build local version to create token
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: package.json
- run: npm ci
- run: npm run build
- uses: ./
id: app-token
with:
app-id: ${{ vars.RELEASER_APP_ID }}
private-key: ${{ secrets.RELEASER_APP_PRIVATE_KEY }}
# install release dependencies and release
- run: npm install --no-save @semantic-release/git semantic-release-plugin-github-breaking-version-tag
- run: npx semantic-release --debug
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
-34
View File
@@ -1,34 +0,0 @@
# This workflow warns and then closes issues that have had no activity for a specified amount of time.
# https://github.com/actions/stale
name: Stale
on:
workflow_dispatch:
schedule:
# 00:00 UTC on Mondays
- cron: '0 0 * * 1'
permissions:
issues: write
pull-requests: write
env:
DAYS_BEFORE_STALE: 180
DAYS_BEFORE_CLOSE: 60
STALE_LABEL: 'stale'
STALE_LABEL_URL: ${{github.server_url}}/${{github.repository}}/labels/stale
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
with:
operations-per-run: 100
days-before-stale: ${{ env.DAYS_BEFORE_STALE }}
days-before-close: ${{ env.DAYS_BEFORE_CLOSE }}
stale-issue-label: ${{ env.STALE_LABEL }}
stale-pr-label: ${{ env.STALE_LABEL }}
stale-issue-message: 'This issue has been marked ${{ env.STALE_LABEL_URL }} because it has been open for ${{ env.DAYS_BEFORE_STALE }} days with no activity. Please close this issue if it is no longer needed. If this issue is still relevant and you would like it to remain open, simply update it within the next ${{ env.DAYS_BEFORE_CLOSE }} days.'
stale-pr-message: 'This pull request has been marked ${{ env.STALE_LABEL_URL }} because it has been open for ${{ env.DAYS_BEFORE_STALE }} days with no activity. Please close this pull request if it is no longer needed. If this pull request is still relevant and you would like it to remain open, simply update it within the next ${{ env.DAYS_BEFORE_CLOSE }} days.'
-81
View File
@@ -1,81 +0,0 @@
name: test
on:
push:
branches:
- main
- beta
pull_request:
merge_group:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
integration:
name: integration
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: package.json
- run: npm ci
- run: npm test
end-to-end:
name: end-to-end
runs-on: ubuntu-latest
# do not run from forks, as forks dont have access to repository secrets
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: package.json
- run: npm ci
- run: npm run build
- uses: ./ # Uses the action in the root directory
id: test
with:
app-id: ${{ vars.TEST_APP_ID }}
private-key: ${{ secrets.TEST_APP_PRIVATE_KEY }}
- uses: octokit/request-action@v2.x
id: get-repository
env:
GITHUB_TOKEN: ${{ steps.test.outputs.token }}
with:
route: GET /installation/repositories
- run: echo '${{ steps.get-repository.outputs.data }}'
end-to-end-proxy:
name: end-to-end with unreachable proxy
runs-on: ubuntu-latest
# do not run from forks, as forks dont have access to repository secrets
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.event.pull_request.base.repo.owner.login
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version-file: package.json
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: ./ # Uses the action in the root directory
continue-on-error: true
id: test
env:
NODE_USE_ENV_PROXY: "1"
https_proxy: http://127.0.0.1:9
with:
app-id: ${{ vars.TEST_APP_ID }}
private-key: ${{ secrets.TEST_APP_PRIVATE_KEY }}
- name: Assert action failed through unreachable proxy
run: test "${{ steps.test.outcome }}" = "failure"
@@ -1,42 +0,0 @@
name: Update Permission Inputs
on:
pull_request:
paths:
- 'package.json'
- 'package-lock.json'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
jobs:
update-permission-inputs:
runs-on: ubuntu-latest
env:
COMMIT_MESSAGE: 'feat: update permission inputs'
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: package.json
- name: Install dependencies
run: npm ci
- name: Run permission inputs update script
run: node scripts/update-permission-inputs.js
- name: Commit changes
id: auto-commit
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: ${{ env.COMMIT_MESSAGE }}
- name: Update PR title
if: github.event_name == 'pull_request' && steps.auto-commit.outputs.changes_detected == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr edit ${{ github.event.pull_request.number }} --title "${{ env.COMMIT_MESSAGE }}"
+1
View File
@@ -1,3 +1,4 @@
.env
coverage
node_modules/
.DS_Store
+3
View File
@@ -0,0 +1,3 @@
{
".": "3.1.1"
}
+74 -41
View File
@@ -9,10 +9,10 @@ GitHub Action for creating a GitHub App installation access token.
In order to use this action, you need to:
1. [Register new GitHub App](https://docs.github.com/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app).
2. [Store the App's ID or Client ID in your repository environment variables](https://docs.github.com/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows) (example: `APP_ID`).
3. [Store the App's private key in your repository secrets](https://docs.github.com/actions/security-guides/encrypted-secrets?tool=webui#creating-encrypted-secrets-for-a-repository) (example: `PRIVATE_KEY`).
2. [Store the App's Client ID in your repository variables](https://docs.github.com/actions/how-tos/write-workflows/choose-what-workflows-do/use-variables#defining-configuration-variables-for-multiple-workflows) (example: `APP_CLIENT_ID`).
3. [Store the App's private key in your repository secrets](https://docs.github.com/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets?tool=webui#creating-secrets-for-a-repository) (example: `APP_PRIVATE_KEY`).
> [!IMPORTANT]
> [!IMPORTANT]
> An installation access token expires after 1 hour. Please [see this comment](https://github.com/actions/create-github-app-token/issues/121#issuecomment-2043214796) for alternative approaches if you have long-running processes.
### Create a token for the current repository
@@ -31,8 +31,8 @@ jobs:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: ./actions/staging-tests
with:
token: ${{ steps.app-token.outputs.token }}
@@ -51,15 +51,15 @@ jobs:
id: app-token
with:
# required
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- uses: actions/checkout@v5
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: actions/checkout@v6
with:
token: ${{ steps.app-token.outputs.token }}
ref: ${{ github.head_ref }}
# Make sure the value of GITHUB_TOKEN will not be persisted in repo's config
persist-credentials: false
- uses: creyD/prettier_action@v4.3
- uses: creyD/prettier_action@v6
with:
github_token: ${{ steps.app-token.outputs.token }}
```
@@ -77,8 +77,8 @@ jobs:
id: app-token
with:
# required
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Get GitHub App User ID
id: get-user-id
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
@@ -102,8 +102,8 @@ jobs:
id: app-token
with:
# required
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Get GitHub App User ID
id: get-user-id
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
@@ -138,10 +138,10 @@ jobs:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: peter-evans/create-or-update-comment@v3
- uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ steps.app-token.outputs.token }}
issue-number: ${{ github.event.issue.number }}
@@ -160,13 +160,13 @@ jobs:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: |
repo1
repo2
- uses: peter-evans/create-or-update-comment@v3
- uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ steps.app-token.outputs.token }}
issue-number: ${{ github.event.issue.number }}
@@ -185,20 +185,42 @@ jobs:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: another-owner
- uses: peter-evans/create-or-update-comment@v3
- uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ steps.app-token.outputs.token }}
issue-number: ${{ github.event.issue.number }}
body: "Hello, World!"
```
### Create a token for an enterprise installation
```yaml
on: [workflow_dispatch]
jobs:
hello-world:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v3
id: app-token
with:
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
enterprise: my-enterprise-slug
- name: Call enterprise management REST API with gh
run: |
gh api /enterprises/my-enterprise-slug/apps/installable_organizations
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
```
### Create a token with specific permissions
> [!NOTE]
> Selected permissions must be granted to the installation of the specified app and repository owner. Setting a permission that the installation does not have will result in an error.
> Selected permissions must be granted to the specified app installation. Setting a permission that the installation does not have will result in an error.
```yaml
on: [issues]
@@ -210,11 +232,11 @@ jobs:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
permission-issues: write
- uses: peter-evans/create-or-update-comment@v3
- uses: peter-evans/create-or-update-comment@v4
with:
token: ${{ steps.app-token.outputs.token }}
issue-number: ${{ github.event.issue.number }}
@@ -252,8 +274,8 @@ jobs:
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ matrix.owners-and-repos.owner }}
repositories: ${{ join(matrix.owners-and-repos.repos) }}
- uses: octokit/request-action@v2.x
@@ -281,7 +303,7 @@ jobs:
id: create_token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.GHES_APP_ID }}
client-id: ${{ vars.GHES_APP_CLIENT_ID }}
private-key: ${{ secrets.GHES_APP_PRIVATE_KEY }}
owner: ${{ vars.GHES_INSTALLATION_ORG }}
github-api-url: ${{ vars.GITHUB_API_URL }}
@@ -310,15 +332,18 @@ If you set `HTTP_PROXY` or `HTTPS_PROXY`, also set `NODE_USE_ENV_PROXY: "1"` on
NO_PROXY: github.example.com
NODE_USE_ENV_PROXY: "1"
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
```
## Inputs
### `app-id`
### `client-id` or `app-id`
**Required:** GitHub App ID.
**Required:** GitHub App Client ID.
> [!NOTE]
> The legacy `app-id` input is also accepted, but `client-id` is recommended.
### `private-key`
@@ -331,14 +356,14 @@ steps:
- name: Decode the GitHub App Private Key
id: decode
run: |
private_key=$(echo "${{ secrets.PRIVATE_KEY }}" | base64 -d | awk 'BEGIN {ORS="\\n"} {print}' | head -c -2) &> /dev/null
private_key=$(echo "${{ secrets.APP_PRIVATE_KEY }}" | base64 -d | awk 'BEGIN {ORS="\\n"} {print}' | head -c -2) &> /dev/null
echo "::add-mask::$private_key"
echo "private-key=$private_key" >> "$GITHUB_OUTPUT"
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.APP_ID }}
client-id: ${{ vars.APP_CLIENT_ID }}
private-key: ${{ steps.decode.outputs.private-key }}
```
@@ -353,6 +378,13 @@ steps:
> [!NOTE]
> If `owner` is set and `repositories` is empty, access will be scoped to all repositories in the provided repository owner's installation. If `owner` and `repositories` are empty, access will be scoped to only the current repository.
### `enterprise`
**Optional:** The slug of the enterprise account to generate a token for an enterprise installation.
> [!NOTE]
> The `enterprise` input is mutually exclusive with `owner` and `repositories`. Use it when the GitHub App is installed on an enterprise account. Enterprise installation tokens can call enterprise APIs, but do not grant organization or repository access.
### `permission-<permission name>`
**Optional:** The permissions to grant to the token. By default, the token inherits all of the installation's permissions. We recommend to explicitly list the permissions that are required for a use case. This follows GitHub's own recommendation to [control permissions of `GITHUB_TOKEN` in workflows](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token). The documentation also lists all available permissions, just prefix the permission key with `permission-` (e.g., `pull-requests``permission-pull-requests`).
@@ -383,13 +415,14 @@ GitHub App slug.
## How it works
The action creates an installation access token using [the `POST /app/installations/{installation_id}/access_tokens` endpoint](https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app). By default,
The action creates an installation access token using [the `POST /app/installations/{installation_id}/access_tokens` endpoint](https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app).
1. The token is scoped to the current repository or `repositories` if set.
2. The token inherits all the installation's permissions.
3. The token is set as output `token` which can be used in subsequent steps.
4. Unless the `skip-token-revoke` input is set to true, the token is revoked in the `post` step of the action, which means it cannot be passed to another job.
5. The token is masked, it cannot be logged accidentally.
The token target depends on the inputs: `enterprise` creates a token for an enterprise installation, `owner` without `repositories` creates a token for all repositories in the owner's installation, `repositories` scopes the token to those repositories, and no target inputs scopes the token to the current repository.
1. The token inherits all the installation's permissions.
2. The token is set as output `token` which can be used in subsequent steps.
3. Unless the `skip-token-revoke` input is set to true, the token is revoked in the `post` step of the action, which means it cannot be passed to another job.
4. The token is masked, it cannot be logged accidentally.
> [!NOTE]
> Installation permissions can differ from the app's permissions they belong to. Installation permissions are set when an app is installed on an account. When the app adds more permissions after the installation, an account administrator will have to approve the new permissions before they are set on the installation.
+16 -1
View File
@@ -5,9 +5,13 @@ branding:
icon: "lock"
color: "gray-dark"
inputs:
client-id:
description: "GitHub App Client ID"
required: false
app-id:
description: "GitHub App ID"
required: true
required: false
deprecationMessage: "Use 'client-id' instead."
private-key:
description: "GitHub App private key"
required: true
@@ -17,6 +21,9 @@ inputs:
repositories:
description: "Comma or newline-separated list of repositories to install the GitHub App on (defaults to current repository if owner is unset)"
required: false
enterprise:
description: "The slug of the enterprise account where the GitHub App is installed (cannot be used with 'owner' or 'repositories')"
required: false
skip-token-revoke:
description: "If true, the token will not be revoked when the current job is complete"
required: false
@@ -31,6 +38,10 @@ inputs:
description: "The level of permission to grant the access token for GitHub Actions workflows, workflow runs, and artifacts. Can be set to 'read' or 'write'."
permission-administration:
description: "The level of permission to grant the access token for repository creation, deletion, settings, teams, and collaborators creation. Can be set to 'read' or 'write'."
permission-artifact-metadata:
description: "The level of permission to grant the access token to create and retrieve build artifact metadata records. Can be set to 'read' or 'write'."
permission-attestations:
description: "The level of permission to create and retrieve the access token for repository attestations. Can be set to 'read' or 'write'."
permission-checks:
description: "The level of permission to grant the access token for checks on code. Can be set to 'read' or 'write'."
permission-codespaces:
@@ -43,6 +54,8 @@ inputs:
description: "The level of permission to grant the access token to manage Dependabot secrets. Can be set to 'read' or 'write'."
permission-deployments:
description: "The level of permission to grant the access token for deployments and deployment statuses. Can be set to 'read' or 'write'."
permission-discussions:
description: "The level of permission to grant the access token for discussions and related comments and labels. Can be set to 'read' or 'write'."
permission-email-addresses:
description: "The level of permission to grant the access token to manage the email addresses belonging to a user. Can be set to 'read' or 'write'."
permission-enterprise-custom-properties-for-organizations:
@@ -61,6 +74,8 @@ inputs:
description: "The level of permission to grant the access token for issues and related comments, assignees, labels, and milestones. Can be set to 'read' or 'write'."
permission-members:
description: "The level of permission to grant the access token for organization teams and members. Can be set to 'read' or 'write'."
permission-merge-queues:
description: "The level of permission to grant the access token to manage the merge queues for a repository. Can be set to 'read' or 'write'."
permission-metadata:
description: "The level of permission to grant the access token to search repositories, list collaborators, and access repository metadata. Can be set to 'read' or 'write'."
permission-organization-administration:
+211 -121
View File
@@ -22964,30 +22964,37 @@ var isError = (value) => objectToString.call(value) === "[object Error]";
var errorMessages = /* @__PURE__ */ new Set([
"network error",
// Chrome
"Failed to fetch",
// Chrome
"NetworkError when attempting to fetch resource.",
// Firefox
"The Internet connection appears to be offline.",
// Safari 16
"Load failed",
// Safari 17+
"Network request failed",
// `cross-fetch`
"fetch failed",
// Undici (Node.js)
"terminated"
"terminated",
// Undici (Node.js)
" A network error occurred.",
// Bun (WebKit)
"Network connection lost"
// Cloudflare Workers (fetch)
]);
function isNetworkError(error2) {
const isValid = error2 && isError(error2) && error2.name === "TypeError" && typeof error2.message === "string";
if (!isValid) {
return false;
}
if (error2.message === "Load failed") {
return error2.stack === void 0;
const { message, stack } = error2;
if (message === "Load failed" || message.startsWith("Load failed (") && message.endsWith(")")) {
return stack === void 0 || "__sentry_captured__" in error2;
}
return errorMessages.has(error2.message);
if (message.startsWith("error sending request for url")) {
return true;
}
if (message === "Failed to fetch" || message.startsWith("Failed to fetch (") && message.endsWith(")")) {
return true;
}
return errorMessages.has(message);
}
// node_modules/p-retry/index.js
@@ -23017,6 +23024,14 @@ function validateNumberOption(name, value, { min = 0, allowInfinity = false } =
throw new TypeError(`Expected \`${name}\` to be \u2265 ${min}.`);
}
}
function validateFunctionOption(name, value) {
if (value === void 0) {
return;
}
if (typeof value !== "function") {
throw new TypeError(`Expected \`${name}\` to be a function.`);
}
}
var AbortError = class extends Error {
constructor(message) {
super();
@@ -23044,6 +23059,26 @@ function calculateRemainingTime(start, max) {
}
return max - (performance.now() - start);
}
async function delayForRetry(delay, options) {
if (delay <= 0) {
return;
}
await new Promise((resolve2, reject) => {
const onAbort = () => {
clearTimeout(timeoutToken);
options.signal?.removeEventListener("abort", onAbort);
reject(options.signal.reason);
};
const timeoutToken = setTimeout(() => {
options.signal?.removeEventListener("abort", onAbort);
resolve2();
}, delay);
if (options.unref) {
timeoutToken.unref?.();
}
options.signal?.addEventListener("abort", onAbort, { once: true });
});
}
async function onAttemptFailure({ error: error2, attemptNumber, retriesConsumed, startTime, options }) {
const normalizedError = error2 instanceof Error ? error2 : new TypeError(`Non-error was thrown: "${error2}". You should only throw errors.`);
if (normalizedError instanceof AbortError) {
@@ -23051,55 +23086,60 @@ async function onAttemptFailure({ error: error2, attemptNumber, retriesConsumed,
}
const retriesLeft = Number.isFinite(options.retries) ? Math.max(0, options.retries - retriesConsumed) : options.retries;
const maxRetryTime = options.maxRetryTime ?? Number.POSITIVE_INFINITY;
const delayTime = calculateDelay(retriesConsumed, options);
const remainingTimeBeforeCallbacks = calculateRemainingTime(startTime, maxRetryTime);
if (remainingTimeBeforeCallbacks <= 0) {
const context2 = Object.freeze({
error: normalizedError,
attemptNumber,
retriesLeft,
retriesConsumed,
retryDelay: 0
});
await options.onFailedAttempt(context2);
throw normalizedError;
}
const consumeRetryContext = Object.freeze({
error: normalizedError,
attemptNumber,
retriesLeft,
retriesConsumed,
retryDelay: retriesLeft > 0 ? delayTime : 0
});
const consumeRetry = await options.shouldConsumeRetry(consumeRetryContext);
const effectiveDelay = consumeRetry && retriesLeft > 0 ? delayTime : 0;
const context = Object.freeze({
error: normalizedError,
attemptNumber,
retriesLeft,
retriesConsumed
retriesConsumed,
retryDelay: effectiveDelay
});
await options.onFailedAttempt(context);
if (calculateRemainingTime(startTime, maxRetryTime) <= 0) {
throw normalizedError;
}
const consumeRetry = await options.shouldConsumeRetry(context);
const remainingTime = calculateRemainingTime(startTime, maxRetryTime);
if (remainingTime <= 0 || retriesLeft <= 0) {
throw normalizedError;
}
if (normalizedError instanceof TypeError && !isNetworkError(normalizedError)) {
if (consumeRetry) {
throw normalizedError;
}
options.signal?.throwIfAborted();
return false;
throw normalizedError;
}
if (!await options.shouldRetry(context)) {
throw normalizedError;
}
const remainingTimeAfterShouldRetry = calculateRemainingTime(startTime, maxRetryTime);
if (remainingTimeAfterShouldRetry <= 0) {
throw normalizedError;
}
if (!consumeRetry) {
options.signal?.throwIfAborted();
return false;
}
const delayTime = calculateDelay(retriesConsumed, options);
const finalDelay = Math.min(delayTime, remainingTime);
const finalDelay = Math.min(effectiveDelay, remainingTimeAfterShouldRetry);
options.signal?.throwIfAborted();
if (finalDelay > 0) {
await new Promise((resolve2, reject) => {
const onAbort = () => {
clearTimeout(timeoutToken);
options.signal?.removeEventListener("abort", onAbort);
reject(options.signal.reason);
};
const timeoutToken = setTimeout(() => {
options.signal?.removeEventListener("abort", onAbort);
resolve2();
}, finalDelay);
if (options.unref) {
timeoutToken.unref?.();
}
options.signal?.addEventListener("abort", onAbort, { once: true });
});
}
await delayForRetry(finalDelay, options);
options.signal?.throwIfAborted();
return true;
}
@@ -23119,6 +23159,9 @@ async function pRetry(input, options = {}) {
};
options.shouldRetry ??= () => true;
options.shouldConsumeRetry ??= () => true;
validateFunctionOption("onFailedAttempt", options.onFailedAttempt);
validateFunctionOption("shouldRetry", options.shouldRetry);
validateFunctionOption("shouldConsumeRetry", options.shouldConsumeRetry);
validateNumberOption("factor", options.factor, { min: 0, allowInfinity: false });
validateNumberOption("minTimeout", options.minTimeout, { min: 0, allowInfinity: false });
validateNumberOption("maxTimeout", options.maxTimeout, { min: 0, allowInfinity: true });
@@ -23153,80 +23196,20 @@ async function pRetry(input, options = {}) {
}
// lib/main.js
async function main(appId, privateKey, owner, repositories, permissions, core, createAppAuth2, request2, skipTokenRevoke) {
let parsedOwner = "";
let parsedRepositoryNames = [];
if (!owner && repositories.length === 0) {
const [owner2, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
parsedOwner = owner2;
parsedRepositoryNames = [repo];
core.info(
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (${owner2}/${repo}).`
);
}
if (owner && repositories.length === 0) {
parsedOwner = owner;
core.info(
`Input 'repositories' is not set. Creating token for all repositories owned by ${owner}.`
);
}
if (!owner && repositories.length > 0) {
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
parsedRepositoryNames = repositories;
core.info(
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories.map((repo) => `
- ${parsedOwner}/${repo}`).join("")}`
);
}
if (owner && repositories.length > 0) {
parsedOwner = owner;
parsedRepositoryNames = repositories;
core.info(
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
${repositories.map((repo) => `
- ${parsedOwner}/${repo}`).join("")}`
);
async function main(clientId, privateKey, enterprise, owner, repositories, permissions, core, createAppAuth2, request2, skipTokenRevoke) {
if (enterprise && (owner || repositories.length > 0)) {
throw new Error("Cannot use 'enterprise' input with 'owner' or 'repositories' inputs");
}
const target = resolveInstallationTarget(enterprise, owner, repositories, core);
const auth5 = createAppAuth2({
appId,
appId: clientId,
privateKey,
request: request2
});
let authentication, installationId, appSlug;
if (parsedRepositoryNames.length > 0) {
({ authentication, installationId, appSlug } = await pRetry(
() => getTokenFromRepository(
request2,
auth5,
parsedOwner,
parsedRepositoryNames,
permissions
),
{
shouldRetry: ({ error: error2 }) => error2.status >= 500,
onFailedAttempt: (context) => {
core.info(
`Failed to create token for "${parsedRepositoryNames.join(
","
)}" (attempt ${context.attemptNumber}): ${context.error.message}`
);
},
retries: 3
}
));
} else {
({ authentication, installationId, appSlug } = await pRetry(
() => getTokenFromOwner(request2, auth5, parsedOwner, permissions),
{
onFailedAttempt: (context) => {
core.info(
`Failed to create token for "${parsedOwner}" (attempt ${context.attemptNumber}): ${context.error.message}`
);
},
retries: 3
}
));
}
const { authentication, installationId, appSlug } = await pRetry(
() => getTokenFromTarget(request2, auth5, target, permissions),
createTokenRetryOptions(core, getTokenRetryDescription(target))
);
core.setSecret(authentication.token);
core.setOutput("token", authentication.token);
core.setOutput("installation-id", installationId);
@@ -23236,6 +23219,102 @@ async function main(appId, privateKey, owner, repositories, permissions, core, c
core.saveState("expiresAt", authentication.expiresAt);
}
}
function resolveInstallationTarget(enterprise, owner, repositories, core) {
if (enterprise) {
core.info(`Creating enterprise installation token for enterprise "${enterprise}".`);
return { type: "enterprise", enterprise };
}
if (!owner && repositories.length === 0) {
const [defaultOwner, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
core.info(
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (${defaultOwner}/${repo}).`
);
return {
type: "repository",
owner: defaultOwner,
repositories: [repo]
};
}
if (owner && repositories.length === 0) {
core.info(
`Input 'repositories' is not set. Creating token for all repositories owned by ${owner}.`
);
return { type: "owner", owner };
}
const parsedOwner = owner || String(process.env.GITHUB_REPOSITORY_OWNER);
if (!owner) {
core.info(
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories.map((repo) => `
- ${parsedOwner}/${repo}`).join("")}`
);
} else {
core.info(
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:${repositories.map((repo) => `
- ${parsedOwner}/${repo}`).join("")}`
);
}
return {
type: "repository",
owner: parsedOwner,
repositories
};
}
function getTokenRetryDescription(target) {
switch (target.type) {
case "enterprise":
return `enterprise "${target.enterprise}"`;
case "repository":
return `"${target.repositories.map((repository) => `${target.owner}/${repository}`).join(",")}"`;
case "owner":
return `"${target.owner}"`;
/* c8 ignore next 2 */
default:
throw new Error(`Unsupported installation target type: ${target.type}`);
}
}
function getTokenFromTarget(request2, auth5, target, permissions) {
switch (target.type) {
case "enterprise":
return getTokenFromEnterprise(request2, auth5, target.enterprise, permissions);
case "repository":
return getTokenFromRepository(
request2,
auth5,
target.owner,
target.repositories,
permissions
);
case "owner":
return getTokenFromOwner(request2, auth5, target.owner, permissions);
/* c8 ignore next 2 */
default:
throw new Error(`Unsupported installation target type: ${target.type}`);
}
}
function createTokenRetryOptions(core, targetDescription) {
return {
shouldRetry: ({ error: error2 }) => error2.status >= 500 || isNetworkError(error2),
onFailedAttempt: (context) => {
core.info(
`Failed to create token for ${targetDescription} (attempt ${context.attemptNumber}): ${context.error.message}`
);
},
retries: 3
};
}
async function createInstallationAuthResult(auth5, installation, permissions, options = {}) {
const authentication = await auth5({
type: "installation",
installationId: installation.id,
permissions,
...options
});
return {
authentication,
installationId: installation.id,
appSlug: installation["app_slug"]
};
}
async function getTokenFromOwner(request2, auth5, parsedOwner, permissions) {
const response = await request2("GET /users/{username}/installation", {
username: parsedOwner,
@@ -23243,14 +23322,7 @@ async function getTokenFromOwner(request2, auth5, parsedOwner, permissions) {
hook: auth5.hook
}
});
const authentication = await auth5({
type: "installation",
installationId: response.data.id,
permissions
});
const installationId = response.data.id;
const appSlug = response.data["app_slug"];
return { authentication, installationId, appSlug };
return createInstallationAuthResult(auth5, response.data, permissions);
}
async function getTokenFromRepository(request2, auth5, parsedOwner, parsedRepositoryNames, permissions) {
const response = await request2("GET /repos/{owner}/{repo}/installation", {
@@ -23260,15 +23332,28 @@ async function getTokenFromRepository(request2, auth5, parsedOwner, parsedReposi
hook: auth5.hook
}
});
const authentication = await auth5({
type: "installation",
installationId: response.data.id,
repositoryNames: parsedRepositoryNames,
permissions
return createInstallationAuthResult(auth5, response.data, permissions, {
repositoryNames: parsedRepositoryNames
});
const installationId = response.data.id;
const appSlug = response.data["app_slug"];
return { authentication, installationId, appSlug };
}
async function getTokenFromEnterprise(request2, auth5, enterprise, permissions) {
let response;
try {
response = await request2("GET /enterprises/{enterprise}/installation", {
enterprise,
request: {
hook: auth5.hook
}
});
} catch (error2) {
if (error2.status === 404) {
throw new Error(
`No enterprise installation found matching the enterprise slug "${enterprise}".`
);
}
throw error2;
}
return createInstallationAuthResult(auth5, response.data, permissions);
}
// lib/request.js
@@ -23307,15 +23392,20 @@ if (!process.env.GITHUB_REPOSITORY_OWNER) {
}
async function run() {
ensureNativeProxySupport();
const appId = getInput("app-id");
const clientId = getInput("client-id") || getInput("app-id");
if (!clientId) {
throw new Error("The 'client-id' (or deprecated 'app-id') input must be set to a non-empty string. If using a secret or variable, ensure it is available in this workflow context.");
}
const privateKey = getInput("private-key");
const enterprise = getInput("enterprise");
const owner = getInput("owner");
const repositories = getInput("repositories").split(/[\n,]+/).map((s) => s.trim()).filter((x) => x !== "");
const skipTokenRevoke = getBooleanInput("skip-token-revoke");
const permissions = getPermissionsFromInputs(process.env);
return main(
appId,
clientId,
privateKey,
enterprise,
owner,
repositories,
permissions,
+160 -106
View File
@@ -1,9 +1,11 @@
import pRetry from "p-retry";
import isNetworkError from "is-network-error";
// @ts-check
/**
* @param {string} appId
* @param {string} clientId
* @param {string} privateKey
* @param {string} enterprise
* @param {string} owner
* @param {string[]} repositories
* @param {undefined | Record<string, string>} permissions
@@ -13,107 +15,34 @@ import pRetry from "p-retry";
* @param {boolean} skipTokenRevoke
*/
export async function main(
appId,
clientId,
privateKey,
enterprise,
owner,
repositories,
permissions,
core,
createAppAuth,
request,
skipTokenRevoke
skipTokenRevoke,
) {
let parsedOwner = "";
let parsedRepositoryNames = [];
// If neither owner nor repositories are set, default to current repository
if (!owner && repositories.length === 0) {
const [owner, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
parsedOwner = owner;
parsedRepositoryNames = [repo];
core.info(
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (${owner}/${repo}).`
);
// Validate mutual exclusivity of enterprise with owner/repositories
if (enterprise && (owner || repositories.length > 0)) {
throw new Error("Cannot use 'enterprise' input with 'owner' or 'repositories' inputs");
}
// If only an owner is set, default to all repositories from that owner
if (owner && repositories.length === 0) {
parsedOwner = owner;
core.info(
`Input 'repositories' is not set. Creating token for all repositories owned by ${owner}.`
);
}
// If repositories are set, but no owner, default to `GITHUB_REPOSITORY_OWNER`
if (!owner && repositories.length > 0) {
parsedOwner = String(process.env.GITHUB_REPOSITORY_OWNER);
parsedRepositoryNames = repositories;
core.info(
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories
.map((repo) => `\n- ${parsedOwner}/${repo}`)
.join("")}`
);
}
// If both owner and repositories are set, use those values
if (owner && repositories.length > 0) {
parsedOwner = owner;
parsedRepositoryNames = repositories;
core.info(
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
${repositories.map((repo) => `\n- ${parsedOwner}/${repo}`).join("")}`
);
}
const target = resolveInstallationTarget(enterprise, owner, repositories, core);
const auth = createAppAuth({
appId,
appId: clientId,
privateKey,
request,
});
let authentication, installationId, appSlug;
// If at least one repository is set, get installation ID from that repository
if (parsedRepositoryNames.length > 0) {
({ authentication, installationId, appSlug } = await pRetry(
() =>
getTokenFromRepository(
request,
auth,
parsedOwner,
parsedRepositoryNames,
permissions
),
{
shouldRetry: ({ error }) => error.status >= 500,
onFailedAttempt: (context) => {
core.info(
`Failed to create token for "${parsedRepositoryNames.join(
","
)}" (attempt ${context.attemptNumber}): ${context.error.message}`
);
},
retries: 3,
}
));
} else {
// Otherwise get the installation for the owner, which can either be an organization or a user account
({ authentication, installationId, appSlug } = await pRetry(
() => getTokenFromOwner(request, auth, parsedOwner, permissions),
{
onFailedAttempt: (context) => {
core.info(
`Failed to create token for "${parsedOwner}" (attempt ${context.attemptNumber}): ${context.error.message}`
);
},
retries: 3,
}
));
}
const { authentication, installationId, appSlug } = await pRetry(
() => getTokenFromTarget(request, auth, target, permissions),
createTokenRetryOptions(core, getTokenRetryDescription(target))
);
// Register the token with the runner as a secret to ensure it is masked in logs
core.setSecret(authentication.token);
@@ -129,6 +58,125 @@ export async function main(
}
}
function resolveInstallationTarget(enterprise, owner, repositories, core) {
if (enterprise) {
core.info(`Creating enterprise installation token for enterprise "${enterprise}".`);
return { type: "enterprise", enterprise };
}
if (!owner && repositories.length === 0) {
const [defaultOwner, repo] = String(process.env.GITHUB_REPOSITORY).split("/");
core.info(
`Inputs 'owner' and 'repositories' are not set. Creating token for this repository (${defaultOwner}/${repo}).`
);
return {
type: "repository",
owner: defaultOwner,
repositories: [repo],
};
}
if (owner && repositories.length === 0) {
core.info(
`Input 'repositories' is not set. Creating token for all repositories owned by ${owner}.`
);
return { type: "owner", owner };
}
const parsedOwner = owner || String(process.env.GITHUB_REPOSITORY_OWNER);
if (!owner) {
core.info(
`No 'owner' input provided. Using default owner '${parsedOwner}' to create token for the following repositories:${repositories
.map((repo) => `\n- ${parsedOwner}/${repo}`)
.join("")}`
);
} else {
core.info(
`Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:${repositories
.map((repo) => `\n- ${parsedOwner}/${repo}`)
.join("")}`
);
}
return {
type: "repository",
owner: parsedOwner,
repositories,
};
}
function getTokenRetryDescription(target) {
switch (target.type) {
case "enterprise":
return `enterprise "${target.enterprise}"`;
case "repository":
return `"${target.repositories
.map((repository) => `${target.owner}/${repository}`)
.join(",")}"`;
case "owner":
return `"${target.owner}"`;
/* c8 ignore next 2 */
default:
throw new Error(`Unsupported installation target type: ${target.type}`);
}
}
function getTokenFromTarget(request, auth, target, permissions) {
switch (target.type) {
case "enterprise":
return getTokenFromEnterprise(request, auth, target.enterprise, permissions);
case "repository":
return getTokenFromRepository(
request,
auth,
target.owner,
target.repositories,
permissions
);
case "owner":
return getTokenFromOwner(request, auth, target.owner, permissions);
/* c8 ignore next 2 */
default:
throw new Error(`Unsupported installation target type: ${target.type}`);
}
}
function createTokenRetryOptions(core, targetDescription) {
return {
shouldRetry: ({ error }) => error.status >= 500 || isNetworkError(error),
onFailedAttempt: (context) => {
core.info(
`Failed to create token for ${targetDescription} (attempt ${context.attemptNumber}): ${context.error.message}`
);
},
retries: 3,
};
}
async function createInstallationAuthResult(
auth,
installation,
permissions,
options = {},
) {
const authentication = await auth({
type: "installation",
installationId: installation.id,
permissions,
...options,
});
return {
authentication,
installationId: installation.id,
appSlug: installation["app_slug"],
};
}
async function getTokenFromOwner(request, auth, parsedOwner, permissions) {
// https://docs.github.com/rest/apps/apps?apiVersion=2022-11-28#get-a-user-installation-for-the-authenticated-app
// This endpoint works for both users and organizations
@@ -139,17 +187,8 @@ async function getTokenFromOwner(request, auth, parsedOwner, permissions) {
},
});
// Get token for for all repositories of the given installation
const authentication = await auth({
type: "installation",
installationId: response.data.id,
permissions,
});
const installationId = response.data.id;
const appSlug = response.data["app_slug"];
return { authentication, installationId, appSlug };
// Get token for all repositories of the given installation
return createInstallationAuthResult(auth, response.data, permissions);
}
async function getTokenFromRepository(
@@ -169,15 +208,30 @@ async function getTokenFromRepository(
});
// Get token for given repositories
const authentication = await auth({
type: "installation",
installationId: response.data.id,
return createInstallationAuthResult(auth, response.data, permissions, {
repositoryNames: parsedRepositoryNames,
permissions,
});
const installationId = response.data.id;
const appSlug = response.data["app_slug"];
return { authentication, installationId, appSlug };
}
async function getTokenFromEnterprise(request, auth, enterprise, permissions) {
let response;
try {
response = await request("GET /enterprises/{enterprise}/installation", {
enterprise,
request: {
hook: auth.hook,
},
});
} catch (error) {
if (error.status === 404) {
throw new Error(
`No enterprise installation found matching the enterprise slug "${enterprise}".`
);
}
throw error;
}
// Get token for the enterprise installation
return createInstallationAuthResult(auth, response.data, permissions);
}
+7 -2
View File
@@ -18,8 +18,12 @@ if (!process.env.GITHUB_REPOSITORY_OWNER) {
async function run() {
ensureNativeProxySupport();
const appId = core.getInput("app-id");
const clientId = core.getInput("client-id") || core.getInput("app-id");
if (!clientId) {
throw new Error("The 'client-id' (or deprecated 'app-id') input must be set to a non-empty string. If using a secret or variable, ensure it is available in this workflow context.");
}
const privateKey = core.getInput("private-key");
const enterprise = core.getInput("enterprise");
const owner = core.getInput("owner");
const repositories = core
.getInput("repositories")
@@ -32,8 +36,9 @@ async function run() {
const permissions = getPermissionsFromInputs(process.env);
return main(
appId,
clientId,
privateKey,
enterprise,
owner,
repositories,
permissions,
+404 -652
View File
File diff suppressed because it is too large Load Diff
+10 -37
View File
@@ -2,7 +2,7 @@
"name": "create-github-app-token",
"private": true,
"type": "module",
"version": "3.0.0-beta.6",
"version": "3.1.1",
"description": "GitHub Action for creating a GitHub App Installation Access Token",
"engines": {
"node": ">=24.4.0"
@@ -16,45 +16,18 @@
},
"license": "MIT",
"dependencies": {
"@actions/core": "^3.0.0",
"@actions/core": "^3.0.1",
"@octokit/auth-app": "^8.2.0",
"@octokit/request": "^10.0.8",
"p-retry": "^7.1.1",
"undici": "^7.24.1"
"is-network-error": "^1.3.2",
"p-retry": "^8.0.0"
},
"devDependencies": {
"@octokit/openapi": "^21.0.0",
"c8": "^10.1.3",
"dotenv": "^17.3.1",
"esbuild": "^0.27.3",
"open-cli": "^8.0.0",
"yaml": "^2.8.2"
},
"release": {
"branches": [
"+([0-9]).x",
"main",
{
"name": "beta",
"prerelease": true
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github",
"@semantic-release/npm",
[
"@semantic-release/git",
{
"assets": [
"package.json",
"package-lock.json",
"dist/*"
],
"message": "build(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
"@octokit/openapi": "^22.0.0",
"c8": "^11.0.0",
"esbuild": "^0.28.0",
"open-cli": "^9.0.0",
"undici": "^8.2.0",
"yaml": "^2.8.4"
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"packages": {
".": {
"prerelease": true,
"prerelease-type": "beta",
"include-component-in-tag": false,
"release-type": "node",
"versioning": "prerelease"
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"packages": {
".": {
"include-component-in-tag": false,
"release-type": "node"
}
}
}
+32
View File
@@ -19,6 +19,22 @@
"write"
]
},
"artifact_metadata": {
"type": "string",
"description": "The level of permission to grant the access token to create and retrieve build artifact metadata records.",
"enum": [
"read",
"write"
]
},
"attestations": {
"type": "string",
"description": "The level of permission to create and retrieve the access token for repository attestations.",
"enum": [
"read",
"write"
]
},
"checks": {
"type": "string",
"description": "The level of permission to grant the access token for checks on code.",
@@ -59,6 +75,14 @@
"write"
]
},
"discussions": {
"type": "string",
"description": "The level of permission to grant the access token for discussions and related comments and labels.",
"enum": [
"read",
"write"
]
},
"environments": {
"type": "string",
"description": "The level of permission to grant the access token for managing repository environments.",
@@ -75,6 +99,14 @@
"write"
]
},
"merge_queues": {
"type": "string",
"description": "The level of permission to grant the access token to manage the merge queues for a repository.",
"enum": [
"read",
"write"
]
},
"metadata": {
"type": "string",
"description": "The level of permission to grant the access token to search repositories, list collaborators, and access repository metadata.",
+2 -2
View File
@@ -32,5 +32,5 @@ node --test --test-update-snapshots tests/index.js
We have tests both for the `main.js` and `post.js` scripts.
- If you do not expect an error, take [main-token-permissions-set.test.js](tests/main-token-permissions-set.test.js) as a starting point.
- If your test has an expected error, take [main-missing-app-id.test.js](tests/main-missing-app-id.test.js) as a starting point.
- If you do not expect an error, take [main-token-permissions-set.test.js](main-token-permissions-set.test.js) as a starting point.
- If your test has an expected error, take [main-missing-client-and-app-id.test.js](main-missing-client-and-app-id.test.js) as a starting point.
+27 -5
View File
@@ -11,11 +11,24 @@ snapshot.setDefaultSnapshotSerializers([
(value) => (typeof value === "string" ? value : undefined),
]);
function normalizeStderr(stderr) {
return stderr
.replaceAll(/\u001B\[[0-9;]*m/g, "")
.replaceAll(process.cwd(), "<cwd>")
.replaceAll(/:\d+:\d+/g, ":<line>:<column>");
}
// Get all files in tests directory
const files = readdirSync("tests");
// Files to ignore
const ignore = ["index.js", "index.js.snapshot", "main.js", "README.md"];
const ignore = [
"index.js",
"index.js.snapshot",
"main.js",
"mock-agent.js",
"README.md",
];
const testFiles = files.filter((file) => !ignore.includes(file)).sort();
@@ -39,10 +52,19 @@ for (const file of testFiles) {
NODE_USE_ENV_PROXY,
...env
} = process.env;
const { stderr, stdout } = await execFileAsync("node", [`tests/${file}`], {
env,
});
const trimmedStderr = stderr.replace(/\r?\n$/, "");
let stderr, stdout;
try {
({ stderr, stdout } = await execFileAsync("node", [`tests/${file}`], {
env,
}));
} catch (error) {
if (!(error instanceof Error) || !("stderr" in error) || !("stdout" in error)) {
throw error;
}
({ stderr, stdout } = error);
}
const trimmedStderr = normalizeStderr(stderr).replace(/\r?\n$/, "");
const trimmedStdout = stdout.replace(/\r?\n$/, "");
await t.test("stderr", (t) => {
if (trimmedStderr) t.assert.snapshot(trimmedStderr);
+205 -7
View File
@@ -1,6 +1,43 @@
exports[`action-deprecated-inputs.test.js > stdout 1`] = `
app-id — Use 'client-id' instead.
`;
exports[`main-app-id-fallback.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are not set. Creating token for this repository (actions/create-github-app-token).
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=installation-id::123456
::set-output name=app-slug::github-actions
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::save-state name=expiresAt::2016-07-11T22:14:10Z
--- REQUESTS ---
GET /repos/actions/create-github-app-token/installation
POST /app/installations/123456/access_tokens
{"repositories":["create-github-app-token"]}
`;
exports[`main-client-id-precedence.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are not set. Creating token for this repository (actions/create-github-app-token).
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=installation-id::123456
::set-output name=app-slug::github-actions
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::save-state name=expiresAt::2016-07-11T22:14:10Z
--- REQUESTS ---
GET /repos/actions/create-github-app-token/installation
POST /app/installations/123456/access_tokens
{"repositories":["create-github-app-token"]}
`;
exports[`main-custom-github-api-url.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/create-github-app-token
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a
@@ -17,6 +54,113 @@ POST /api/v3/app/installations/123456/access_tokens
{"repositories":["create-github-app-token"]}
`;
exports[`main-enterprise-fail-response.test.js > stdout 1`] = `
Creating enterprise installation token for enterprise "test-enterprise".
Failed to create token for enterprise "test-enterprise" (attempt 1): GitHub API not available
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=installation-id::123456
::set-output name=app-slug::github-actions
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::save-state name=expiresAt::2016-07-11T22:14:10Z
--- REQUESTS ---
GET /enterprises/test-enterprise/installation
GET /enterprises/test-enterprise/installation
POST /app/installations/123456/access_tokens
null
`;
exports[`main-enterprise-installation-not-found.test.js > stderr 1`] = `
Error: No enterprise installation found matching the enterprise slug "test-enterprise".
at getTokenFromEnterprise (file://<cwd>/lib/main.js:<line>:<column>)
at process.processTicksAndRejections (node:internal/process/task_queues:<line>:<column>)
at async pRetry (file://<cwd>/node_modules/p-retry/index.js:<line>:<column>)
at async main (file://<cwd>/lib/main.js:<line>:<column>)
at async test (file://<cwd>/tests/main.js:<line>:<column>)
at async file://<cwd>/tests/main-enterprise-installation-not-found.test.js:<line>:<column>
`;
exports[`main-enterprise-installation-not-found.test.js > stdout 1`] = `
Creating enterprise installation token for enterprise "test-enterprise".
Failed to create token for enterprise "test-enterprise" (attempt 1): No enterprise installation found matching the enterprise slug "test-enterprise".
::error::No enterprise installation found matching the enterprise slug "test-enterprise".
--- REQUESTS ---
GET /enterprises/test-enterprise/installation
`;
exports[`main-enterprise-mutual-exclusivity-owner.test.js > stderr 1`] = `
Error: Cannot use 'enterprise' input with 'owner' or 'repositories' inputs
at main (file://<cwd>/lib/main.js:<line>:<column>)
at run (file://<cwd>/main.js:<line>:<column>)
at file://<cwd>/main.js:<line>:<column>
at ModuleJob.run (node:internal/modules/esm/module_job:<line>:<column>)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:<line>:<column>)
at async file://<cwd>/tests/main-enterprise-mutual-exclusivity-owner.test.js:<line>:<column>
`;
exports[`main-enterprise-mutual-exclusivity-owner.test.js > stdout 1`] = `
::error::Cannot use 'enterprise' input with 'owner' or 'repositories' inputs
`;
exports[`main-enterprise-mutual-exclusivity-repositories.test.js > stderr 1`] = `
Error: Cannot use 'enterprise' input with 'owner' or 'repositories' inputs
at main (file://<cwd>/lib/main.js:<line>:<column>)
at run (file://<cwd>/main.js:<line>:<column>)
at file://<cwd>/main.js:<line>:<column>
at ModuleJob.run (node:internal/modules/esm/module_job:<line>:<column>)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:<line>:<column>)
at async file://<cwd>/tests/main-enterprise-mutual-exclusivity-repositories.test.js:<line>:<column>
`;
exports[`main-enterprise-mutual-exclusivity-repositories.test.js > stdout 1`] = `
::error::Cannot use 'enterprise' input with 'owner' or 'repositories' inputs
`;
exports[`main-enterprise-only-success.test.js > stdout 1`] = `
Creating enterprise installation token for enterprise "test-enterprise".
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=installation-id::123456
::set-output name=app-slug::github-actions
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::save-state name=expiresAt::2016-07-11T22:14:10Z
--- REQUESTS ---
GET /enterprises/test-enterprise/installation
POST /app/installations/123456/access_tokens
null
`;
exports[`main-enterprise-token-permissions-set.test.js > stdout 1`] = `
Creating enterprise installation token for enterprise "test-enterprise".
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=installation-id::123456
::set-output name=app-slug::github-actions
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::save-state name=expiresAt::2016-07-11T22:14:10Z
--- REQUESTS ---
GET /enterprises/test-enterprise/installation
POST /app/installations/123456/access_tokens
{"permissions":{"enterprise_custom_properties_for_organizations":"read"}}
`;
exports[`main-missing-client-and-app-id.test.js > stderr 1`] = `
The 'client-id' (or deprecated 'app-id') input must be set to a non-empty string. If using a secret or variable, ensure it is available in this workflow context.
`;
exports[`main-missing-client-and-app-id.test.js > stdout 1`] = `
::error::The 'client-id' (or deprecated 'app-id') input must be set to a non-empty string. If using a secret or variable, ensure it is available in this workflow context.
`;
exports[`main-missing-owner.test.js > stderr 1`] = `
GITHUB_REPOSITORY_OWNER missing, must be set to '<owner>'
`;
@@ -57,7 +201,6 @@ exports[`main-repo-skew.test.js > stderr 1`] = `
exports[`main-repo-skew.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/failed-repo
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a
@@ -75,6 +218,45 @@ POST /app/installations/123456/access_tokens
{"repositories":["failed-repo"]}
`;
exports[`main-token-get-owner-set-client-error.test.js > stderr 1`] = `
RequestError [HttpError]: Forbidden
at fetchWrapper (file://<cwd>/node_modules/@octokit/request/dist-bundle/index.js:<line>:<column>)
at process.processTicksAndRejections (node:internal/process/task_queues:<line>:<column>)
at async hook (file://<cwd>/node_modules/@octokit/auth-app/dist-node/index.js:<line>:<column>)
at async getTokenFromOwner (file://<cwd>/lib/main.js:<line>:<column>)
at async pRetry (file://<cwd>/node_modules/p-retry/index.js:<line>:<column>)
at async main (file://<cwd>/lib/main.js:<line>:<column>)
at async test (file://<cwd>/tests/main.js:<line>:<column>)
at async file://<cwd>/tests/main-token-get-owner-set-client-error.test.js:<line>:<column> {
status: 403,
request: {
method: 'GET',
url: 'https://api.github.com/users/smockle/installation',
headers: {
accept: 'application/vnd.github.v3+json',
'user-agent': 'actions/create-github-app-token',
authorization: 'bearer [REDACTED]'
},
request: { hook: [Function: bound hook] AsyncFunction }
},
response: {
url: 'https://api.github.com/users/smockle/installation',
status: 403,
headers: { 'content-type': 'application/json' },
data: { message: 'Forbidden' }
},
[cause]: undefined
}
`;
exports[`main-token-get-owner-set-client-error.test.js > stdout 1`] = `
Input 'repositories' is not set. Creating token for all repositories owned by smockle.
Failed to create token for "smockle" (attempt 1): Forbidden
::error::Forbidden
--- REQUESTS ---
GET /users/smockle/installation
`;
exports[`main-token-get-owner-set-fail-response.test.js > stdout 1`] = `
Input 'repositories' is not set. Creating token for all repositories owned by smockle.
Failed to create token for "smockle" (attempt 1): GitHub API not available
@@ -96,9 +278,8 @@ null
exports[`main-token-get-owner-set-repo-fail-response.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/failed-repo
Failed to create token for "failed-repo" (attempt 1): GitHub API not available
Failed to create token for "actions/failed-repo" (attempt 1): GitHub API not available
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
@@ -115,9 +296,28 @@ POST /app/installations/123456/access_tokens
{"repositories":["failed-repo"]}
`;
exports[`main-token-get-owner-set-repo-network-error.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/network-repo
Failed to create token for "actions/network-repo" (attempt 1): fetch failed
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::set-output name=installation-id::123456
::set-output name=app-slug::github-actions
::save-state name=token::ghs_16C7e42F292c6912E7710c838347Ae178B4a
::save-state name=expiresAt::2016-07-11T22:14:10Z
--- REQUESTS ---
GET /repos/actions/network-repo/installation
GET /repos/actions/network-repo/installation
POST /app/installations/123456/access_tokens
{"repositories":["network-repo"]}
`;
exports[`main-token-get-owner-set-repo-set-to-many-newline.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/create-github-app-token
- actions/toolkit
- actions/checkout
@@ -138,7 +338,6 @@ POST /app/installations/123456/access_tokens
exports[`main-token-get-owner-set-repo-set-to-many.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/create-github-app-token
- actions/toolkit
- actions/checkout
@@ -159,7 +358,6 @@ POST /app/installations/123456/access_tokens
exports[`main-token-get-owner-set-repo-set-to-one.test.js > stdout 1`] = `
Inputs 'owner' and 'repositories' are set. Creating token for the following repositories:
- actions/create-github-app-token
::add-mask::ghs_16C7e42F292c6912E7710c838347Ae178B4a
+11
View File
@@ -0,0 +1,11 @@
import { DEFAULT_ENV, test } from "./main.js";
// Verify `main` falls back to `app-id` when `client-id` is not set
await test(
() => {},
{
...DEFAULT_ENV,
"INPUT_CLIENT-ID": "",
"INPUT_APP-ID": "123456",
}
);
+11
View File
@@ -0,0 +1,11 @@
import { DEFAULT_ENV, test } from "./main.js";
// Verify `client-id` takes precedence when both `client-id` and `app-id` are set
await test(
() => {},
{
...DEFAULT_ENV,
"INPUT_CLIENT-ID": "Iv1.0123456789abcdef",
"INPUT_APP-ID": "123456",
}
);
@@ -0,0 +1,39 @@
import { test } from "./main.js";
// Verify enterprise installation lookup retries when the GitHub API returns a 500 error.
await test((mockPool) => {
process.env.INPUT_ENTERPRISE = "test-enterprise";
delete process.env.INPUT_OWNER;
delete process.env.INPUT_REPOSITORIES;
const mockInstallationId = "123456";
const mockAppSlug = "github-actions";
mockPool
.intercept({
path: "/enterprises/test-enterprise/installation",
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(500, "GitHub API not available");
mockPool
.intercept({
path: "/enterprises/test-enterprise/installation",
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(
200,
{ id: mockInstallationId, app_slug: mockAppSlug },
{ headers: { "content-type": "application/json" } },
);
});
@@ -0,0 +1,25 @@
import { test } from "./main.js";
// Verify `main` handles when no enterprise installation is found.
await test((mockPool) => {
delete process.env.INPUT_OWNER;
delete process.env.INPUT_REPOSITORIES;
process.env.INPUT_ENTERPRISE = "test-enterprise";
// Mock the enterprise installation endpoint to return no matching installation
mockPool
.intercept({
path: "/enterprises/test-enterprise/installation",
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(
404,
{ message: "Not Found" },
{ headers: { "content-type": "application/json" } }
);
});
@@ -0,0 +1,13 @@
import { DEFAULT_ENV } from "./main.js";
// Verify `main` exits with an error when `enterprise` is used with `owner` input.
// Set up environment with enterprise and owner set
for (const [key, value] of Object.entries(DEFAULT_ENV)) {
process.env[key] = value;
}
process.env.INPUT_ENTERPRISE = "test-enterprise";
process.env.INPUT_OWNER = "test-owner";
const { default: promise } = await import("../main.js");
await promise;
@@ -0,0 +1,13 @@
import { DEFAULT_ENV } from "./main.js";
// Verify `main` exits with an error when `enterprise` is used with `repositories` input.
// Set up environment with enterprise and repositories set
for (const [key, value] of Object.entries(DEFAULT_ENV)) {
process.env[key] = value;
}
process.env.INPUT_ENTERPRISE = "test-enterprise";
process.env.INPUT_REPOSITORIES = "repo1,repo2";
const { default: promise } = await import("../main.js");
await promise;
@@ -0,0 +1,30 @@
import { test } from "./main.js";
// Verify `main` successfully obtains a token when only the `enterprise` input is set.
await test((mockPool) => {
process.env.INPUT_ENTERPRISE = "test-enterprise";
delete process.env.INPUT_OWNER;
delete process.env.INPUT_REPOSITORIES;
// Mock the enterprise installation endpoint
const mockInstallationId = "123456";
const mockAppSlug = "github-actions";
mockPool
.intercept({
path: "/enterprises/test-enterprise/installation",
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(
200,
{
id: mockInstallationId,
app_slug: mockAppSlug,
},
{ headers: { "content-type": "application/json" } }
);
});
@@ -0,0 +1,34 @@
import { test } from "./main.js";
// Use a declared enterprise permission from the generated schema to verify
// enterprise token requests forward permission inputs to token creation.
await test((mockPool) => {
process.env.INPUT_ENTERPRISE = "test-enterprise";
delete process.env.INPUT_OWNER;
delete process.env.INPUT_REPOSITORIES;
process.env[
"INPUT_PERMISSION-ENTERPRISE-CUSTOM-PROPERTIES-FOR-ORGANIZATIONS"
] = "read";
// Mock the enterprise installation endpoint
const mockInstallationId = "123456";
const mockAppSlug = "github-actions";
mockPool
.intercept({
path: "/enterprises/test-enterprise/installation",
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(
200,
{
id: mockInstallationId,
app_slug: mockAppSlug,
},
{ headers: { "content-type": "application/json" } }
);
});
@@ -0,0 +1,20 @@
import { DEFAULT_ENV } from "./main.js";
for (const [key, value] of Object.entries({
...DEFAULT_ENV,
"INPUT_CLIENT-ID": "",
"INPUT_APP-ID": "",
})) {
process.env[key] = value;
}
// Log only the error message, not the full stack trace, because the stack
// trace contains environment-specific paths and ANSI codes that differ
// between local and CI environments.
const _error = console.error;
console.error = (err) => _error(err?.message ?? err);
// Verify `main` exits with an error when neither `client-id` nor `app-id` is set.
const { default: promise } = await import("../main.js");
await promise;
process.exitCode = 0;
@@ -0,0 +1,23 @@
import { test } from "./main.js";
// Verify client errors are not retried when getting a token for a user or organization.
await test((mockPool) => {
process.env.INPUT_OWNER = "smockle";
delete process.env.INPUT_REPOSITORIES;
mockPool
.intercept({
path: "/users/smockle/installation",
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(
403,
{ message: "Forbidden" },
{ headers: { "content-type": "application/json" } },
);
});
@@ -0,0 +1,39 @@
import { test } from "./main.js";
// Verify transient network errors are retried when getting a repository token.
await test((mockPool) => {
process.env.INPUT_OWNER = "actions";
process.env.INPUT_REPOSITORIES = "network-repo";
const owner = process.env.INPUT_OWNER;
const repo = process.env.INPUT_REPOSITORIES;
const mockInstallationId = "123456";
const mockAppSlug = "github-actions";
mockPool
.intercept({
path: `/repos/${owner}/${repo}/installation`,
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.replyWithError(new TypeError("fetch failed"));
mockPool
.intercept({
path: `/repos/${owner}/${repo}/installation`,
method: "GET",
headers: {
accept: "application/vnd.github.v3+json",
"user-agent": "actions/create-github-app-token",
// Intentionally omitting the `authorization` header, since JWT creation is not idempotent.
},
})
.reply(
200,
{ id: mockInstallationId, app_slug: mockAppSlug },
{ headers: { "content-type": "application/json" } },
);
});
+3 -5
View File
@@ -1,6 +1,6 @@
// Base for all `main` tests.
// @ts-check
import { MockAgent, setGlobalDispatcher } from "undici";
import { createMockAgent } from "./mock-agent.js";
export const DEFAULT_ENV = {
GITHUB_REPOSITORY_OWNER: "actions",
@@ -9,7 +9,7 @@ export const DEFAULT_ENV = {
// https://docs.github.com/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
"INPUT_GITHUB-API-URL": "https://api.github.com",
"INPUT_SKIP-TOKEN-REVOKE": "false",
"INPUT_APP-ID": "123456",
"INPUT_CLIENT-ID": "Iv1.0123456789abcdef",
// This key is invalidated. Its from https://github.com/octokit/auth-app.js/issues/465#issuecomment-1564998327.
"INPUT_PRIVATE-KEY": `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA280nfuUM9w00Ib9E2rvZJ6Qu3Ua3IqR34ZlK53vn/Iobn2EL
@@ -50,9 +50,7 @@ export async function test(cb = (_mockPool) => {}, env = DEFAULT_ENV) {
// Set up mocking
const baseUrl = new URL(env["INPUT_GITHUB-API-URL"]);
const basePath = baseUrl.pathname === "/" ? "" : baseUrl.pathname;
const mockAgent = new MockAgent({ enableCallHistory: true });
mockAgent.disableNetConnect();
setGlobalDispatcher(mockAgent);
const mockAgent = createMockAgent({ enableCallHistory: true });
const mockPool = mockAgent.get(baseUrl.origin);
// Calling `auth({ type: "app" })` to obtain a JWT doesnt make network requests, so no need to intercept.
+12
View File
@@ -0,0 +1,12 @@
import { install, MockAgent, setGlobalDispatcher } from "undici";
// Ensure MockAgent intercepts requests made through global fetch.
install();
export function createMockAgent(options) {
const mockAgent = new MockAgent(options);
mockAgent.disableNetConnect();
setGlobalDispatcher(mockAgent);
return mockAgent;
}
@@ -1,4 +1,4 @@
import { MockAgent, setGlobalDispatcher } from "undici";
import { createMockAgent } from "./mock-agent.js";
// state variables are set as environment variables with the prefix STATE_
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions
@@ -14,9 +14,7 @@ process.env.STATE_expiresAt = new Date(
Date.now() + 1000 * 60 * 60
).toISOString();
const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent);
const mockAgent = createMockAgent();
// Provide the base url to the request
const mockPool = mockAgent.get("https://api.github.com");
+2 -4
View File
@@ -1,4 +1,4 @@
import { MockAgent, setGlobalDispatcher } from "undici";
import { createMockAgent } from "./mock-agent.js";
// state variables are set as environment variables with the prefix STATE_
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions
@@ -11,9 +11,7 @@ process.env.STATE_expiresAt = new Date(Date.now() - 1000 * 60 * 60).toISOString(
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
process.env["INPUT_SKIP-TOKEN-REVOKE"] = "false";
const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent);
const mockAgent = createMockAgent();
// Provide the base url to the request
const mockPool = mockAgent.get("https://api.github.com");
+2 -4
View File
@@ -1,4 +1,4 @@
import { MockAgent, setGlobalDispatcher } from "undici";
import { createMockAgent } from "./mock-agent.js";
// state variables are set as environment variables with the prefix STATE_
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions
@@ -12,9 +12,7 @@ process.env["INPUT_SKIP-TOKEN-REVOKE"] = "false";
// 1 hour in the future, not expired
process.env.STATE_expiresAt = new Date(Date.now() + 1000 * 60 * 60).toISOString();
const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent);
const mockAgent = createMockAgent();
// Provide the base url to the request
const mockPool = mockAgent.get("https://api.github.com");
+2 -4
View File
@@ -1,4 +1,4 @@
import { MockAgent, setGlobalDispatcher } from "undici";
import { createMockAgent } from "./mock-agent.js";
// state variables are set as environment variables with the prefix STATE_
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions
@@ -8,9 +8,7 @@ process.env.STATE_token = "secret123";
// https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#example-specifying-inputs
process.env["INPUT_SKIP-TOKEN-REVOKE"] = "true";
const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent);
const mockAgent = createMockAgent();
// Provide the base url to the request
const mockPool = mockAgent.get("https://api.github.com");