Compare commits

...

72 Commits

Author SHA1 Message Date
Spike Curtis f02a188f3c chore: refactor /insights API routes for testing 2025-12-01 12:48:23 +00:00
Pedro Magalhães d3fe72c861 chore: add ansible icon (#20887) 2025-12-01 15:43:23 +05:00
dependabot[bot] 163f495346 chore: bump @types/node from 20.17.16 to 20.19.25 in /site (#21003)
Bumps
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
from 20.17.16 to 20.19.25.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=20.17.16&new-version=20.19.25)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 10:33:15 +00:00
dependabot[bot] fad8b54ab8 ci: bump the github-actions group across 1 directory with 6 updates (#21017)
Bumps the github-actions group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `5.0.0` |
`6.0.0` |
| [crate-ci/typos](https://github.com/crate-ci/typos) | `1.39.2` |
`1.40.0` |
| [chromaui/action](https://github.com/chromaui/action) | `13.3.3` |
`13.3.4` |
| [fluxcd/flux2](https://github.com/fluxcd/flux2) | `2.7.3` | `2.7.5` |
|
[tj-actions/changed-files](https://github.com/tj-actions/changed-files)
| `70069877f29101175ed2b055d210fe8b1d54d7d7` |
`abdd2f68ea150cee8f236d4a9fb4e0f2491abf1b` |
| [github/codeql-action](https://github.com/github/codeql-action) |
`4.31.3` | `4.31.6` |


Updates `actions/checkout` from 5.0.0 to 6.0.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v6.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>v6-beta by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2298">actions/checkout#2298</a></li>
<li>update readme/changelog for v6 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2311">actions/checkout#2311</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v5.0.0...v6.0.0">https://github.com/actions/checkout/compare/v5.0.0...v6.0.0</a></p>
<h2>v6-beta</h2>
<h2>What's Changed</h2>
<p>Updated persist-credentials to store the credentials under
<code>$RUNNER_TEMP</code> instead of directly in the local git
config.</p>
<p>This requires a minimum Actions Runner version of <a
href="https://github.com/actions/runner/releases/tag/v2.329.0">v2.329.0</a>
to access the persisted credentials for <a
href="https://docs.github.com/en/actions/tutorials/use-containerized-services/create-a-docker-container-action">Docker
container action</a> scenarios.</p>
<h2>v5.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v5...v5.0.1">https://github.com/actions/checkout/compare/v5...v5.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>V6.0.0</h2>
<ul>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
</ul>
<h2>V5.0.1</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<h2>V5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>V4.3.1</h2>
<ul>
<li>Port v6 cleanup to v4 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li>
</ul>
<h2>V4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>v4.2.0</h2>
<ul>
<li>Add Ref and Commit outputs by <a
href="https://github.com/lucacome"><code>@​lucacome</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li>
<li>Dependency updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>- <a
href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>,
<a
href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li>
</ul>
<h2>v4.1.7</h2>
<ul>
<li>Bump the minor-npm-dependencies group across 1 directory with 4
updates by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1739">actions/checkout#1739</a></li>
<li>Bump actions/checkout from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1697">actions/checkout#1697</a></li>
<li>Check out other refs/* by commit by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li>
<li>Pin actions/checkout's own workflows to a known, good, stable
version. by <a href="https://github.com/jww3"><code>@​jww3</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1776">actions/checkout#1776</a></li>
</ul>
<h2>v4.1.6</h2>
<ul>
<li>Check platform to set archive extension appropriately by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1732">actions/checkout#1732</a></li>
</ul>
<h2>v4.1.5</h2>
<ul>
<li>Update NPM dependencies by <a
href="https://github.com/cory-miller"><code>@​cory-miller</code></a> in
<a
href="https://redirect.github.com/actions/checkout/pull/1703">actions/checkout#1703</a></li>
<li>Bump github/codeql-action from 2 to 3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1694">actions/checkout#1694</a></li>
<li>Bump actions/setup-node from 1 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1696">actions/checkout#1696</a></li>
<li>Bump actions/upload-artifact from 2 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1695">actions/checkout#1695</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/checkout/commit/1af3b93b6815bc44a9784bd300feb67ff0d1eeb3"><code>1af3b93</code></a>
update readme/changelog for v6 (<a
href="https://redirect.github.com/actions/checkout/issues/2311">#2311</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/71cf2267d89c5cb81562390fa70a37fa40b1305e"><code>71cf226</code></a>
v6-beta (<a
href="https://redirect.github.com/actions/checkout/issues/2298">#2298</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/069c6959146423d11cd0184e6accf28f9d45f06e"><code>069c695</code></a>
Persist creds to a separate file (<a
href="https://redirect.github.com/actions/checkout/issues/2286">#2286</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493"><code>ff7abcd</code></a>
Update README to include Node.js 24 support details and requirements (<a
href="https://redirect.github.com/actions/checkout/issues/2248">#2248</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/checkout/compare/08c6903cd8c0fde910a37f88322edcfb5dd907a8...1af3b93b6815bc44a9784bd300feb67ff0d1eeb3">compare
view</a></li>
</ul>
</details>
<br />

Updates `crate-ci/typos` from 1.39.2 to 1.40.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/releases">crate-ci/typos's
releases</a>.</em></p>
<blockquote>
<h2>v1.40.0</h2>
<h2>[1.40.0] - 2025-11-26</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1405">November
2025</a> changes</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/blob/master/CHANGELOG.md">crate-ci/typos's
changelog</a>.</em></p>
<blockquote>
<h1>Change Log</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<p>The format is based on <a href="https://keepachangelog.com/">Keep a
Changelog</a>
and this project adheres to <a href="https://semver.org/">Semantic
Versioning</a>.</p>
<!-- raw HTML omitted -->
<h2>[Unreleased] - ReleaseDate</h2>
<h2>[1.40.0] - 2025-11-26</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1405">November
2025</a> changes</li>
</ul>
<h2>[1.39.2] - 2025-11-13</h2>
<h3>Fixes</h3>
<ul>
<li>Don't offer <code>entry</code> as a correction for
<code>entrys</code></li>
</ul>
<h2>[1.39.1] - 2025-11-12</h2>
<h3>Features</h3>
<ul>
<li>Make <code>--help</code> more vibrant</li>
</ul>
<h2>[1.39.0] - 2025-10-31</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1383">October
2025</a> changes</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>When a typo is pluralized, prefer pluralized corrections</li>
</ul>
<h2>[1.38.1] - 2025-10-07</h2>
<h3>Fixes</h3>
<ul>
<li>Ignore common golang identifiers</li>
</ul>
<h2>[1.38.0] - 2025-10-06</h2>
<h3>Features</h3>
<ul>
<li>Update type list</li>
</ul>
<h3>Fixes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/crate-ci/typos/commit/2d0ce569feab1f8752f1dde43cc2f2aa53236e06"><code>2d0ce56</code></a>
chore: Release</li>
<li><a
href="https://github.com/crate-ci/typos/commit/efbd900f8db9952781b6bd1ad83baa38ebd153e3"><code>efbd900</code></a>
chore: Release</li>
<li><a
href="https://github.com/crate-ci/typos/commit/863fd15db8bf16bfe9117ec1a83ea7b6e802e9f3"><code>863fd15</code></a>
docs: Update changelog</li>
<li><a
href="https://github.com/crate-ci/typos/commit/9a27b16791dd73549457a07f247bc4920f787919"><code>9a27b16</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1432">#1432</a>
from epage/nov</li>
<li><a
href="https://github.com/crate-ci/typos/commit/3dbd9d4eacab7f22586ea581e4d403c3ca9dbbb7"><code>3dbd9d4</code></a>
feat(dict): November additions</li>
<li><a
href="https://github.com/crate-ci/typos/commit/a1a16c7b7c25d1c27cc750525aada8b9ae68b716"><code>a1a16c7</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1427">#1427</a>
from deining/bump-github-action</li>
<li><a
href="https://github.com/crate-ci/typos/commit/cb8d2e78ff23c82fca05340ed256b04513d022b9"><code>cb8d2e7</code></a>
docs: Bump GitHub checkout action in 'github-action.md'</li>
<li><a
href="https://github.com/crate-ci/typos/commit/9f99fb8dfe87b8a3441863fb2e5d5da88a2aa9a2"><code>9f99fb8</code></a>
docs(ref): Clarify extend-words / extend-identifiers</li>
<li>See full diff in <a
href="https://github.com/crate-ci/typos/compare/626c4bedb751ce0b7f03262ca97ddda9a076ae1c...2d0ce569feab1f8752f1dde43cc2f2aa53236e06">compare
view</a></li>
</ul>
</details>
<br />

Updates `chromaui/action` from 13.3.3 to 13.3.4
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/chromaui/action/commit/4c20b95e9d3209ecfdf9cd6aace6bbde71ba1694"><code>4c20b95</code></a>
v13.3.4</li>
<li>See full diff in <a
href="https://github.com/chromaui/action/compare/ac86f2ff0a458ffbce7b40698abd44c0fa34d4b6...4c20b95e9d3209ecfdf9cd6aace6bbde71ba1694">compare
view</a></li>
</ul>
</details>
<br />

Updates `fluxcd/flux2` from 2.7.3 to 2.7.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/fluxcd/flux2/releases">fluxcd/flux2's
releases</a>.</em></p>
<blockquote>
<h2>v2.7.5</h2>
<h2>Highlights</h2>
<p>Flux v2.7.5 is a patch release that comes with fixes to
helm-controller. Users are encouraged to upgrade for the best
experience.</p>
<p>ℹ️ Please follow the <a
href="https://github.com/fluxcd/flux2/discussions/5572">Upgrade
Procedure for Flux v2.7+</a> for a smooth upgrade from Flux v2.6 to the
latest version.</p>
<p>Fixes:</p>
<ul>
<li>Fix HelmRelease history truncation when using the
<code>RetryOnFailure</code> strategy.</li>
</ul>
<p>⚠️ Note that signature verification for OCI artifacts in
source-controller is not compatible with Cosign v3.
Flux users are advised to use <a
href="https://fluxcd.io/flux/flux-gh-action/#push-and-sign-kubernetes-manifests-to-container-registries">Cosign
v2.6</a> for signing Flux OCI artifacts and Helm charts, until support
for Cosign v3 is added in Flux v2.8.</p>
<h2>Components changelog</h2>
<ul>
<li>helm-controller <a
href="https://github.com/fluxcd/helm-controller/blob/v1.4.5/CHANGELOG.md">v1.4.5</a></li>
</ul>
<h2>CLI changelog</h2>
<ul>
<li>[release/v2.7.x] Update toolkit components by <a
href="https://github.com/fluxcdbot"><code>@​fluxcdbot</code></a> in <a
href="https://redirect.github.com/fluxcd/flux2/pull/5649">fluxcd/flux2#5649</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/fluxcd/flux2/compare/v2.7.4...v2.7.5">https://github.com/fluxcd/flux2/compare/v2.7.4...v2.7.5</a></p>
<h2>v2.7.4</h2>
<h2>Highlights</h2>
<p>Flux v2.7.4 is a patch release that comes with various fixes. Users
are encouraged to upgrade for the best experience.</p>
<p>ℹ️ Please follow the <a
href="https://github.com/fluxcd/flux2/discussions/5572">Upgrade
Procedure for Flux v2.7+</a> for a smooth upgrade from Flux v2.6 to the
latest version.</p>
<p>Fixes:</p>
<ul>
<li>Add <code>DisableConfigWatchers</code> feature gate to all
controllers for disabling the Secrets/ConfigMaps watchers</li>
<li>Fix Workload Identity for Azure China Cloud in all controllers</li>
<li>Update Helm Go SDK to v3.19.2 fixing schema validation issues in
helm-controller</li>
<li>Skip secret decryption for remote kustomize patches in
kustomize-controller</li>
<li>Improve post-build error reporting in kustomize-controller</li>
<li>Add <code>ArtifactGenerator</code> to aggregated RBAC roles</li>
</ul>
<p>⚠️ Note that signature verification for OCI artifacts in
source-controller is not compatible with Cosign v3.
Flux users are advised to use <a
href="https://fluxcd.io/flux/flux-gh-action/#push-and-sign-kubernetes-manifests-to-container-registries">Cosign
v2.6</a> for signing Flux OCI artifacts and Helm charts, until support
for Cosign v3 is added in Flux v2.8.</p>
<h2>Components changelog</h2>
<ul>
<li>source-controller <a
href="https://github.com/fluxcd/source-controller/blob/v1.7.4/CHANGELOG.md">v1.7.4</a></li>
<li>kustomize-controller <a
href="https://github.com/fluxcd/kustomize-controller/blob/v1.7.3/CHANGELOG.md">v1.7.3</a></li>
<li>notification-controller <a
href="https://github.com/fluxcd/notification-controller/blob/v1.7.5/CHANGELOG.md">v1.7.5</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/fluxcd/flux2/commit/8454b02a32e48d775b9f563cb51fdcb1787b5b93"><code>8454b02</code></a>
Merge pull request <a
href="https://redirect.github.com/fluxcd/flux2/issues/5649">#5649</a>
from fluxcd/backport-5648-to-release/v2.7.x</li>
<li><a
href="https://github.com/fluxcd/flux2/commit/931f101cb1cba9f7af4efa44ac0a5c7f634d9b06"><code>931f101</code></a>
Update toolkit components</li>
<li><a
href="https://github.com/fluxcd/flux2/commit/06ed49dcd3f3ce2fc8d1988848245873a91550bc"><code>06ed49d</code></a>
Merge pull request <a
href="https://redirect.github.com/fluxcd/flux2/issues/5642">#5642</a>
from fluxcd/backport-5597-to-release/v2.7.x</li>
<li><a
href="https://github.com/fluxcd/flux2/commit/6021981de38a416f214a97dd090a1346a20d9ad4"><code>6021981</code></a>
Allow option to skip tenant namespace creation</li>
<li><a
href="https://github.com/fluxcd/flux2/commit/4b7d46e511ca0efbea80e19fca675b18191a7508"><code>4b7d46e</code></a>
Merge pull request <a
href="https://redirect.github.com/fluxcd/flux2/issues/5640">#5640</a>
from fluxcd/backport-5639-to-release/v2.7.x</li>
<li><a
href="https://github.com/fluxcd/flux2/commit/e8c87047ba81d4bdacc3a33c443e93a35efedda0"><code>e8c8704</code></a>
Update toolkit components</li>
<li><a
href="https://github.com/fluxcd/flux2/commit/abd603eca72aac25d6a7b2da88e902ff798fbf61"><code>abd603e</code></a>
Merge pull request <a
href="https://redirect.github.com/fluxcd/flux2/issues/5635">#5635</a>
from fluxcd/backport-5625-to-release/v2.7.x</li>
<li><a
href="https://github.com/fluxcd/flux2/commit/83d426c3c04baba957c4aa7774990563e125f5d4"><code>83d426c</code></a>
diff: report if object is skipped</li>
<li><a
href="https://github.com/fluxcd/flux2/commit/48e77c820ecea23bb34fe51b1914bc41fd0830ac"><code>48e77c8</code></a>
Merge pull request <a
href="https://redirect.github.com/fluxcd/flux2/issues/5634">#5634</a>
from fluxcd/backport-5633-to-release/v2.7.x</li>
<li><a
href="https://github.com/fluxcd/flux2/commit/01fbe3763997a09e67ec30779aa9280cb74e8885"><code>01fbe37</code></a>
Upgrade k8s to 1.34.2, c-r to 0.22.4 and helm to 3.19.2</li>
<li>Additional commits viewable in <a
href="https://github.com/fluxcd/flux2/compare/b6e76ca2534f76dcb8dd94fb057cdfa923c3b641...8454b02a32e48d775b9f563cb51fdcb1787b5b93">compare
view</a></li>
</ul>
</details>
<br />

Updates `tj-actions/changed-files` from
70069877f29101175ed2b055d210fe8b1d54d7d7 to
abdd2f68ea150cee8f236d4a9fb4e0f2491abf1b
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tj-actions/changed-files/blob/main/HISTORY.md">tj-actions/changed-files's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h1><a
href="https://github.com/tj-actions/changed-files/compare/v46.0.5...v47.0.0">47.0.0</a>
- (2025-09-13)</h1>
<h2><!-- raw HTML omitted -->🚀 Features</h2>
<ul>
<li>Add any_added to outputs (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2567">#2567</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/c260d49a827b5eb266673bed7871c5d3ee9b5aef">c260d49</a>)
- (Jellyfrog)</li>
</ul>
<h2><!-- raw HTML omitted --> Remove</h2>
<ul>
<li>Commit and push step from build job (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2538">#2538</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/be393a90381e27c9fec2c8c2e02b00f005710145">be393a9</a>)
- (Tonye Jack)</li>
</ul>
<h2><!-- raw HTML omitted -->🔄 Update</h2>
<ul>
<li>Updated README.md (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2592">#2592</a>)</li>
</ul>
<p>Co-authored-by: github-actions[bot]
&lt;41898282+github-actions[bot]<a
href="https://github.com/users"><code>@​users</code></a>.noreply.github.com&gt;
(<a
href="https://github.com/tj-actions/changed-files/commit/3dbc1e181273d808ccff822a6e00cf18b6628ef0">3dbc1e1</a>)
- (github-actions[bot])</p>
<ul>
<li>Updated README.md (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2591">#2591</a>)</li>
</ul>
<p>Co-authored-by: github-actions[bot]
&lt;41898282+github-actions[bot]<a
href="https://github.com/users"><code>@​users</code></a>.noreply.github.com&gt;
(<a
href="https://github.com/tj-actions/changed-files/commit/b1ccff8c0892ad141d7d2de6f31e526a9dad931f">b1ccff8</a>)
- (github-actions[bot])</p>
<ul>
<li>Updated README.md (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2574">#2574</a>)</li>
</ul>
<p>Co-authored-by: github-actions[bot]
&lt;41898282+github-actions[bot]<a
href="https://github.com/users"><code>@​users</code></a>.noreply.github.com&gt;
(<a
href="https://github.com/tj-actions/changed-files/commit/050a3d3360d29711ee9d8210fc639d902d23ad07">050a3d3</a>)
- (github-actions[bot])</p>
<h2><!-- raw HTML omitted -->📚 Documentation</h2>
<ul>
<li>Update link to glob patterns (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2590">#2590</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/a892f50f7a7187bc288633c09230b09ce7ad8fd0">a892f50</a>)
- (Tonye Jack)</li>
<li>Add Jellyfrog as a contributor for code, and doc (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2573">#2573</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/f000a9b97f254f9590ff26f651cccde827ad36da">f000a9b</a>)
- (allcontributors[bot])</li>
</ul>
<h2><!-- raw HTML omitted -->🧪 Testing</h2>
<ul>
<li>Manual triggered workflows (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2637">#2637</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/c2ca2493190021783138cb8aac49bcee14b4bb89">c2ca249</a>)
- (Tonye Jack)</li>
</ul>
<h2><!-- raw HTML omitted -->⚙️ Miscellaneous Tasks</h2>
<ul>
<li><strong>deps-dev:</strong> Bump jest from 30.0.5 to 30.1.3 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2655">#2655</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/9a6755550a331fdcc8ec45443738933f8fa22eea">9a67555</a>)
- (dependabot[bot])</li>
<li><strong>deps:</strong> Bump tj-actions/git-cliff from 2.1.0 to 2.2.0
(<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2660">#2660</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/b67e30df88f43e244f4e83775e5ad8335114fb95">b67e30d</a>)
- (dependabot[bot])</li>
<li><strong>deps:</strong> Bump github/codeql-action from 3.30.2 to
3.30.3 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2661">#2661</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/62aef422ffa195474d80d73387535cf4622b2824">62aef42</a>)
- (dependabot[bot])</li>
<li><strong>deps:</strong> Bump github/codeql-action from 3.29.11 to
3.30.2 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2659">#2659</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/e874f3cddd0f54ae776e6995ae6dae4cf40fd3d3">e874f3c</a>)
- (dependabot[bot])</li>
<li><strong>deps:</strong> Bump actions/setup-node from 4.4.0 to 5.0.0
(<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2656">#2656</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/8c14441336bb3d84fd6b7fa83b6d7201c740baf5">8c14441</a>)
- (dependabot[bot])</li>
<li><strong>deps-dev:</strong> Bump <code>@​types/node</code> from
24.3.0 to 24.3.1 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2657">#2657</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/e995ac4be5be2bcb6e29556edc51fb63aca6b49b">e995ac4</a>)
- (dependabot[bot])</li>
<li><strong>deps-dev:</strong> Bump <code>@​types/node</code> from
24.2.1 to 24.3.0 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2649">#2649</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/3b04099b21072562f07469c10deb182b24236ca9">3b04099</a>)
- (dependabot[bot])</li>
<li><strong>deps:</strong> Bump github/codeql-action from 3.29.9 to
3.29.11 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2651">#2651</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/e7b6c977e51984988e3cc1d6b18abe2a3ba8daaa">e7b6c97</a>)
- (dependabot[bot])</li>
<li><strong>deps:</strong> Bump tj-actions/git-cliff from 2.0.2 to 2.1.0
(<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2648">#2648</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/765d62bc041415a5b494ef13d02d566128b25973">765d62b</a>)
- (dependabot[bot])</li>
<li><strong>deps:</strong> Bump github/codeql-action from 3.29.8 to
3.29.9 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2647">#2647</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/2036da178f85576f1940fedb74bb93a36cd89ab7">2036da1</a>)
- (dependabot[bot])</li>
<li><strong>deps:</strong> Bump github/codeql-action from 3.29.7 to
3.29.8 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2644">#2644</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/239aef84a5502c79a1cea96e495d17588c66c659">239aef8</a>)
- (dependabot[bot])</li>
<li><strong>deps-dev:</strong> Bump <code>@​types/node</code> from
24.2.0 to 24.2.1 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2645">#2645</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/a7d5f5f4919b6dbc6d3a3689887964361e8dd88f">a7d5f5f</a>)
- (dependabot[bot])</li>
<li><strong>deps:</strong> Bump actions/checkout from 4.2.2 to 5.0.0 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2646">#2646</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/5107f3abcc0c3737db51e2949f181e2c197d4d5b">5107f3a</a>)
- (dependabot[bot])</li>
<li><strong>deps-dev:</strong> Bump <code>@​types/node</code> from
24.1.0 to 24.2.0 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2640">#2640</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/f963b3f3562b00b6d2dd25efc390eb04e51ef6c6">f963b3f</a>)
- (dependabot[bot])</li>
<li><strong>deps:</strong> Bump actions/download-artifact from 4.3.0 to
5.0.0 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2641">#2641</a>)
(<a
href="https://github.com/tj-actions/changed-files/commit/f956744105e18d78bba3844a1199ce43d6503017">f956744</a>)
- (dependabot[bot])</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tj-actions/changed-files/commit/abdd2f68ea150cee8f236d4a9fb4e0f2491abf1b"><code>abdd2f6</code></a>
chore(deps): bump peter-evans/create-pull-request from 7.0.8 to 7.0.9
(<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2717">#2717</a>)</li>
<li><a
href="https://github.com/tj-actions/changed-files/commit/1d08ab228c06cfdb5c92a6be6fb2e03654e2410c"><code>1d08ab2</code></a>
chore(deps-dev): bump <code>@​types/lodash</code> from 4.17.20 to
4.17.21 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2718">#2718</a>)</li>
<li><a
href="https://github.com/tj-actions/changed-files/commit/085eb10d7ef84ec5ab690becdd4072966e781924"><code>085eb10</code></a>
chore(deps-dev): bump eslint-plugin-jest from 29.1.0 to 29.2.1 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2719">#2719</a>)</li>
<li><a
href="https://github.com/tj-actions/changed-files/commit/72321d1f003ac6191c4d5c7781eed4241a728447"><code>72321d1</code></a>
chore(deps): bump github/codeql-action from 4.31.4 to 4.31.5 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2720">#2720</a>)</li>
<li><a
href="https://github.com/tj-actions/changed-files/commit/8c66df4f78c44069a58c4517f775d02211a2b06b"><code>8c66df4</code></a>
chore(deps-dev): bump prettier from 3.6.2 to 3.7.1 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2722">#2722</a>)</li>
<li><a
href="https://github.com/tj-actions/changed-files/commit/6da3c88b60ebf09464ada9b06fba5b6f2d34bb94"><code>6da3c88</code></a>
chore(deps): bump nrwl/nx-set-shas from 4.3.3 to 4.4.0 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2712">#2712</a>)</li>
<li><a
href="https://github.com/tj-actions/changed-files/commit/48b6e9756da9107e9b08fcb676b292caaac1140b"><code>48b6e97</code></a>
chore(deps): bump actions/checkout from 5.0.0 to 5.0.1 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2714">#2714</a>)</li>
<li><a
href="https://github.com/tj-actions/changed-files/commit/4206138f174379c379efaa0f5c1d1f82a96eac0a"><code>4206138</code></a>
Update README.md</li>
<li><a
href="https://github.com/tj-actions/changed-files/commit/31e663cd71ad1f011b9f9e87f3a710e5ebb9a658"><code>31e663c</code></a>
chore(deps): bump github/codeql-action from 4.31.2 to 4.31.4 (<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2715">#2715</a>)</li>
<li><a
href="https://github.com/tj-actions/changed-files/commit/46fd332157a59040c95f13a55162240a4b789b6c"><code>46fd332</code></a>
chore(deps-dev): bump <code>@​types/node</code> from 24.10.0 to 24.10.1
(<a
href="https://redirect.github.com/tj-actions/changed-files/issues/2711">#2711</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tj-actions/changed-files/compare/70069877f29101175ed2b055d210fe8b1d54d7d7...abdd2f68ea150cee8f236d4a9fb4e0f2491abf1b">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action` from 4.31.3 to 4.31.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.31.6</h2>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>4.31.6 - 01 Dec 2025</h2>
<p>No user facing changes.</p>
<p>See the full <a
href="https://github.com/github/codeql-action/blob/v4.31.6/CHANGELOG.md">CHANGELOG.md</a>
for more information.</p>
<h2>v4.31.5</h2>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>4.31.5 - 24 Nov 2025</h2>
<ul>
<li>Update default CodeQL bundle version to 2.23.6. <a
href="https://redirect.github.com/github/codeql-action/pull/3321">#3321</a></li>
</ul>
<p>See the full <a
href="https://github.com/github/codeql-action/blob/v4.31.5/CHANGELOG.md">CHANGELOG.md</a>
for more information.</p>
<h2>v4.31.4</h2>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>4.31.4 - 18 Nov 2025</h2>
<p>No user facing changes.</p>
<p>See the full <a
href="https://github.com/github/codeql-action/blob/v4.31.4/CHANGELOG.md">CHANGELOG.md</a>
for more information.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.31.6 - 01 Dec 2025</h2>
<p>No user facing changes.</p>
<h2>4.31.5 - 24 Nov 2025</h2>
<ul>
<li>Update default CodeQL bundle version to 2.23.6. <a
href="https://redirect.github.com/github/codeql-action/pull/3321">#3321</a></li>
</ul>
<h2>4.31.4 - 18 Nov 2025</h2>
<p>No user facing changes.</p>
<h2>4.31.3 - 13 Nov 2025</h2>
<ul>
<li>CodeQL Action v3 will be deprecated in December 2026. The Action now
logs a warning for customers who are running v3 but could be running v4.
For more information, see <a
href="https://github.blog/changelog/2025-10-28-upcoming-deprecation-of-codeql-action-v3/">Upcoming
deprecation of CodeQL Action v3</a>.</li>
<li>Update default CodeQL bundle version to 2.23.5. <a
href="https://redirect.github.com/github/codeql-action/pull/3288">#3288</a></li>
</ul>
<h2>4.31.2 - 30 Oct 2025</h2>
<p>No user facing changes.</p>
<h2>4.31.1 - 30 Oct 2025</h2>
<ul>
<li>The <code>add-snippets</code> input has been removed from the
<code>analyze</code> action. This input has been deprecated since CodeQL
Action 3.26.4 in August 2024 when this removal was announced.</li>
</ul>
<h2>4.31.0 - 24 Oct 2025</h2>
<ul>
<li>Bump minimum CodeQL bundle version to 2.17.6. <a
href="https://redirect.github.com/github/codeql-action/pull/3223">#3223</a></li>
<li>When SARIF files are uploaded by the <code>analyze</code> or
<code>upload-sarif</code> actions, the CodeQL Action automatically
performs post-processing steps to prepare the data for the upload.
Previously, these post-processing steps were only performed before an
upload took place. We are now changing this so that the post-processing
steps will always be performed, even when the SARIF files are not
uploaded. This does not change anything for the
<code>upload-sarif</code> action. For <code>analyze</code>, this may
affect Advanced Setup for CodeQL users who specify a value other than
<code>always</code> for the <code>upload</code> input. <a
href="https://redirect.github.com/github/codeql-action/pull/3222">#3222</a></li>
</ul>
<h2>4.30.9 - 17 Oct 2025</h2>
<ul>
<li>Update default CodeQL bundle version to 2.23.3. <a
href="https://redirect.github.com/github/codeql-action/pull/3205">#3205</a></li>
<li>Experimental: A new <code>setup-codeql</code> action has been added
which is similar to <code>init</code>, except it only installs the
CodeQL CLI and does not initialize a database. Do not use this in
production as it is part of an internal experiment and subject to change
at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3204">#3204</a></li>
</ul>
<h2>4.30.8 - 10 Oct 2025</h2>
<p>No user facing changes.</p>
<h2>4.30.7 - 06 Oct 2025</h2>
<ul>
<li>[v4+ only] The CodeQL Action now runs on Node.js v24. <a
href="https://redirect.github.com/github/codeql-action/pull/3169">#3169</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/fe4161a26a8629af62121b670040955b330f9af2"><code>fe4161a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3336">#3336</a>
from github/update-v4.31.6-ecec1f887</li>
<li><a
href="https://github.com/github/codeql-action/commit/88c2ab5eee3b475eef2f7aabf89bd9f052153d91"><code>88c2ab5</code></a>
Update changelog for v4.31.6</li>
<li><a
href="https://github.com/github/codeql-action/commit/ecec1f88769052ebc45aa0affc53ea30d474cffa"><code>ecec1f8</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3335">#3335</a>
from github/mbg/ci/run-codeql-on-all-prs</li>
<li><a
href="https://github.com/github/codeql-action/commit/23da73277866951560f258278028b48f68958a0a"><code>23da732</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3334">#3334</a>
from github/kaspersv/overlay-minor-comments</li>
<li><a
href="https://github.com/github/codeql-action/commit/f7abc748a3da068e17cfd0e1086e8d72e51f17b6"><code>f7abc74</code></a>
Remove branch filter for PR event in CodeQL workflow</li>
<li><a
href="https://github.com/github/codeql-action/commit/32ada5e061c0433b9e40f11632c2412a55b745f9"><code>32ada5e</code></a>
Merge branch 'main' into kaspersv/overlay-minor-comments</li>
<li><a
href="https://github.com/github/codeql-action/commit/75b2f49aeaf4e8a9eab338ddc5d628eea7366eeb"><code>75b2f49</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3333">#3333</a>
from github/kaspersv/overlay-no-resource-checks-option</li>
<li><a
href="https://github.com/github/codeql-action/commit/f036b1cb781fa664100fee1f7c56a0088663dd26"><code>f036b1c</code></a>
Merge branch 'main' into kaspersv/overlay-no-resource-checks-option</li>
<li><a
href="https://github.com/github/codeql-action/commit/58c5954801c246a3975b658372285b37c45de271"><code>58c5954</code></a>
Add comment to runnerSupportsOverlayAnalysis</li>
<li><a
href="https://github.com/github/codeql-action/commit/b02fa13292ce189c02cbb1ba5488f7dbbc8c6b14"><code>b02fa13</code></a>
Order feature flags alphabetically</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/014f16e7ab1402f30e7c3329d33797e7948572db...fe4161a26a8629af62121b670040955b330f9af2">compare
view</a></li>
</ul>
</details>
<br />

<details>
<summary>Most Recent Ignore Conditions Applied to This Pull
Request</summary>

| Dependency Name | Ignore Conditions |
| --- | --- |
| crate-ci/typos | [>= 1.30.a, < 1.31] |
</details>


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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 10:32:29 +00:00
dependabot[bot] ffc3e8151a chore: bump @storybook/addon-docs from 9.1.2 to 9.1.16 in /site (#21000)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps
[@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs)
from 9.1.2 to 9.1.16.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/storybookjs/storybook/releases"><code>@​storybook/addon-docs</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v9.1.16</h2>
<h2>9.1.16</h2>
<ul>
<li>CLI: Fix Nextjs project creation in empty directories - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32828">#32828</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Core: Add `experimental_devServer` preset - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32862">#32862</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Telemetry: Fix preview-first-load event - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32859">#32859</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>v9.1.15</h2>
<h2>9.1.15</h2>
<ul>
<li>Core: Add `preview-first-load` telemetry - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32770">#32770</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
<li>Dependencies: Update `vite-plugin-storybook-nextjs` - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32821">#32821</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<h2>v9.1.14</h2>
<h2>9.1.14</h2>
<ul>
<li>NextJS: Add NextJS 16 support - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32791">#32791</a>,
thanks <a href="https://github.com/yannbf"><code>@​yannbf</code></a> and
<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>Addon-Vitest: Support Vitest 4 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32819">#32819</a>,
thanks <a href="https://github.com/yannbf"><code>@​yannbf</code></a> and
<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>CSF: Fix `play-fn` tag for methods - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32695">#32695</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>v9.1.12</h2>
<h2>9.1.12</h2>
<ul>
<li>Maintenance: Hotfix for missing nextjs dts files, thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<h2>v9.1.11</h2>
<h2>9.1.11</h2>
<ul>
<li>Automigration: Improve the viewport/backgrounds automigration - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32619">#32619</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Mocking: Fix `sb.mock` usage in Storybook's deployed in subpaths -
<a
href="https://redirect.github.com/storybookjs/storybook/pull/32678">#32678</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>NextJS-Vite: Automatically fix bad PostCSS configuration - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32691">#32691</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>React Native Web: Fix REACT_NATIVE_AND_RNW should detect vite
builder - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32718">#32718</a>,
thanks <a
href="https://github.com/dannyhw"><code>@​dannyhw</code></a>!</li>
<li>Telemetry: Add metadata for react routers - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32615">#32615</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>v9.1.10</h2>
<h2>9.1.10</h2>
<ul>
<li>Automigrations: Add automigration for viewport and backgrounds - <a
href="https://redirect.github.com/storybookjs/storybook/pull/31614">#31614</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Telemetry: Log userAgent in onboarding - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32566">#32566</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>v9.1.9</h2>
<h2>9.1.9</h2>
<ul>
<li>Angular: Enable experimental zoneless detection on Angular v21 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32580">#32580</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Svelte: Ignore inherited <code>HTMLAttributes</code> docgen when
using utility types - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32173">#32173</a>,
thanks <a
href="https://github.com/steciuk"><code>@​steciuk</code></a>!</li>
</ul>
<h2>v9.1.8</h2>
<h2>9.1.8</h2>
<ul>
<li>PreactVite: Add <code>node</code> entry point - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32534">#32534</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md"><code>@​storybook/addon-docs</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>9.1.16</h2>
<ul>
<li>CLI: Fix Nextjs project creation in empty directories - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32828">#32828</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Core: Add <code>experimental_devServer</code> preset - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32862">#32862</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Telemetry: Fix preview-first-load event - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32859">#32859</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>9.1.15</h2>
<ul>
<li>Core: Add <code>preview-first-load</code> telemetry - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32770">#32770</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
<li>Dependencies: Update <code>vite-plugin-storybook-nextjs</code> - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32821">#32821</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<h2>9.1.14</h2>
<ul>
<li>NextJS: Add NextJS 16 support - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32791">#32791</a>,
thanks <a href="https://github.com/yannbf"><code>@​yannbf</code></a> and
<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>Addon-Vitest: Support Vitest 4 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32819">#32819</a>,
thanks <a href="https://github.com/yannbf"><code>@​yannbf</code></a> and
<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>CSF: Fix <code>play-fn</code> tag for methods - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32695">#32695</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>9.1.13</h2>
<ul>
<li>Nextjs: Fix config access for Vite - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32759">#32759</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
</ul>
<h2>9.1.12</h2>
<ul>
<li>Maintenance: Hotfix for missing nextjs dts files, thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<h2>9.1.11</h2>
<ul>
<li>Automigration: Improve the viewport/backgrounds automigration - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32619">#32619</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Mocking: Fix <code>sb.mock</code> usage in Storybook's deployed in
subpaths - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32678">#32678</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>NextJS-Vite: Automatically fix bad PostCSS configuration - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32691">#32691</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>React Native Web: Fix REACT_NATIVE_AND_RNW should detect vite
builder - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32718">#32718</a>,
thanks <a
href="https://github.com/dannyhw"><code>@​dannyhw</code></a>!</li>
<li>Telemetry: Add metadata for react routers - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32615">#32615</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>9.1.10</h2>
<ul>
<li>Automigrations: Add automigration for viewport and backgrounds - <a
href="https://redirect.github.com/storybookjs/storybook/pull/31614">#31614</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Telemetry: Log userAgent in onboarding - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32566">#32566</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>9.1.9</h2>
<ul>
<li>Angular: Enable experimental zoneless detection on Angular v21 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32580">#32580</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Svelte: Ignore inherited <code>HTMLAttributes</code> docgen when
using utility types - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32173">#32173</a>,
thanks <a
href="https://github.com/steciuk"><code>@​steciuk</code></a>!</li>
</ul>
<h2>9.1.8</h2>
<ul>
<li>PreactVite: Add <code>node</code> entry point - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32534">#32534</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<h2>9.1.7</h2>
<ul>
<li>Dependencies: Update <code>vite-plugin-storybook-nextjs</code> to
2.0.7 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32331">#32331</a>,
thanks <a href="https://github.com/k35o"><code>@​k35o</code></a>!</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/storybookjs/storybook/commit/a54a04cef3ea631f2dacf3631f7f78e4453cd096"><code>a54a04c</code></a>
Bump version from &quot;9.1.15&quot; to &quot;9.1.16&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/d0d17d96288be91ae0969803cbfcd7849b9c98f8"><code>d0d17d9</code></a>
Bump version from &quot;9.1.14&quot; to &quot;9.1.15&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/5afb39f85e981d380fba4658a82fac24fa5ce51b"><code>5afb39f</code></a>
Bump version from &quot;9.1.13&quot; to &quot;9.1.14&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/c2483f0e584fea0a7b4c306489b506f6165dc73b"><code>c2483f0</code></a>
Bump version from &quot;9.1.12&quot; to &quot;9.1.13&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/efe8a7ce5bf67cdef96bb4f34a787ef0d6152745"><code>efe8a7c</code></a>
Bump version from &quot;9.1.11&quot; to &quot;9.1.12&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/5b2e0edf9f1b56a4708721578be83d439ebc59f5"><code>5b2e0ed</code></a>
Bump version from &quot;9.1.10&quot; to &quot;9.1.11&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/642f0cf47ed9463cecd67fdbad978113edc88196"><code>642f0cf</code></a>
Bump version from &quot;9.1.9&quot; to &quot;9.1.10&quot; [skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/01867d0d587fe23765bbd43397d861a6e08223f8"><code>01867d0</code></a>
Bump version from &quot;9.1.8&quot; to &quot;9.1.9&quot; [skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/28833d41b8d0e33bdc11244907fa8d14c8ddd1bf"><code>28833d4</code></a>
Bump version from &quot;9.1.7&quot; to &quot;9.1.8&quot; [skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/006b304feb4cb0d2fd1408505ebeb5aababb0aad"><code>006b304</code></a>
Bump version from &quot;9.1.6&quot; to &quot;9.1.7&quot; [skip ci]</li>
<li>Additional commits viewable in <a
href="https://github.com/storybookjs/storybook/commits/v9.1.16/code/addons/docs">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>@​storybook/addon-docs</code> since
your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@storybook/addon-docs&package-manager=npm_and_yarn&previous-version=9.1.2&new-version=9.1.16)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 10:04:55 +00:00
dependabot[bot] 61b95314ae chore: bump @storybook/addon-themes from 9.1.2 to 9.1.16 in /site (#21005)
Bumps
[@storybook/addon-themes](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/themes)
from 9.1.2 to 9.1.16.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/storybookjs/storybook/releases"><code>@​storybook/addon-themes</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v9.1.16</h2>
<h2>9.1.16</h2>
<ul>
<li>CLI: Fix Nextjs project creation in empty directories - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32828">#32828</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Core: Add `experimental_devServer` preset - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32862">#32862</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Telemetry: Fix preview-first-load event - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32859">#32859</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>v9.1.15</h2>
<h2>9.1.15</h2>
<ul>
<li>Core: Add `preview-first-load` telemetry - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32770">#32770</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
<li>Dependencies: Update `vite-plugin-storybook-nextjs` - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32821">#32821</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<h2>v9.1.14</h2>
<h2>9.1.14</h2>
<ul>
<li>NextJS: Add NextJS 16 support - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32791">#32791</a>,
thanks <a href="https://github.com/yannbf"><code>@​yannbf</code></a> and
<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>Addon-Vitest: Support Vitest 4 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32819">#32819</a>,
thanks <a href="https://github.com/yannbf"><code>@​yannbf</code></a> and
<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>CSF: Fix `play-fn` tag for methods - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32695">#32695</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>v9.1.12</h2>
<h2>9.1.12</h2>
<ul>
<li>Maintenance: Hotfix for missing nextjs dts files, thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<h2>v9.1.11</h2>
<h2>9.1.11</h2>
<ul>
<li>Automigration: Improve the viewport/backgrounds automigration - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32619">#32619</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Mocking: Fix `sb.mock` usage in Storybook's deployed in subpaths -
<a
href="https://redirect.github.com/storybookjs/storybook/pull/32678">#32678</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>NextJS-Vite: Automatically fix bad PostCSS configuration - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32691">#32691</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>React Native Web: Fix REACT_NATIVE_AND_RNW should detect vite
builder - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32718">#32718</a>,
thanks <a
href="https://github.com/dannyhw"><code>@​dannyhw</code></a>!</li>
<li>Telemetry: Add metadata for react routers - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32615">#32615</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>v9.1.10</h2>
<h2>9.1.10</h2>
<ul>
<li>Automigrations: Add automigration for viewport and backgrounds - <a
href="https://redirect.github.com/storybookjs/storybook/pull/31614">#31614</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Telemetry: Log userAgent in onboarding - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32566">#32566</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>v9.1.9</h2>
<h2>9.1.9</h2>
<ul>
<li>Angular: Enable experimental zoneless detection on Angular v21 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32580">#32580</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Svelte: Ignore inherited <code>HTMLAttributes</code> docgen when
using utility types - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32173">#32173</a>,
thanks <a
href="https://github.com/steciuk"><code>@​steciuk</code></a>!</li>
</ul>
<h2>v9.1.8</h2>
<h2>9.1.8</h2>
<ul>
<li>PreactVite: Add <code>node</code> entry point - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32534">#32534</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md"><code>@​storybook/addon-themes</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>9.1.16</h2>
<ul>
<li>CLI: Fix Nextjs project creation in empty directories - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32828">#32828</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Core: Add <code>experimental_devServer</code> preset - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32862">#32862</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Telemetry: Fix preview-first-load event - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32859">#32859</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>9.1.15</h2>
<ul>
<li>Core: Add <code>preview-first-load</code> telemetry - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32770">#32770</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
<li>Dependencies: Update <code>vite-plugin-storybook-nextjs</code> - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32821">#32821</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<h2>9.1.14</h2>
<ul>
<li>NextJS: Add NextJS 16 support - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32791">#32791</a>,
thanks <a href="https://github.com/yannbf"><code>@​yannbf</code></a> and
<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>Addon-Vitest: Support Vitest 4 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32819">#32819</a>,
thanks <a href="https://github.com/yannbf"><code>@​yannbf</code></a> and
<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>CSF: Fix <code>play-fn</code> tag for methods - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32695">#32695</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>9.1.13</h2>
<ul>
<li>Nextjs: Fix config access for Vite - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32759">#32759</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
</ul>
<h2>9.1.12</h2>
<ul>
<li>Maintenance: Hotfix for missing nextjs dts files, thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<h2>9.1.11</h2>
<ul>
<li>Automigration: Improve the viewport/backgrounds automigration - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32619">#32619</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Mocking: Fix <code>sb.mock</code> usage in Storybook's deployed in
subpaths - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32678">#32678</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>NextJS-Vite: Automatically fix bad PostCSS configuration - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32691">#32691</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
<li>React Native Web: Fix REACT_NATIVE_AND_RNW should detect vite
builder - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32718">#32718</a>,
thanks <a
href="https://github.com/dannyhw"><code>@​dannyhw</code></a>!</li>
<li>Telemetry: Add metadata for react routers - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32615">#32615</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>9.1.10</h2>
<ul>
<li>Automigrations: Add automigration for viewport and backgrounds - <a
href="https://redirect.github.com/storybookjs/storybook/pull/31614">#31614</a>,
thanks <a
href="https://github.com/valentinpalkovic"><code>@​valentinpalkovic</code></a>!</li>
<li>Telemetry: Log userAgent in onboarding - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32566">#32566</a>,
thanks <a
href="https://github.com/shilman"><code>@​shilman</code></a>!</li>
</ul>
<h2>9.1.9</h2>
<ul>
<li>Angular: Enable experimental zoneless detection on Angular v21 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32580">#32580</a>,
thanks <a
href="https://github.com/yannbf"><code>@​yannbf</code></a>!</li>
<li>Svelte: Ignore inherited <code>HTMLAttributes</code> docgen when
using utility types - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32173">#32173</a>,
thanks <a
href="https://github.com/steciuk"><code>@​steciuk</code></a>!</li>
</ul>
<h2>9.1.8</h2>
<ul>
<li>PreactVite: Add <code>node</code> entry point - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32534">#32534</a>,
thanks <a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>!</li>
</ul>
<h2>9.1.7</h2>
<ul>
<li>Dependencies: Update <code>vite-plugin-storybook-nextjs</code> to
2.0.7 - <a
href="https://redirect.github.com/storybookjs/storybook/pull/32331">#32331</a>,
thanks <a href="https://github.com/k35o"><code>@​k35o</code></a>!</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/storybookjs/storybook/commit/a54a04cef3ea631f2dacf3631f7f78e4453cd096"><code>a54a04c</code></a>
Bump version from &quot;9.1.15&quot; to &quot;9.1.16&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/d0d17d96288be91ae0969803cbfcd7849b9c98f8"><code>d0d17d9</code></a>
Bump version from &quot;9.1.14&quot; to &quot;9.1.15&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/5afb39f85e981d380fba4658a82fac24fa5ce51b"><code>5afb39f</code></a>
Bump version from &quot;9.1.13&quot; to &quot;9.1.14&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/c2483f0e584fea0a7b4c306489b506f6165dc73b"><code>c2483f0</code></a>
Bump version from &quot;9.1.12&quot; to &quot;9.1.13&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/efe8a7ce5bf67cdef96bb4f34a787ef0d6152745"><code>efe8a7c</code></a>
Bump version from &quot;9.1.11&quot; to &quot;9.1.12&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/5b2e0edf9f1b56a4708721578be83d439ebc59f5"><code>5b2e0ed</code></a>
Bump version from &quot;9.1.10&quot; to &quot;9.1.11&quot; [skip
ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/642f0cf47ed9463cecd67fdbad978113edc88196"><code>642f0cf</code></a>
Bump version from &quot;9.1.9&quot; to &quot;9.1.10&quot; [skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/01867d0d587fe23765bbd43397d861a6e08223f8"><code>01867d0</code></a>
Bump version from &quot;9.1.8&quot; to &quot;9.1.9&quot; [skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/28833d41b8d0e33bdc11244907fa8d14c8ddd1bf"><code>28833d4</code></a>
Bump version from &quot;9.1.7&quot; to &quot;9.1.8&quot; [skip ci]</li>
<li><a
href="https://github.com/storybookjs/storybook/commit/006b304feb4cb0d2fd1408505ebeb5aababb0aad"><code>006b304</code></a>
Bump version from &quot;9.1.6&quot; to &quot;9.1.7&quot; [skip ci]</li>
<li>Additional commits viewable in <a
href="https://github.com/storybookjs/storybook/commits/v9.1.16/code/addons/themes">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>@​storybook/addon-themes</code> since
your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@storybook/addon-themes&package-manager=npm_and_yarn&previous-version=9.1.2&new-version=9.1.16)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:56:38 +00:00
dependabot[bot] 2530471922 chore: bump the react group across 1 directory with 2 updates (#20994)
Bumps the react group with 2 updates in the /site directory:
[@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react)
and
[@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom).

Updates `@types/react` from 19.2.2 to 19.2.7
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react">compare
view</a></li>
</ul>
</details>
<br />

Updates `@types/react-dom` from 19.2.2 to 19.2.3
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom">compare
view</a></li>
</ul>
</details>
<br />


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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:55:29 +00:00
dependabot[bot] fe963369ce chore: bump @types/node from 20.19.24 to 20.19.25 in /offlinedocs (#20998)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps
[@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)
from 20.19.24 to 20.19.25.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=20.19.24&new-version=20.19.25)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:45:57 +00:00
dependabot[bot] fc3e0e834f chore: bump @chromatic-com/storybook from 4.1.0 to 4.1.3 in /site (#21002)
Bumps
[@chromatic-com/storybook](https://github.com/chromaui/addon-visual-tests)
from 4.1.0 to 4.1.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/chromaui/addon-visual-tests/releases"><code>@​chromatic-com/storybook</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v4.1.3</h2>
<h4>🐛 Bug Fix</h4>
<ul>
<li>Upgrade <code>chromatic</code> to v13.3.3 <a
href="https://redirect.github.com/chromaui/addon-visual-tests/pull/394">#394</a>
(<a
href="https://github.com/ghengeveld"><code>@​ghengeveld</code></a>)</li>
</ul>
<h4>Authors: 1</h4>
<ul>
<li>Gert Hengeveld (<a
href="https://github.com/ghengeveld"><code>@​ghengeveld</code></a>)</li>
</ul>
<h2>v4.1.3-next.0</h2>
<h4>🐛 Bug Fix</h4>
<ul>
<li>Upgrade <code>chromatic</code> to v13.3.3 <a
href="https://redirect.github.com/chromaui/addon-visual-tests/pull/394">#394</a>
(<a
href="https://github.com/ghengeveld"><code>@​ghengeveld</code></a>)</li>
</ul>
<h4>Authors: 1</h4>
<ul>
<li>Gert Hengeveld (<a
href="https://github.com/ghengeveld"><code>@​ghengeveld</code></a>)</li>
</ul>
<h2>v4.1.2</h2>
<h4>🐛 Bug Fix</h4>
<ul>
<li>Broaden version-range for storybook peerDependency to include
10.1.0-0 <a
href="https://redirect.github.com/chromaui/addon-visual-tests/pull/392">#392</a>
(<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
</ul>
<h4>Authors: 2</h4>
<ul>
<li><a
href="https://github.com/chromatic-support"><code>@​chromatic-support</code></a></li>
<li>Norbert de Langen (<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
</ul>
<h2>v4.1.2-next.4</h2>
<h4>⚠️ Pushed to <code>next</code></h4>
<ul>
<li>Broaden version-range for storybook peerDependency to include
10.2.0-0 and 10.3.0-0 (<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
</ul>
<h4>Authors: 1</h4>
<ul>
<li>Norbert de Langen (<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
</ul>
<h2>v4.1.2-next.3</h2>
<h4>⚠️ Pushed to <code>next</code></h4>
<ul>
<li>Update GitHub Actions workflow to fetch full git history and tags
with optimized settings (<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
</ul>
<h4>Authors: 1</h4>
<ul>
<li>Norbert de Langen (<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
</ul>
<h2>v4.1.2-next.2</h2>
<h4>⚠️ Pushed to <code>next</code></h4>
<ul>
<li>bump yarn version (<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/chromaui/addon-visual-tests/blob/v4.1.3/CHANGELOG.md"><code>@​chromatic-com/storybook</code>'s
changelog</a>.</em></p>
<blockquote>
<h1>v4.1.3 (Mon Nov 17 2025)</h1>
<h4>🐛 Bug Fix</h4>
<ul>
<li>Upgrade <code>chromatic</code> to v13.3.3 <a
href="https://redirect.github.com/chromaui/addon-visual-tests/pull/394">#394</a>
(<a
href="https://github.com/ghengeveld"><code>@​ghengeveld</code></a>)</li>
</ul>
<h4>Authors: 1</h4>
<ul>
<li>Gert Hengeveld (<a
href="https://github.com/ghengeveld"><code>@​ghengeveld</code></a>)</li>
</ul>
<hr />
<h1>v4.1.2 (Wed Oct 29 2025)</h1>
<h4>🐛 Bug Fix</h4>
<ul>
<li>Broaden version-range for storybook peerDependency to include
10.1.0-0 <a
href="https://redirect.github.com/chromaui/addon-visual-tests/pull/392">#392</a>
(<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
<li>Main <a
href="https://redirect.github.com/chromaui/addon-visual-tests/pull/391">#391</a>
(<a href="https://github.com/ndelangen"><code>@​ndelangen</code></a> <a
href="https://github.com/chromatic-support"><code>@​chromatic-support</code></a>)</li>
</ul>
<h4>Authors: 2</h4>
<ul>
<li><a
href="https://github.com/chromatic-support"><code>@​chromatic-support</code></a></li>
<li>Norbert de Langen (<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
</ul>
<hr />
<h1>v4.1.1 (Wed Aug 20 2025)</h1>
<h4>🐛 Bug Fix</h4>
<ul>
<li>Broaden version-range for storybook peerDependency <a
href="https://redirect.github.com/chromaui/addon-visual-tests/pull/389">#389</a>
(<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
</ul>
<h4>Authors: 1</h4>
<ul>
<li>Norbert de Langen (<a
href="https://github.com/ndelangen"><code>@​ndelangen</code></a>)</li>
</ul>
<hr />
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/chromaui/addon-visual-tests/commit/f382bd5c24d552b1bb4a6c40ed0a6e82a7723b05"><code>f382bd5</code></a>
Bump version to: 4.1.3 [skip ci]</li>
<li><a
href="https://github.com/chromaui/addon-visual-tests/commit/eeea23948483541818a46203cf4837de084257f7"><code>eeea239</code></a>
Update CHANGELOG.md [skip ci]</li>
<li><a
href="https://github.com/chromaui/addon-visual-tests/commit/05e1b8a5ed17a1692639cab84c376578d26f51f0"><code>05e1b8a</code></a>
Merge pull request <a
href="https://redirect.github.com/chromaui/addon-visual-tests/issues/395">#395</a>
from chromaui/next</li>
<li><a
href="https://github.com/chromaui/addon-visual-tests/commit/bf78815c1b82c8cfd3bbfbff3fe28416dd299723"><code>bf78815</code></a>
Merge pull request <a
href="https://redirect.github.com/chromaui/addon-visual-tests/issues/394">#394</a>
from chromaui/upgrade-chromatic-cli</li>
<li><a
href="https://github.com/chromaui/addon-visual-tests/commit/aed7067fdfc73689503b47fa1b52c13ca6a711b5"><code>aed7067</code></a>
Upgrade CLI package to v13.3.3</li>
<li><a
href="https://github.com/chromaui/addon-visual-tests/commit/a3af186430dd5852f8109f5cbf833b88933e8dce"><code>a3af186</code></a>
Bump version to: 4.1.2 [skip ci]</li>
<li><a
href="https://github.com/chromaui/addon-visual-tests/commit/5c28b499ed66e891e968e7147db46330d9dbebc0"><code>5c28b49</code></a>
Update CHANGELOG.md [skip ci]</li>
<li><a
href="https://github.com/chromaui/addon-visual-tests/commit/b5fef8d290f5b6cbb45f93e3cae7282036ec3e7f"><code>b5fef8d</code></a>
Merge pull request <a
href="https://redirect.github.com/chromaui/addon-visual-tests/issues/393">#393</a>
from chromaui/next</li>
<li><a
href="https://github.com/chromaui/addon-visual-tests/commit/dbc88e78778da6d26968b6ee72c7d47ec6e0a9a6"><code>dbc88e7</code></a>
Broaden version-range for storybook peerDependency to include 10.2.0-0
and 10...</li>
<li><a
href="https://github.com/chromaui/addon-visual-tests/commit/2b73fc62cc295aa7522975c1e386f030e20f4b67"><code>2b73fc6</code></a>
Broaden version-range for storybook peerDependency to include 10.2.0-0
and 10...</li>
<li>Additional commits viewable in <a
href="https://github.com/chromaui/addon-visual-tests/compare/v4.1.0...v4.1.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@chromatic-com/storybook&package-manager=npm_and_yarn&previous-version=4.1.0&new-version=4.1.3)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:43:30 +00:00
dependabot[bot] 77febb1469 chore: bump jsdom from 27.0.1 to 27.2.0 in /site (#21001)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 27.0.1 to 27.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/jsdom/jsdom/releases">jsdom's
releases</a>.</em></p>
<blockquote>
<h2>Version 27.2.0</h2>
<ul>
<li>Added <code>CSSGroupingRule</code>,
<code>CSSNestedDeclarations</code>, <code>CSSConditionRule</code>,
<code>CSSContainerRule</code>, <code>CSSScopeRule</code>,
<code>CSSSupportsRule</code>, <code>CSSLayerBlockRule</code>, and
<code>CSSLayerStatementRule</code> to jsdom <code>Window</code>s.
(acemir)</li>
<li>Improved CSS parsing and CSSOM object APIs via updates to <a
href="https://www.npmjs.com/package/@acemir/cssom"><code>@acemir/cssom</code></a>.
(acemir)</li>
<li>Fixed <code>@import</code>-ed stylesheets to be properly exposed to
CSSOM, and not to overwrite the sheet created from the
<code>&lt;link&gt;</code> or <code>&lt;style&gt;</code> element.
(acemir)</li>
</ul>
<h2>Version 27.1.0</h2>
<ul>
<li>Improved CSS parsing by switching to <a
href="https://www.npmjs.com/package/@acemir/cssom"><code>@acemir/cssom</code></a>,
including support for nested selectors, nested declarations, layer
statements, and improved at-rule validation. (acemir)</li>
<li>Fixed some selector cache invalidation issues where changes to
attributes were not being picked up. (asamuzaK)</li>
<li>Fixed <code>package.json</code> <code>&quot;engines&quot;</code>
field to reflect the new minimum Node.js versions needed to run jsdom,
as noted in the changelog for v27.0.1.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/jsdom/jsdom/blob/main/Changelog.md">jsdom's
changelog</a>.</em></p>
<blockquote>
<h2>27.2.0</h2>
<ul>
<li>Added <code>CSSGroupingRule</code>,
<code>CSSNestedDeclarations</code>, <code>CSSConditionRule</code>,
<code>CSSContainerRule</code>, <code>CSSScopeRule</code>,
<code>CSSSupportsRule</code>, <code>CSSLayerBlockRule</code>, and
<code>CSSLayerStatementRule</code> to jsdom <code>Window</code>s.
(acemir)</li>
<li>Improved CSS parsing and CSSOM object APIs via updates to <a
href="https://www.npmjs.com/package/@acemir/cssom"><code>@acemir/cssom</code></a>.
(acemir)</li>
<li>Fixed <code>@import</code>-ed stylesheets to be properly exposed to
CSSOM, and not to overwrite the sheet created from the
<code>&lt;link&gt;</code> or <code>&lt;style&gt;</code> element.
(acemir)</li>
</ul>
<h2>27.1.0</h2>
<ul>
<li>Improved CSS parsing by switching to <a
href="https://www.npmjs.com/package/@acemir/cssom"><code>@acemir/cssom</code></a>,
including support for nested selectors, nested declarations, layer
statements, and improved at-rule validation. (acemir)</li>
<li>Fixed some selector cache invalidation issues where changes to
attributes were not being picked up. (asamuzaK)</li>
<li>Fixed <code>package.json</code> <code>&quot;engines&quot;</code>
field to reflect the new minimum Node.js versions needed to run jsdom,
as noted in the changelog for v27.0.1.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/jsdom/jsdom/commit/b0805a908fb905ec69c4d1afc09977226927ae09"><code>b0805a9</code></a>
Version 27.2.0</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/3e07e1e1a93e5f67d6aac93046c99da94bc47625"><code>3e07e1e</code></a>
Update dependencies and dev dependencies</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/931aabedb686bb612b34afca4dc803f86976f426"><code>931aabe</code></a>
Various CSSOM improvements</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/70741dab9a9733c8c359ae82745269c5de607e44"><code>70741da</code></a>
Add failing tests for border shorthand property parsing</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/b282400b448d34477dcf539f0e5c9654e2ed4a0c"><code>b282400</code></a>
Update <code>@​acemir/cssom</code> dependency</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/adb999a12912f2f5ceb49fde6b1c9f7051968dc8"><code>adb999a</code></a>
Version 27.1.0</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/91f40c4bb1ef7c814b01b8e24db33356dc233eb1"><code>91f40c4</code></a>
Update dependencies and dev dependencies</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/ebad33c3ce5eb9c2fdfb81aaa5c0ab200626aac1"><code>ebad33c</code></a>
Note more strict minimum version requirement</li>
<li><a
href="https://github.com/jsdom/jsdom/commit/bd02585e2fd07bf846490779fba4696462d2eb94"><code>bd02585</code></a>
Swap rweb-cssom to <code>@​acemir/cssom</code></li>
<li><a
href="https://github.com/jsdom/jsdom/commit/f15c8302a03d0e9ccdef743e8297ff2e18e3f748"><code>f15c830</code></a>
Add failing test for cssText setter</li>
<li>Additional commits viewable in <a
href="https://github.com/jsdom/jsdom/compare/27.0.1...27.2.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=jsdom&package-manager=npm_and_yarn&previous-version=27.0.1&new-version=27.2.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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:32:50 +00:00
dependabot[bot] 154baf3d45 chore: bump github.com/anthropics/anthropic-sdk-go from 1.18.0 to 1.19.0 (#21013)
Bumps
[github.com/anthropics/anthropic-sdk-go](https://github.com/anthropics/anthropic-sdk-go)
from 1.18.0 to 1.19.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/anthropics/anthropic-sdk-go/releases">github.com/anthropics/anthropic-sdk-go's
releases</a>.</em></p>
<blockquote>
<h2>v1.19.0</h2>
<h2>1.19.0 (2025-11-24)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-go/compare/v1.18.1...v1.19.0">v1.18.1...v1.19.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> adds support for Claude Opus 4.5, Effort,
Advance Tool Use Features, Autocompaction, and Computer Use v5 (<a
href="https://github.com/anthropics/anthropic-sdk-go/commit/a03391cb00b8c78c79fd8bfe447f00d78f37db25">a03391c</a>)</li>
</ul>
<h2>v1.18.1</h2>
<h2>1.18.1 (2025-11-19)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-go/compare/v1.18.0...v1.18.1">v1.18.0...v1.18.1</a></p>
<h3>Bug Fixes</h3>
<ul>
<li><strong>structured outputs:</strong> use correct beta header (<a
href="https://github.com/anthropics/anthropic-sdk-go/commit/09ec0a647b1a108bb7c74e4c7b1016502ca781bb">09ec0a6</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/anthropics/anthropic-sdk-go/blob/main/CHANGELOG.md">github.com/anthropics/anthropic-sdk-go's
changelog</a>.</em></p>
<blockquote>
<h2>1.19.0 (2025-11-24)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-go/compare/v1.18.1...v1.19.0">v1.18.1...v1.19.0</a></p>
<h3>Features</h3>
<ul>
<li><strong>api:</strong> adds support for Claude Opus 4.5, Effort,
Advance Tool Use Features, Autocompaction, and Computer Use v5 (<a
href="https://github.com/anthropics/anthropic-sdk-go/commit/a03391cb00b8c78c79fd8bfe447f00d78f37db25">a03391c</a>)</li>
</ul>
<h2>1.18.1 (2025-11-19)</h2>
<p>Full Changelog: <a
href="https://github.com/anthropics/anthropic-sdk-go/compare/v1.18.0...v1.18.1">v1.18.0...v1.18.1</a></p>
<h3>Bug Fixes</h3>
<ul>
<li><strong>structured outputs:</strong> use correct beta header (<a
href="https://github.com/anthropics/anthropic-sdk-go/commit/09ec0a647b1a108bb7c74e4c7b1016502ca781bb">09ec0a6</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/anthropics/anthropic-sdk-go/commit/0e4a219d952e839fbbfe42e54282e44c1c28f1fb"><code>0e4a219</code></a>
release: 1.19.0</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-go/commit/9ea2999a49dac535ab07ff6a94aaf70f4ec3b85a"><code>9ea2999</code></a>
feat(api): adds support for Claude Opus 4.5, Effort, Advance Tool Use
Feature...</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-go/commit/9d1fca29394cf4a2aff51e99b4b089882cf0b769"><code>9d1fca2</code></a>
release: 1.18.1</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-go/commit/f79df507220e2a46ca26e00562fd0300de1fe99f"><code>f79df50</code></a>
fix(structured outputs): use correct beta header</li>
<li><a
href="https://github.com/anthropics/anthropic-sdk-go/commit/9c3ce43f8b134b0f74449bf258f5a6828f80f59d"><code>9c3ce43</code></a>
codegen metadata</li>
<li>See full diff in <a
href="https://github.com/anthropics/anthropic-sdk-go/compare/v1.18.0...v1.19.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/anthropics/anthropic-sdk-go&package-manager=go_modules&previous-version=1.18.0&new-version=1.19.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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:30:28 +00:00
dependabot[bot] ba370f1daa chore: bump react-router from 7.9.5 to 7.9.6 in /site (#21006)
Bumps
[react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router)
from 7.9.5 to 7.9.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/remix-run/react-router/releases">react-router's
releases</a>.</em></p>
<blockquote>
<h2>v7.9.6</h2>
<p>See the changelog for release notes: <a
href="https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v796">https://github.com/remix-run/react-router/blob/main/CHANGELOG.md#v796</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md">react-router's
changelog</a>.</em></p>
<blockquote>
<h2>7.9.6</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p>[UNSTABLE] Add <code>location</code>/<code>params</code> as arguments
to client-side <code>unstable_onError</code> to permit enhanced error
reporting. (<a
href="https://redirect.github.com/remix-run/react-router/pull/14509">#14509</a>)</p>
<p>⚠️ This is a breaking change if you've already adopted
<code>unstable_onError</code>. The second <code>errorInfo</code>
parameter is now an object with <code>location</code> and
<code>params</code>:</p>
<pre lang="tsx"><code>// Before
function errorHandler(error: unknown, errorInfo?: React.errorInfo) {
  /*...*/
}
<p>// After
function errorHandler(
error: unknown,
info: {
location: Location;
params: Params;
errorInfo?: React.ErrorInfo;
},
) {
/<em>...</em>/
}
</code></pre></p>
</li>
<li>
<p>Properly handle ancestor thrown middleware errors before
<code>next()</code> on fetcher submissions (<a
href="https://redirect.github.com/remix-run/react-router/pull/14517">#14517</a>)</p>
</li>
<li>
<p>Fix issue with splat routes interfering with multiple calls to
patchRoutesOnNavigation (<a
href="https://redirect.github.com/remix-run/react-router/pull/14487">#14487</a>)</p>
</li>
<li>
<p>Normalize double-slashes in <code>resolvePath</code> (<a
href="https://redirect.github.com/remix-run/react-router/pull/14529">#14529</a>)</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/remix-run/react-router/commit/e75da95e16ecf625f4e3bbc864b1a0a5202052c2"><code>e75da95</code></a>
chore: Update version for release (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router/issues/14538">#14538</a>)</li>
<li><a
href="https://github.com/remix-run/react-router/commit/6104e1fc91b5b9485573b7b0c17ab17ecda6ca33"><code>6104e1f</code></a>
chore: Update version for release (pre) (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router/issues/14535">#14535</a>)</li>
<li><a
href="https://github.com/remix-run/react-router/commit/02e65a55daa6f9c0aa88d0de8732330e0b449dad"><code>02e65a5</code></a>
Normalize double-slashes in resolvePath (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router/issues/14529">#14529</a>)</li>
<li><a
href="https://github.com/remix-run/react-router/commit/092c872603c2cba6498144ef988437773ff89438"><code>092c872</code></a>
chore: Update version for release (pre) (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router/issues/14523">#14523</a>)</li>
<li><a
href="https://github.com/remix-run/react-router/commit/cd6eb8d4e034d9ee9038050c53d8b2d46a073988"><code>cd6eb8d</code></a>
Fix bug with fetcher submission ancestor-thrown middleware errors (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router/issues/14517">#14517</a>)</li>
<li><a
href="https://github.com/remix-run/react-router/commit/fc01722e301b909a736fe312ec5bac4c1f5c1c26"><code>fc01722</code></a>
Add location/params arguments to client-side unstable_onError (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router/issues/14509">#14509</a>)</li>
<li><a
href="https://github.com/remix-run/react-router/commit/7c115a6850a0b7652ff40086daec6544a9cb40a1"><code>7c115a6</code></a>
Fix splat routes blocking multiple FOW calls (<a
href="https://github.com/remix-run/react-router/tree/HEAD/packages/react-router/issues/14487">#14487</a>)</li>
<li><a
href="https://github.com/remix-run/react-router/commit/89e2bfe8232ad7c24cf610b0998e9028dcfa9ce8"><code>89e2bfe</code></a>
chore: format</li>
<li>See full diff in <a
href="https://github.com/remix-run/react-router/commits/react-router@7.9.6/packages/react-router">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 react-router since your current
version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=react-router&package-manager=npm_and_yarn&previous-version=7.9.5&new-version=7.9.6)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:24:54 +00:00
dependabot[bot] 4431e9aef7 chore: bump the coder-modules group across 3 directories with 11 updates (#21011)
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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:23:53 +00:00
dependabot[bot] ccf8fd6c58 chore: bump jest-fixed-jsdom from 0.0.10 to 0.0.11 in /site (#21012)
Bumps [jest-fixed-jsdom](https://github.com/mswjs/jest-fixed-jsdom) from
0.0.10 to 0.0.11.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/mswjs/jest-fixed-jsdom/releases">jest-fixed-jsdom's
releases</a>.</em></p>
<blockquote>
<h2>v0.0.11 (2025-11-14)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>add WritableStream (<a
href="https://redirect.github.com/mswjs/jest-fixed-jsdom/issues/40">#40</a>)
(b73393cb3a8171cf9cf43e6e790d64bbbe03c242) <a
href="https://github.com/jamiemoyes"><code>@​jamiemoyes</code></a> <a
href="https://github.com/jamie-moyes-depop"><code>@​jamie-moyes-depop</code></a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/mswjs/jest-fixed-jsdom/commit/f4ef52d727c1860b883785cef9f73509008c7def"><code>f4ef52d</code></a>
chore(release): v0.0.11</li>
<li><a
href="https://github.com/mswjs/jest-fixed-jsdom/commit/b73393cb3a8171cf9cf43e6e790d64bbbe03c242"><code>b73393c</code></a>
fix: add WritableStream (<a
href="https://redirect.github.com/mswjs/jest-fixed-jsdom/issues/40">#40</a>)</li>
<li>See full diff in <a
href="https://github.com/mswjs/jest-fixed-jsdom/compare/v0.0.10...v0.0.11">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=jest-fixed-jsdom&package-manager=npm_and_yarn&previous-version=0.0.10&new-version=0.0.11)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:23:00 +00:00
dependabot[bot] e713870b69 chore: bump @types/lodash from 4.17.20 to 4.17.21 in /site (#21010)
Bumps
[@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash)
from 4.17.20 to 4.17.21.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@types/lodash&package-manager=npm_and_yarn&previous-version=4.17.20&new-version=4.17.21)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:20:57 +00:00
dependabot[bot] 8bb481566b chore: bump @tailwindcss/typography from 0.5.16 to 0.5.19 in /site (#21009)
Bumps
[@tailwindcss/typography](https://github.com/tailwindlabs/tailwindcss-typography)
from 0.5.16 to 0.5.19.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss-typography/releases"><code>@​tailwindcss/typography</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v0.5.19</h2>
<h3>Fixed</h3>
<ul>
<li>Fixed broken color styles (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/405">#405</a>)</li>
</ul>
<h2>v0.5.18</h2>
<h3>Fixed</h3>
<ul>
<li>Fixed undefined variable error (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/403">#403</a>)</li>
</ul>
<h2>v0.5.17</h2>
<h3>Added</h3>
<ul>
<li>Add modifiers for description list elements (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/357">#357</a>)</li>
<li>Add <code>prose-picture</code> modifier (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/367">#367</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Include unit in <code>hr</code> border-width value (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/379">#379</a>)</li>
<li>Ensure <code>&lt;kbd&gt;</code> styles work with Tailwind CSS v4 (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/387">#387</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Remove lodash dependencies (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/402">#402</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tailwindlabs/tailwindcss-typography/blob/main/CHANGELOG.md"><code>@​tailwindcss/typography</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>[0.5.19] - 2025-09-24</h2>
<h3>Fixed</h3>
<ul>
<li>Fixed broken color styles (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/405">#405</a>)</li>
</ul>
<h2>[0.5.18] - 2025-09-19</h2>
<h3>Fixed</h3>
<ul>
<li>Fixed undefined variable error (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/403">#403</a>)</li>
</ul>
<h2>[0.5.17] - 2025-09-19</h2>
<h3>Added</h3>
<ul>
<li>Add modifiers for description list elements (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/357">#357</a>)</li>
<li>Add <code>prose-picture</code> modifier (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/367">#367</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Include unit in <code>hr</code> border-width value (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/379">#379</a>)</li>
<li>Ensure <code>&lt;kbd&gt;</code> styles work with Tailwind CSS v4 (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/387">#387</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Remove lodash dependencies (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/pull/402">#402</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tailwindlabs/tailwindcss-typography/commit/e002ab89ad8f4202638249c1c300c0cf0b3739c5"><code>e002ab8</code></a>
0.5.19</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss-typography/commit/bbb1c21099e34ff4d1d7f82f7528b85e71ed3c5a"><code>bbb1c21</code></a>
Fix bad RGB syntax (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/issues/405">#405</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss-typography/commit/b316f958af5bc12a981526c3091d8319626e274e"><code>b316f95</code></a>
0.5.18</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss-typography/commit/ed952066e698dbb65a2f082eeb903ccba5a6834a"><code>ed95206</code></a>
Fix variable declaration in opacity function (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/issues/403">#403</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss-typography/commit/7efcb4a499e6ede67088e28393a906d4d089e580"><code>7efcb4a</code></a>
0.5.17</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss-typography/commit/e0ec248bafa002d589509e29bfd9f054570e6d85"><code>e0ec248</code></a>
chore(ci): update actions for release insiders</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss-typography/commit/511afcb0bd9732eb8fcdc703f35ff76e57bfcd7b"><code>511afcb</code></a>
Add modifiers for description list elements (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/issues/357">#357</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss-typography/commit/042a531528cd3ddecafda94fe972394dc8aab6ae"><code>042a531</code></a>
Add <code>prose-picture</code> modifiers (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/issues/367">#367</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss-typography/commit/f822222ae6e289e8cc0b23636891dc3545d5682a"><code>f822222</code></a>
Fix <code>kbd</code> shadow colors not being calculated on oklch colors
(<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/issues/387">#387</a>)</li>
<li><a
href="https://github.com/tailwindlabs/tailwindcss-typography/commit/ecb7e87a52d86afbbff64200d40f05fe59433039"><code>ecb7e87</code></a>
Add Tailwind v4 custom color theme example to README (<a
href="https://redirect.github.com/tailwindlabs/tailwindcss-typography/issues/396">#396</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.16...v0.5.19">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@tailwindcss/typography&package-manager=npm_and_yarn&previous-version=0.5.16&new-version=0.5.19)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:20:30 +00:00
dependabot[bot] 5a78ec1428 chore: bump humanize-duration from 3.32.2 to 3.33.1 in /site (#21008)
Bumps
[humanize-duration](https://github.com/EvanHahn/HumanizeDuration.js)
from 3.32.2 to 3.33.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/EvanHahn/HumanizeDuration.js/blob/main/HISTORY.md">humanize-duration's
changelog</a>.</em></p>
<blockquote>
<h1>3.33.1 / 2025-09-15</h1>
<ul>
<li>change: shrink size slightly</li>
</ul>
<h1>3.33.0 / 2025-06-05</h1>
<ul>
<li>new: Serbian Latin support (<code>sr_Latn</code>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/EvanHahn/HumanizeDuration.js/commit/7d96bbc85bc7c04397518afbb4183f8dc81f1b62"><code>7d96bbc</code></a>
3.33.1</li>
<li><a
href="https://github.com/EvanHahn/HumanizeDuration.js/commit/8e7cf83314beceb5d4387980c060ce6b84ee7ce1"><code>8e7cf83</code></a>
Update changelog and bower.json for 3.33.1 release</li>
<li><a
href="https://github.com/EvanHahn/HumanizeDuration.js/commit/eb2ab9d406f832080ea7236125602a75d41b2fbe"><code>eb2ab9d</code></a>
Shorten definition of &quot;ones&quot; languages, like English</li>
<li><a
href="https://github.com/EvanHahn/HumanizeDuration.js/commit/aa943ce284ca96afdf1439be696c72ec5de5d425"><code>aa943ce</code></a>
Shorten definitions of Slavic languages</li>
<li><a
href="https://github.com/EvanHahn/HumanizeDuration.js/commit/2e310914ded5fee0f3cc32af75455b0bd0e16154"><code>2e31091</code></a>
Minor: shrink condition in <code>language</code> helper by a few
bytes</li>
<li><a
href="https://github.com/EvanHahn/HumanizeDuration.js/commit/3fa60af062bfbf8cbb273a27e5f13b9d49e6b041"><code>3fa60af</code></a>
Remove unnecessary <code>@ts-check</code> directives</li>
<li><a
href="https://github.com/EvanHahn/HumanizeDuration.js/commit/1e520bbe2808a64307a9cfb2de4d12950a5a9a99"><code>1e520bb</code></a>
Update devDependencies to latest versions</li>
<li><a
href="https://github.com/EvanHahn/HumanizeDuration.js/commit/46098849ce4afb49098a1d4631e272432edebe6e"><code>4609884</code></a>
Add funding metadata to package</li>
<li><a
href="https://github.com/EvanHahn/HumanizeDuration.js/commit/1f03ec67a772a36a95056b864554989bba5023a8"><code>1f03ec6</code></a>
3.33.0</li>
<li><a
href="https://github.com/EvanHahn/HumanizeDuration.js/commit/7c18165182968a8120a19237562a40fffe7863c7"><code>7c18165</code></a>
Update changelog and bower.json for 3.33.0 release</li>
<li>Additional commits viewable in <a
href="https://github.com/EvanHahn/HumanizeDuration.js/compare/v3.32.2...v3.33.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=humanize-duration&package-manager=npm_and_yarn&previous-version=3.32.2&new-version=3.33.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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:20:06 +00:00
dependabot[bot] cfd6c4bc13 chore: bump the vite group across 1 directory with 3 updates (#20995)
Bumps the vite group with 3 updates in the /site directory:
[@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react),
[vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) and
[vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest).

Updates `@vitejs/plugin-react` from 5.1.0 to 5.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/releases"><code>@​vitejs/plugin-react</code>'s
releases</a>.</em></p>
<blockquote>
<h2>plugin-react@5.1.1</h2>
<h3>Update code to support newer <code>rolldown-vite</code> (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/976">#976</a>)</h3>
<p><code>rolldown-vite</code> will remove
<code>optimizeDeps.rollupOptions</code> in favor of
<code>optimizeDeps.rolldownOptions</code> soon. This plugin now uses
<code>optimizeDeps.rolldownOptions</code> to support newer
<code>rolldown-vite</code>. Please update <code>rolldown-vite</code> to
the latest version if you are using an older version.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md"><code>@​vitejs/plugin-react</code>'s
changelog</a>.</em></p>
<blockquote>
<h2>5.1.1 (2025-11-12)</h2>
<h3>Update code to support newer <code>rolldown-vite</code> (<a
href="https://redirect.github.com/vitejs/vite-plugin-react/pull/976">#976</a>)</h3>
<p><code>rolldown-vite</code> will remove
<code>optimizeDeps.rollupOptions</code> in favor of
<code>optimizeDeps.rolldownOptions</code> soon. This plugin now uses
<code>optimizeDeps.rolldownOptions</code> to support newer
<code>rolldown-vite</code>. Please update <code>rolldown-vite</code> to
the latest version if you are using an older version.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/23db72731b7c9c3d57a8188f0395d2ec90a6e2f6"><code>23db727</code></a>
release: plugin-react@5.1.1</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/bcc7db076ec038ab0152fd00a712913569c901a4"><code>bcc7db0</code></a>
chore: add changelog for <a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/976">#976</a>
and <a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/978">#978</a></li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/4a2e229b2093b4a2e4af1c07f3e07aa91f3e12a5"><code>4a2e229</code></a>
fix(react): use rolldownOptions instead of deprecated rollupOptions in
optimi...</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/41cb8237bfb902fe1873c55de0780f0ca2cf3640"><code>41cb823</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/968">#968</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/9cabe2720f571338cc319c957f285e6f787f23fa"><code>9cabe27</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/960">#960</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/c8d918eab8d8cb0f9d38966d5f29a113a0c397ac"><code>c8d918e</code></a>
chore(react): use ts-ignore for ecosystem-ci (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/956">#956</a>)</li>
<li><a
href="https://github.com/vitejs/vite-plugin-react/commit/0849e420b30a6183c9364aae2320435684696090"><code>0849e42</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react/issues/953">#953</a>)</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite-plugin-react/commits/plugin-react@5.1.1/packages/plugin-react">compare
view</a></li>
</ul>
</details>
<br />

Updates `vite` from 7.1.12 to 7.2.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v7.2.6</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v7.2.6/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v7.2.5</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v7.2.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<p><em>Note: 7.2.5 failed to publish so it is skipped on npm</em></p>
<h2>v7.2.4</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v7.2.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v7.2.3</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v7.2.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v7.2.2</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v7.2.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@7.2.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@7.2.1/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v7.2.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v7.2.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>plugin-legacy@7.2.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/plugin-legacy@7.2.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v7.2.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v7.2.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v7.2.0-beta.1</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v7.2.0-beta.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
<h2>v7.2.0-beta.0</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v7.2.0-beta.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v7.2.5...v7.2.6">7.2.6</a>
(2025-12-01)<!-- raw HTML omitted --></h2>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v7.2.4...v7.2.5">7.2.5</a>
(2025-12-01)<!-- raw HTML omitted --></h2>
<h3>Bug Fixes</h3>
<ul>
<li><strong>config:</strong> handle shebang properly (<a
href="https://redirect.github.com/vitejs/vite/issues/21158">#21158</a>)
(<a
href="https://github.com/vitejs/vite/commit/df5a30d2690a2ebc4824a79becdcef30538dc602">df5a30d</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/21146">#21146</a>)
(<a
href="https://github.com/vitejs/vite/commit/a3cd262f37228967e455617e982b35fccc49ffe9">a3cd262</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/21175">#21175</a>)
(<a
href="https://github.com/vitejs/vite/commit/72e398a46d8d2f54fbcbeb9ff0dceab346aeb642">72e398a</a>)</li>
<li>fix <code>external: true</code> merging (<a
href="https://redirect.github.com/vitejs/vite/issues/21164">#21164</a>)
(<a
href="https://github.com/vitejs/vite/commit/5ef557a96c4a1f2b3a3aa25c12df3ee87b4a03f5">5ef557a</a>)</li>
<li>shortcuts not rebound after server restart (<a
href="https://redirect.github.com/vitejs/vite/issues/21166">#21166</a>)
(<a
href="https://github.com/vitejs/vite/commit/3765f7baea36234bf3816eeed38776d27bfd3649">3765f7b</a>)</li>
</ul>
<h3>Performance Improvements</h3>
<ul>
<li><strong>deps:</strong> replace debug with obug (<a
href="https://redirect.github.com/vitejs/vite/issues/21137">#21137</a>)
(<a
href="https://github.com/vitejs/vite/commit/203a5512a42a1031f685993f5d9cbae5f328354f">203a551</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>clarify manifest.json <code>imports</code> field is JS chunks only
(<a
href="https://redirect.github.com/vitejs/vite/issues/21136">#21136</a>)
(<a
href="https://github.com/vitejs/vite/commit/46d3077f2b63771cc50230bc907c48f5773c00fb">46d3077</a>)</li>
</ul>
<h3>Miscellaneous Chores</h3>
<ul>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/21174">#21174</a>)
(<a
href="https://github.com/vitejs/vite/commit/74559c947483a8ee24da052ac2d9568f7cb3546a">74559c9</a>)</li>
</ul>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v7.2.3...v7.2.4">7.2.4</a>
(2025-11-20)<!-- raw HTML omitted --></h2>
<h3>Bug Fixes</h3>
<ul>
<li>revert &quot;perf(deps): replace debug with obug (<a
href="https://redirect.github.com/vitejs/vite/issues/21107">#21107</a>)&quot;
(<a
href="https://github.com/vitejs/vite/commit/2d66b7b14aa6dfd62f3d6a59ee8382ed5ca6fd32">2d66b7b</a>)</li>
</ul>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v7.2.2...v7.2.3">7.2.3</a>
(2025-11-20)<!-- raw HTML omitted --></h2>
<h3>Bug Fixes</h3>
<ul>
<li>allow multiple <code>bindCLIShortcuts</code> calls with shortcut
merging (<a
href="https://redirect.github.com/vitejs/vite/issues/21103">#21103</a>)
(<a
href="https://github.com/vitejs/vite/commit/5909efd8fbfd1bf1eab65427aea0613124b2797a">5909efd</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/21096">#21096</a>)
(<a
href="https://github.com/vitejs/vite/commit/6a34ac3422686e7cf7cc9a25d299cb8e5a8d92a0">6a34ac3</a>)</li>
<li><strong>deps:</strong> update all non-major dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/21128">#21128</a>)
(<a
href="https://github.com/vitejs/vite/commit/4f8171eb3046bd70c83964689897dab4c6b58bc0">4f8171e</a>)</li>
</ul>
<h3>Performance Improvements</h3>
<ul>
<li><strong>deps:</strong> replace debug with obug (<a
href="https://redirect.github.com/vitejs/vite/issues/21107">#21107</a>)
(<a
href="https://github.com/vitejs/vite/commit/acfe939e1f7c303c34b0b39b883cc302da767fa2">acfe939</a>)</li>
</ul>
<h3>Miscellaneous Chores</h3>
<ul>
<li><strong>deps:</strong> update dependency
<code>@​rollup/plugin-commonjs</code> to v29 (<a
href="https://redirect.github.com/vitejs/vite/issues/21099">#21099</a>)
(<a
href="https://github.com/vitejs/vite/commit/02ceaec45e17bef19159188a28d9196fed1761be">02ceaec</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/21095">#21095</a>)
(<a
href="https://github.com/vitejs/vite/commit/39a0a15fd24ed37257c48b795097a3794e54d255">39a0a15</a>)</li>
<li><strong>deps:</strong> update rolldown-related dependencies (<a
href="https://redirect.github.com/vitejs/vite/issues/21127">#21127</a>)
(<a
href="https://github.com/vitejs/vite/commit/50297208452241061cb44d09a4bbdf77a11ac01e">5029720</a>)</li>
</ul>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v7.2.1...v7.2.2">7.2.2</a>
(2025-11-07)<!-- raw HTML omitted --></h2>
<h3>Bug Fixes</h3>
<ul>
<li>revert &quot;refactor: use fs.cpSync (<a
href="https://redirect.github.com/vitejs/vite/issues/21019">#21019</a>)&quot;
(<a
href="https://redirect.github.com/vitejs/vite/issues/21081">#21081</a>)
(<a
href="https://github.com/vitejs/vite/commit/728c8eeebc0ad7ba48e680f46bbdb55020b2e152">728c8ee</a>)</li>
</ul>
<h2><!-- raw HTML omitted --><a
href="https://github.com/vitejs/vite/compare/v7.2.0...v7.2.1">7.2.1</a>
(2025-11-06)<!-- raw HTML omitted --></h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitejs/vite/commit/bda5dbb648fa7cf877ea9c76ba8a7da226b65cce"><code>bda5dbb</code></a>
release: v7.2.6</li>
<li><a
href="https://github.com/vitejs/vite/commit/3aa7527fb4fc7dafe3ab57c41d637d2985c7bb6e"><code>3aa7527</code></a>
release: v7.2.5</li>
<li><a
href="https://github.com/vitejs/vite/commit/72e398a46d8d2f54fbcbeb9ff0dceab346aeb642"><code>72e398a</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21175">#21175</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/3765f7baea36234bf3816eeed38776d27bfd3649"><code>3765f7b</code></a>
fix: shortcuts not rebound after server restart (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21166">#21166</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/5ef557a96c4a1f2b3a3aa25c12df3ee87b4a03f5"><code>5ef557a</code></a>
fix: fix <code>external: true</code> merging (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21164">#21164</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/74559c947483a8ee24da052ac2d9568f7cb3546a"><code>74559c9</code></a>
chore(deps): update rolldown-related dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21174">#21174</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/df5a30d2690a2ebc4824a79becdcef30538dc602"><code>df5a30d</code></a>
fix(config): handle shebang properly (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21158">#21158</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/a3cd262f37228967e455617e982b35fccc49ffe9"><code>a3cd262</code></a>
fix(deps): update all non-major dependencies (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21146">#21146</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/46d3077f2b63771cc50230bc907c48f5773c00fb"><code>46d3077</code></a>
docs: clarify manifest.json <code>imports</code> field is JS chunks only
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21136">#21136</a>)</li>
<li><a
href="https://github.com/vitejs/vite/commit/203a5512a42a1031f685993f5d9cbae5f328354f"><code>203a551</code></a>
perf(deps): replace debug with obug (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/21137">#21137</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitejs/vite/commits/v7.2.6/packages/vite">compare
view</a></li>
</ul>
</details>
<br />

Updates `vitest` from 4.0.6 to 4.0.14
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitest-dev/vitest/releases">vitest's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.14</h2>
<h3>   🚀 Experimental Features</h3>
<ul>
<li><strong>browser</strong>: Expose
<code>utils.configurePrettyDOM</code>  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9103">vitest-dev/vitest#9103</a>
<a href="https://github.com/vitest-dev/vitest/commit/2cc34e0d4"><!-- raw
HTML omitted -->(2cc34)<!-- raw HTML omitted --></a></li>
<li><strong>runner</strong>: Add full names to tasks  -  by <a
href="https://github.com/macarie"><code>@​macarie</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9087">vitest-dev/vitest#9087</a>
<a href="https://github.com/vitest-dev/vitest/commit/821aa2002"><!-- raw
HTML omitted -->(821aa)<!-- raw HTML omitted --></a></li>
<li><strong>ui</strong>: Add tabbed failure view for
<code>toMatchScreenshot</code> with comparison slider  -  by <a
href="https://github.com/macarie"><code>@​macarie</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/8813">vitest-dev/vitest#8813</a>
<a href="https://github.com/vitest-dev/vitest/commit/c37c2eb0b"><!-- raw
HTML omitted -->(c37c2)<!-- raw HTML omitted --></a></li>
</ul>
<h3>   🐞 Bug Fixes</h3>
<ul>
<li>Externalize before caching  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9077">vitest-dev/vitest#9077</a>
<a href="https://github.com/vitest-dev/vitest/commit/e1b2e086a"><!-- raw
HTML omitted -->(e1b2e)<!-- raw HTML omitted --></a></li>
<li>Collect the duration of external imports  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9097">vitest-dev/vitest#9097</a>
<a href="https://github.com/vitest-dev/vitest/commit/3326cc9f4"><!-- raw
HTML omitted -->(3326c)<!-- raw HTML omitted --></a></li>
<li>Rename <code>collect</code> to <code>import</code>, remove
<code>prepare</code>  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9091">vitest-dev/vitest#9091</a>
<a href="https://github.com/vitest-dev/vitest/commit/1256b5ca3"><!-- raw
HTML omitted -->(1256b)<!-- raw HTML omitted --></a></li>
<li><strong>browser</strong>:
<ul>
<li>Unsubscribe <code>onCancel</code> on rpc destroy  -  by <a
href="https://github.com/AriPerkkio"><code>@​AriPerkkio</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9088">vitest-dev/vitest#9088</a>
<a href="https://github.com/vitest-dev/vitest/commit/f5b72374b"><!-- raw
HTML omitted -->(f5b72)<!-- raw HTML omitted --></a></li>
<li>Revert the viewport scaling in non-ui mode <a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/9018">#9018</a>
 -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9072">vitest-dev/vitest#9072</a>
and <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9018">vitest-dev/vitest#9018</a>
<a href="https://github.com/vitest-dev/vitest/commit/64502a2c4"><!-- raw
HTML omitted -->(64502)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>coverage</strong>:
<ul>
<li>Invalidate circular modules correctly on rerun with coverage  -  by
<a href="https://github.com/aicest"><code>@​aicest</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9096">vitest-dev/vitest#9096</a>
<a href="https://github.com/vitest-dev/vitest/commit/6f22c675f"><!-- raw
HTML omitted -->(6f22c)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>expect</strong>:
<ul>
<li>Allow function as standard schema  -  by <a
href="https://github.com/hi-ogawa"><code>@​hi-ogawa</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9099">vitest-dev/vitest#9099</a>
<a href="https://github.com/vitest-dev/vitest/commit/ed8a2ebbd"><!-- raw
HTML omitted -->(ed8a2)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>jsdom</strong>:
<ul>
<li>Reuse abort signals if possible  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9090">vitest-dev/vitest#9090</a>
<a href="https://github.com/vitest-dev/vitest/commit/2c468ee95"><!-- raw
HTML omitted -->(2c468)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>pool</strong>:
<ul>
<li>Init <code>VITEST_POOL_ID</code> + <code>VITEST_WORKER_ID</code>
before environment setup  -  by <a
href="https://github.com/AriPerkkio"><code>@​AriPerkkio</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9085">vitest-dev/vitest#9085</a>
<a href="https://github.com/vitest-dev/vitest/commit/379185b40"><!-- raw
HTML omitted -->(37918)<!-- raw HTML omitted --></a></li>
</ul>
</li>
<li><strong>web-worker</strong>:
<ul>
<li><code>postMessage</code> to send ports to workers  -  by <a
href="https://github.com/whitphx"><code>@​whitphx</code></a> and <a
href="https://github.com/AriPerkkio"><code>@​AriPerkkio</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9078">vitest-dev/vitest#9078</a>
<a href="https://github.com/vitest-dev/vitest/commit/9d1763d11"><!-- raw
HTML omitted -->(9d176)<!-- raw HTML omitted --></a></li>
</ul>
</li>
</ul>
<h3>   🏎 Performance</h3>
<ul>
<li>Replace <code>debug</code> with <code>obug</code>  -  by <a
href="https://github.com/sxzz"><code>@​sxzz</code></a> and <a
href="https://github.com/AriPerkkio"><code>@​AriPerkkio</code></a> in <a
href="https://redirect.github.com/vitest-dev/vitest/issues/9057">vitest-dev/vitest#9057</a>
<a href="https://github.com/vitest-dev/vitest/commit/acc5152b9"><!-- raw
HTML omitted -->(acc51)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/vitest-dev/vitest/compare/v4.0.13...v4.0.14">View
changes on GitHub</a></h5>
<h2>v4.0.13</h2>
<h3>   🐞 Bug Fixes</h3>
<ul>
<li><strong>types</strong>:
<ul>
<li>Don't use type from Vite 7.1  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9071">vitest-dev/vitest#9071</a>
<a href="https://github.com/vitest-dev/vitest/commit/6356b1d38"><!-- raw
HTML omitted -->(6356b)<!-- raw HTML omitted --></a></li>
<li>Don't import node.js dependent types in vitest/browser  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9068">vitest-dev/vitest#9068</a>
<a href="https://github.com/vitest-dev/vitest/commit/332afa0de"><!-- raw
HTML omitted -->(332af)<!-- raw HTML omitted --></a></li>
</ul>
</li>
</ul>
<h3>   🏎 Performance</h3>
<ul>
<li>Avoid fetchModule roundtrip if the module is cached  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9075">vitest-dev/vitest#9075</a>
<a href="https://github.com/vitest-dev/vitest/commit/b27e002e5"><!-- raw
HTML omitted -->(b27e0)<!-- raw HTML omitted --></a></li>
<li><strong>experimental</strong>: If <code>fsCacheModule</code> is
enabled, read from the memory when possible  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9076">vitest-dev/vitest#9076</a>
<a href="https://github.com/vitest-dev/vitest/commit/6b9a1b5b1"><!-- raw
HTML omitted -->(6b9a1)<!-- raw HTML omitted --></a></li>
</ul>
<h5>    <a
href="https://github.com/vitest-dev/vitest/compare/v4.0.12...v4.0.13">View
changes on GitHub</a></h5>
<h2>v4.0.12</h2>
<h3>   🐞 Bug Fixes</h3>
<ul>
<li>Inherit <code>fsModuleCachePath</code> by default  -  by <a
href="https://github.com/sheremet-va"><code>@​sheremet-va</code></a> in
<a
href="https://redirect.github.com/vitest-dev/vitest/issues/9063">vitest-dev/vitest#9063</a>
<a href="https://github.com/vitest-dev/vitest/commit/9a8bc78c5"><!-- raw
HTML omitted -->(9a8bc)<!-- raw HTML omitted --></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vitest-dev/vitest/commit/9ca74cfb2060d8bc1c7a319ba3cba1578517adb0"><code>9ca74cf</code></a>
chore: release v4.0.14</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/821aa20021111de63a71e8a9c69eb9188d6e409a"><code>821aa20</code></a>
feat(runner): Add full names to tasks (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/9087">#9087</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/1256b5ca34a36d3a29479d5dfddb129348964d80"><code>1256b5c</code></a>
fix: rename <code>collect</code> to <code>import</code>, remove
<code>prepare</code> (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/9091">#9091</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/3326cc9f43f102322f3c627590d103d8a3890dd5"><code>3326cc9</code></a>
fix: collect the duration of external imports (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/9097">#9097</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/379185b408baba90ec91540b5e7cf545a4527f1a"><code>379185b</code></a>
fix(pool): init <code>VITEST_POOL_ID</code> +
<code>VITEST_WORKER_ID</code> before environment setu...</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/2c468ee95ee1d42129f59665eabc563e34dae1e4"><code>2c468ee</code></a>
fix(jsdom): reuse abort signals if possible (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/9090">#9090</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/e1b2e086a40ce154ae11714fa71749ec21b1ac23"><code>e1b2e08</code></a>
fix: externalize before caching (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/9077">#9077</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/acc5152b9690992e3ac0cff691db4347f2dccdf9"><code>acc5152</code></a>
perf: replace <code>debug</code> with <code>obug</code> (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/9057">#9057</a>)</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/73b54ce2859d34f3847de465efb3f6affda0f8c1"><code>73b54ce</code></a>
chore: release v4.0.13</li>
<li><a
href="https://github.com/vitest-dev/vitest/commit/b27e002e578e7b6cf182c3c173ac5d8fd390598d"><code>b27e002</code></a>
perf: avoid fetchModule roundtrip if the module is cached (<a
href="https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest/issues/9075">#9075</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vitest-dev/vitest/commits/v4.0.14/packages/vitest">compare
view</a></li>
</ul>
</details>
<br />


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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:15:13 +00:00
dependabot[bot] 5f8575dbaf chore: bump the radix group across 1 directory with 4 updates (#20993)
Bumps the radix group with 4 updates in the /site directory:
[@radix-ui/react-avatar](https://github.com/radix-ui/primitives),
[@radix-ui/react-label](https://github.com/radix-ui/primitives),
[@radix-ui/react-separator](https://github.com/radix-ui/primitives) and
[@radix-ui/react-slot](https://github.com/radix-ui/primitives).

Updates `@radix-ui/react-avatar` from 1.1.10 to 1.1.11
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/radix-ui/primitives/commits">compare
view</a></li>
</ul>
</details>
<br />

Updates `@radix-ui/react-label` from 2.1.7 to 2.1.8
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/radix-ui/primitives/commits">compare
view</a></li>
</ul>
</details>
<br />

Updates `@radix-ui/react-separator` from 1.1.7 to 1.1.8
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/radix-ui/primitives/commits">compare
view</a></li>
</ul>
</details>
<br />

Updates `@radix-ui/react-slot` from 1.2.3 to 1.2.4
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/radix-ui/primitives/commits">compare
view</a></li>
</ul>
</details>
<br />


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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:12:53 +00:00
dependabot[bot] 0415d1f84a chore: bump @types/lodash from 4.17.20 to 4.17.21 in /offlinedocs (#20997)
Bumps
[@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash)
from 4.17.20 to 4.17.21.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@types/lodash&package-manager=npm_and_yarn&previous-version=4.17.20&new-version=4.17.21)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:12:22 +00:00
dependabot[bot] 0e10b315f2 chore: bump prettier from 3.6.2 to 3.7.3 in /offlinedocs (#20996)
Bumps [prettier](https://github.com/prettier/prettier) from 3.6.2 to
3.7.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/prettier/releases">prettier's
releases</a>.</em></p>
<blockquote>
<h2>3.7.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix <code>prettier.getFileInfo()</code> change that breaks VSCode
extension by <a
href="https://github.com/fisker"><code>@​fisker</code></a> in <a
href="https://redirect.github.com/prettier/prettier/pull/18375">prettier/prettier#18375</a></li>
</ul>
<p>🔗 <a
href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md#373">Changelog</a></p>
<h2>3.7.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix string print when switching quotes by <a
href="https://github.com/fisker"><code>@​fisker</code></a> in <a
href="https://redirect.github.com/prettier/prettier/pull/18351">prettier/prettier#18351</a></li>
<li>Preserve quote for embedded HTML attribute values by <a
href="https://github.com/kovsu"><code>@​kovsu</code></a> in <a
href="https://redirect.github.com/prettier/prettier/pull/18352">prettier/prettier#18352</a></li>
<li>Fix comment in empty type literal by <a
href="https://github.com/fisker"><code>@​fisker</code></a> in <a
href="https://redirect.github.com/prettier/prettier/pull/18364">prettier/prettier#18364</a></li>
</ul>
<p>🔗 <a
href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md#372">Changelog</a></p>
<h2>3.7.1</h2>
<ul>
<li>Fix performance regression in doc printer (<a
href="https://redirect.github.com/prettier/prettier/pull/18342">#18342</a>
by <a href="https://github.com/fisker"><code>@​fisker</code></a>)</li>
</ul>
<p>🔗 <a
href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md#371">Changelog</a></p>
<h2>3.7.0</h2>
<p><a
href="https://github.com/prettier/prettier/compare/3.6.2...3.7.0">diff</a></p>
<p>🔗 <a href="https://prettier.io/blog/2025/11/27/3.7.0">Release
note</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/prettier/prettier/blob/main/CHANGELOG.md">prettier's
changelog</a>.</em></p>
<blockquote>
<h1>3.7.3</h1>
<p><a
href="https://github.com/prettier/prettier/compare/3.7.2...3.7.3">diff</a></p>
<h4>API: Fix <code>prettier.getFileInfo()</code> change that breaks
VSCode extension (<a
href="https://redirect.github.com/prettier/prettier/pull/18375">#18375</a>
by <a href="https://github.com/fisker"><code>@​fisker</code></a>)</h4>
<p>An internal refactor accidentally broke the VSCode extension plugin
loading.</p>
<h1>3.7.2</h1>
<p><a
href="https://github.com/prettier/prettier/compare/3.7.1...3.7.2">diff</a></p>
<h4>JavaScript: Fix string print when switching quotes (<a
href="https://redirect.github.com/prettier/prettier/pull/18351">#18351</a>
by <a href="https://github.com/fisker"><code>@​fisker</code></a>)</h4>
<!-- raw HTML omitted -->
<pre lang="jsx"><code>// Input
console.log(&quot;A descriptor\\'s .kind must be \&quot;method\&quot; or
\&quot;field\&quot;.&quot;)
<p>// Prettier 3.7.1
console.log('A descriptor\'s .kind must be &quot;method&quot; or
&quot;field&quot;.');</p>
<p>// Prettier 3.7.2
console.log('A descriptor\'s .kind must be &quot;method&quot; or
&quot;field&quot;.');
</code></pre></p>
<h4>JavaScript: Preserve quote for embedded HTML attribute values (<a
href="https://redirect.github.com/prettier/prettier/pull/18352">#18352</a>
by <a href="https://github.com/kovsu"><code>@​kovsu</code></a>)</h4>
<!-- raw HTML omitted -->
<pre lang="tsx"><code>// Input
const html = /* HTML */ ` &lt;div
class=&quot;${styles.banner}&quot;&gt;&lt;/div&gt; `;
<p>// Prettier 3.7.1
const html = /* HTML */ <code>&amp;lt;div
class=${styles.banner}&amp;gt;&amp;lt;/div&amp;gt;</code>;</p>
<p>// Prettier 3.7.2
const html = /* HTML */ <code>&amp;lt;div
class=&amp;quot;${styles.banner}&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;</code>;
</code></pre></p>
<h4>TypeScript: Fix comment in empty type literal (<a
href="https://redirect.github.com/prettier/prettier/pull/18364">#18364</a>
by <a href="https://github.com/fisker"><code>@​fisker</code></a>)</h4>
<!-- raw HTML omitted -->
<pre lang="tsx"><code>// Input
export type XXX = {
  // tbd
};
<p>// Prettier 3.7.1
&lt;/tr&gt;&lt;/table&gt;
</code></pre></p>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/prettier/prettier/commit/fdfa6701767f5140a85902ecc9fb6444f5b4e3f8"><code>fdfa670</code></a>
Release 3.7.3</li>
<li><a
href="https://github.com/prettier/prettier/commit/2dce3ec09081427775cc93a8d92cb828a0129e6f"><code>2dce3ec</code></a>
Fix typo</li>
<li><a
href="https://github.com/prettier/prettier/commit/27d6c645cadeebe86011a195e8058d29888a68f6"><code>27d6c64</code></a>
Revert previous change to <code>getFileInfo</code> (<a
href="https://redirect.github.com/prettier/prettier/issues/18375">#18375</a>)</li>
<li><a
href="https://github.com/prettier/prettier/commit/f4a7afaebfa27b975f6b4e336091cd600b0f5592"><code>f4a7afa</code></a>
Add types for config related functions (<a
href="https://redirect.github.com/prettier/prettier/issues/18376">#18376</a>)</li>
<li><a
href="https://github.com/prettier/prettier/commit/9266e3e85b0dd4c594554cf9a91988e20c006f9b"><code>9266e3e</code></a>
Add resolved test cases (<a
href="https://redirect.github.com/prettier/prettier/issues/18358">#18358</a>)</li>
<li><a
href="https://github.com/prettier/prettier/commit/3bfc014442370ebad42b42e8eceea6d25891148d"><code>3bfc014</code></a>
Bump Prettier dependency to 3.7.2</li>
<li><a
href="https://github.com/prettier/prettier/commit/081b84695b060651dc221afcda7531f4c7731c2e"><code>081b846</code></a>
Clean changelog_unreleased</li>
<li><a
href="https://github.com/prettier/prettier/commit/03384c964d99ef26cb1a7fec68e7e5e263220cea"><code>03384c9</code></a>
Release 3.7.2</li>
<li><a
href="https://github.com/prettier/prettier/commit/514e51afa2046aac7beb651270f0f4ce74a3dafa"><code>514e51a</code></a>
Release <code>@​prettier/plugin-hermes</code> &amp;
<code>@​prettier/plugin-oxc</code> v0.1.2</li>
<li><a
href="https://github.com/prettier/prettier/commit/29a11ae1ae417b8d0fa66d88d656001a6a90e297"><code>29a11ae</code></a>
Fix comment in empty type literal (<a
href="https://redirect.github.com/prettier/prettier/issues/18364">#18364</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/prettier/prettier/compare/3.6.2...3.7.3">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 prettier since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=prettier&package-manager=npm_and_yarn&previous-version=3.6.2&new-version=3.7.3)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 09:12:12 +00:00
Atif Ali 012d1cbd39 chore: fix dependabot terraform module grouping pattern (#20992) 2025-12-01 13:51:56 +05:00
Ethan 782a1052c8 test: use toHaveValue() to avoid flaky parameter verification (#20990)
## Problem

The test `create workspace with default and required parameters` was
flaky because `verifyParameters` in `site/e2e/helpers.ts` didn't wait
for input values to be populated before asserting.

After PR #20710 removed classic parameters, the form now uses dynamic
parameters loaded asynchronously via WebSocket. The input field can be
visible before its value is populated.

Closes https://github.com/coder/internal/issues/1154

## Fix

Replace immediate read + assertion:
```typescript
const value = await parameterField.inputValue();
expect(value).toEqual(buildParameter.value);
```

With Playwright's auto-retrying assertion:
```typescript
await expect(parameterField).toHaveValue(buildParameter.value);
```

From [Playwright docs for
`inputValue()`](https://playwright.dev/docs/api/class-locator#locator-input-value):

> **NOTE** If you need to assert input value, prefer
`expect(locator).toHaveValue(value[, options])` to avoid flakiness. See
assertions guide for more details.

---

This PR was fully generated by [mux](https://github.com/coder/mux), and
reviewed by a human.
2025-12-01 13:46:19 +11:00
dependabot[bot] 7d45d078f2 chore: bump coder/code-server/coder from 1.4.0 to 1.4.1 in /dogfood/coder-envbuilder (#20988)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)



[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=coder/code-server/coder&package-manager=terraform&previous-version=1.4.0&new-version=1.4.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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 00:49:17 +00:00
dependabot[bot] 9179a5971b chore: bump coder/code-server/coder from 1.4.0 to 1.4.1 in /dogfood/coder (#20984)
[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=coder/code-server/coder&package-manager=terraform&previous-version=1.4.0&new-version=1.4.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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 00:49:07 +00:00
dependabot[bot] 976cc61686 chore: bump coder/mux/coder from 1.0.1 to 1.0.2 in /dogfood/coder (#20987)
[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=coder/mux/coder&package-manager=terraform&previous-version=1.0.1&new-version=1.0.2)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 00:49:01 +00:00
dependabot[bot] bc7278d306 chore: bump coder/git-config/coder from 1.0.31 to 1.0.32 in /dogfood/coder (#20985)
[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=coder/git-config/coder&package-manager=terraform&previous-version=1.0.31&new-version=1.0.32)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 00:48:53 +00:00
dependabot[bot] ea8694cb65 chore: bump coder/zed/coder from 1.1.1 to 1.1.2 in /dogfood/coder (#20986)
[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=coder/zed/coder&package-manager=terraform&previous-version=1.1.1&new-version=1.1.2)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 00:48:49 +00:00
dependabot[bot] 8e32f86e44 chore: bump coder/vscode-web/coder from 1.4.1 to 1.4.2 in /dogfood/coder (#20983)
[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=coder/vscode-web/coder&package-manager=terraform&previous-version=1.4.1&new-version=1.4.2)](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
2025-12-01 00:48:35 +00:00
Susana Ferreira f8d9a8046f feat: add notification warning alert to Tasks page (#20900)
## Problem

Users may not realize that task notifications are disabled by default.
To improve awareness, we show a warning alert on the Tasks page when all
task notifications are disabled.

**Alert visibility logic:**
- Shows when **all** task notification templates (Task Working, Task
Idle, Task Completed, Task Failed) are disabled
- Can be dismissed by the user, which stores the dismissal in the user
preferences API
- If the user later enables any task notification in Account Settings,
the dismissal state is cleared so the alert will show again if they
disable all notifications in the future

<img width="2980" height="1588" alt="Screenshot 2025-11-25 at 17 48 17"
src="https://github.com/user-attachments/assets/316bf097-d9d2-4489-bc16-2987ba45f45c"
/>

## Changes

- Added a warning alert to the Tasks page when all task notifications
are disabled
- Introduced new `/users/{user}/preferences` endpoint to manage user
preferences (stored in `user_configs` table)
- Alert is dismissible and stores the dismissal state via the new user
preferences API endpoint
- Enabling any task notification in Account Settings clears the
dismissal state via the preferences API
- Added comprehensive Storybook stories for both TasksPage and
NotificationsPage to test all alert visibility states and interactions

Closes: https://github.com/coder/internal/issues/1089
2025-11-28 16:50:59 +00:00
Marcin Tojek a8862be546 feat(site): add tab to invalidate prebuilds (#20864)
Updates https://github.com/coder/coder/issues/17917
2025-11-28 08:44:38 +01:00
Sas Swart ce627bf23f feat: implement agent socket api, client and cli (#20758)
closes: https://github.com/coder/coder/issues/10352
closes: https://github.com/coder/internal/issues/1094
closes: https://github.com/coder/internal/issues/1095

In this pull request, we enable a new set of experimental cli commands
grouped under `coder exp sync`.
These commands allow any process acting within a coder workspace to
inform the coder agent of its requirements and execution progress. The
coder agent will then relay this information to other processes that
have subscribed.

These commands are:
```
# Check if this feature is enabled in your environment 
coder exp sync ping

# express that your unit depends on another
coder exp sync want <unit> <dependency_unit> 

# express that your unit intends to start a portion of the script that requires 
# other units to have completed first. This command blocks until all dependencies have been met
coder exp sync start <unit> 

# express that your unit has completes its work, allowing dependent units to begin their execution
coder exp sync complete <unit>
```

Example:

In order to automatically run claude code in a new workspace, it must
first have a git repository cloned. The scripts responsible for cloning
the repository and for running claude code would coordinate in the
following way:

```bash
# Script A: Claude code

# Inform the agent that the claude script wants the git script.
# That is, the git script must have completed before the claude script can begin its execution
coder exp sync want claude git

# Inform the agent that we would now like to begin execution of claude.
# This command will block until the git script (and any other defined dependencies)
# have completed
coder exp sync start claude

# Now we run claude code and any other commands we need
claude ...

# Once our script has completed, we inform the agent, so that any scripts that depend on this one
# may begin their execution

coder exp sync complete claude
```

```bash
# Script B: Git

# Because the git script does not have any dependencies, we can simply inform the agent that we 
# intend to start
coder exp sync start git

git clone ssh://git@github.com/coder/coder

# Once the repository have been cloned, we inform the agent that this script is complete, so that
# scripts that depend on it may begin their execution.
coder exp sync complete git
```

Notes:
* Unit names (ie. `claude` and `git`) given as input to the sync
commands are arbitrary strings. You do not have to conform to specific
identifiers. We recommend naming your scripts descriptively, but
succinctly.
* Scripts unit names should be well documented. Other scripts will need
to know the names you've chosen in order to depend on yours. Therefore,
you

---------

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2025-11-28 08:33:50 +02:00
Mathias Fredriksson ee58f40cad feat(site): add bulk delete for tasks (#20905)
This change implements bulk delete for tasks, closely copying UI and
components from workspaces batch actions.

Fixes coder/internal#1088
2025-11-27 16:05:17 +00:00
Susana Ferreira 21efebeadc fix: show task display name in task topbar (#20957)
## Description

Update task topbar in task page to show display name, instead of task
name.

Follow-up PR: https://github.com/coder/coder/pull/20918
Related to internal slack thread:
https://codercom.slack.com/archives/C0992H8HGCS/p1764086497375829
2025-11-27 15:57:35 +00:00
Michael Suchacz dba34da981 fix: make mux mandatory (#20969)
<!--

If you have used AI to produce some or all of this PR, please ensure you
have read our [AI Contribution
guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)
before submitting.

-->
2025-11-27 15:02:17 +00:00
Sas Swart 1d726c81bb fix: remove a sensitive field from an agent log line (#20968)
This PR removes a log field that could expose sensitive information in
agent logs for workspaces that pass such information to the agent via
its manifest.
2025-11-27 16:12:03 +02:00
Danny Kopping ebbdfa03b8 chore: document bedrock setup process for aibridge (#20956)
Documents the steps needed to setup AWS Bedrock

See also: https://github.com/coder/coder/pull/20507

---------

Signed-off-by: Danny Kopping <danny@coder.com>
2025-11-27 10:14:16 +02:00
Jake Howell caf711d70a fix: ensure we check if the user can actually see ai bridge (#20942)
There was a mild regression here where-in if the user only had `AI
Bridge` they wouldn't be able to see this content in the dropdown menu.
This was necessary for reasons earlier with it being an experiment
however its now fine to check for this and won't upset anything.
2025-11-27 17:45:51 +11:00
Callum Styan d22d34e45b fix: pass context with authorization to agentapi (#20959)
The agentapi context needs to be a context with some amount of
authorization attached to it via the context so that the cache refresh
routine can fetch the workspace from the db via GetWorkspaceForAgentID.

---------

Signed-off-by: Callum Styan <callumstyan@gmail.com>
2025-11-26 14:53:16 -08:00
Yevhenii Shcherbina a6a8a060ea docs: update boundary docs (#20958) 2025-11-26 15:33:55 -05:00
Atif Ali e3671f38ff docs: change AI Bridge state from 'early access' to 'beta' (#20951)
<!--

If you have used AI to produce some or all of this PR, please ensure you have read our [AI Contribution guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING) before submitting.

-->
2025-11-27 01:05:30 +05:00
Mathias Fredriksson bcc74b2664 docs: improve code comment guidelines for AI agents (#20952)
This PR enhances the CLAUDE.md document with comprehensive guidelines
for writing better code comments, specifically targeted at AI agents and
LLM-generated code.

## Changes

- **Proper sentence structure**: Comments should end with punctuation
- **Explain why, not what**: Focus on rationale rather than describing
code
- **Line length and wrapping**: 80-character width with natural wrapping

## Example

The guidelines include before/after examples showing the difference
between well-formatted, meaningful comments and poorly written ones.

## Impact

These standards will help ensure AI-generated code includes
professional, maintainable comments that align with project conventions.

---

🤖 This change was written by Claude Sonnet 4.5 Thinking using
[mux](https://github.com/coder/mux) and reviewed by a human 🏂
2025-11-26 18:46:37 +02:00
Mathias Fredriksson 363ed5f64a docs: consolidate cursorrules into AGENTS.md (#20950)
Replace `.cursorrules` with symlink to `AGENTS.md` to establish a single
authoritative document for AI agent guidelines.

Extract architectural documentation from `.cursorrules` into
`.claude/docs/ARCHITECTURE.md` for reference material.

This ensures all AI agents (Claude, Cursor, etc.) use the same
guidelines without duplication.

---

🤖 This change was written by Claude Sonnet 4.5 Thinking using
[mux](https://github.com/coder/mux) and reviewed by a human 🏂
2025-11-26 16:13:16 +02:00
Marcin Tojek 9c7135a61d chore: add license check for prebuilds (#20947)
Related: https://github.com/coder/coder/pull/20864
2025-11-26 15:00:07 +01:00
Mathias Fredriksson b7d8918d60 fix(site): only show active tasks in waiting for input tab (#20933)
This change filters out non-active tasks from the "Waiting for input"
tab filter for the tasks list.

---

🤖 This change was initially written by Claude Code using Coder Tasks, then reviewed and edited by a human 🏂
2025-11-26 13:13:39 +00:00
Danielle Maywood e7dbbcde87 fix: do not notify marked for deletion for deleted workspaces (#20937)
Closes https://github.com/coder/coder/issues/20913

I've ran the test without the fix, verified the test caught the issue,
then applied the fix, and confirmed the issue no longer happens.

---

🤖 PR was initially written by Claude Opus 4.5 Thinking using Claude Code
and then review by a human 👩
2025-11-26 09:23:16 +00:00
Zach bbf7b137da fix(cli): remove defaulting to keyring when --global-config set (#20943)
This fixes a regression that caused the VS code extension to be unable
to authenticate after making keyring usage on by default. This is
because the VS code extension assumes the CLI will always use the
session token stored on disk, specifically in the directory specified by
--global-config.

This fix makes keyring usage enabled when the --global-config directory
is not set. This is a bit wonky but necessary to allow the extension to
continue working without modification and without backwards compat
concerns. In the future we should modify these extensions to either
access the credential in the keyring (like Coder Desktop) or some other
approach that doesn't rely on the session token being stored on disk.

Tests:
`coder login dev.coder.com` -> token stored in keyring
`coder login --global-config=/tmp/ dev.coder.com` -> token stored in
`/tmp/session`
2025-11-26 10:17:31 +01:00
Mykyta Protsenko c87c33f7dd perf: add index to improve the GetWorkspaceAgentByInstanceID query performance (#20936)
## Context

GetWorkspaceAgentByInstanceID has a suboptimal plan. Even though it is
designed to fetch a small subset of records, there are no corresponding
indexes and that query results in full table scan:

Query:

```
SELECT id, auth_instance_id FROM workspace_agents
where auth_instance_id='i-013c2b96b6441648a' and deleted=FALSE;
```

Plan:

```
------------------------------------------------------------------------------------------------------------------
 Seq Scan on workspace_agents  (cost=0.00..222325.48 rows=2 width=36) (actual time=0.012..234.152 rows=4 loops=1)
   Filter: ((NOT deleted) AND ((auth_instance_id)::text = 'i-013c2b96b6441648a'::text))
   Rows Removed by Filter: 302276
 Planning Time: 0.173 ms
 Execution Time: 234.169 ms
```

After adding the index, the plan improves drastically.

Updated plan:

```
 Bitmap Heap Scan on workspace_agents  (cost=4.44..12.32 rows=2 width=36) (actual time=0.019..0.019 rows=0 loops=1)
   Recheck Cond: (((auth_instance_id)::text = 'i-013c2b96b6441648a'::text) AND (NOT deleted))
   ->  Bitmap Index Scan on workspace_agents_auth_instance_id_deleted_idx  (cost=0.00..4.44 rows=2 width=0) (actual time=0.013..0.014 rows=0 loops=1)
         Index Cond: (((auth_instance_id)::text = 'i-013c2b96b6441648a'::text) AND (deleted = false))
 Planning Time: 0.388 ms
 Execution Time: 0.044 ms
```

## Changes

* add an index to optimize this query

## Testing

* ran the queries manually against prod and test DBs
* ran `./scripts/develop.sh`, connected to the local PostgreSQL
instance, inspected the indexes to make sure new index is there:

```
Indexes:
    "workspace_agents_pkey" PRIMARY KEY, btree (id)
    // NEW INDEX CREATED SUCCESSFULLY  [comment is mine]
    "workspace_agents_auth_instance_id_deleted_idx" btree (auth_instance_id, deleted)
    "workspace_agents_auth_token_idx" btree (auth_token)
    "workspace_agents_resource_id_idx" btree (resource_id)
```

---------

Signed-off-by: Danny Kopping <danny@coder.com>
Co-authored-by: Danny Kopping <danny@coder.com>
2025-11-26 05:57:25 +02:00
George K a9261577bc perf: optimize migration 371 to run faster on large deployments (#20906)
closes https://github.com/coder/coder/issues/20899

This is in response to a migration in v2.27 that takes very long on
deployments with large `api_keys` tables.

NOTE: The optimization causes the _up_ migration to delete old data
(keys that expired more than 7 days ago). The _down_ migration won't
resurrect the deleted data.
2025-11-25 21:44:59 -06:00
Benjamin Cohen 9c2f94b574 fix(site): remove erroneous "install Cursor" notification for Cursor Desktop (#20875)
### Summary

This change removes the erroneous “Install Cursor” notification in the
desktop environment when Cursor is already installed. The issue is the
timeout of 500ms was simply too short for Cursor Desktop to respond in
time, so I increased it to 1500ms. This seems to consistently work, but
it could easily be increased to 2000ms to be safe.

### Issue

Fixes #20289

### Testing

- Verified that when Cursor is installed, the notification no longer
appears

### QA

#### Before


https://github.com/user-attachments/assets/facd2e74-6eb7-47ac-935d-7b11974648a0

#### After


https://github.com/user-attachments/assets/ced817d8-ca0c-428c-8436-5f30ffc6134b
2025-11-26 12:59:06 +11:00
Zach 6238a99275 feat(cli)!: enable keyring usage by default (#20851)
Make keyring usage for session token storage on by default for supported
platforms (Windows and macOS), with the ability to opt-out via
--use-keyring=false.

This change will be a breaking change for any users depending on the
session token being stored on disk, though users can restore file usage
via the flag above.

This change will also require CLI users to authenticate after updating.
2025-11-25 18:13:00 -07:00
Asher c266bb830c chore: add debug logging and recovery to agent api requests (#20785)
This is to debug context timeouts on API requests to the agent.

Because rbac and database cannot be imported in slim, split the logger
middleware into slim and non-slim versions and break out the recovery
middleware.
2025-11-25 14:59:20 -09:00
Callum Styan b0e8384b82 perf: reduce DB calls to GetWorkspaceByAgentID via caching workspace info (#20662)
---------

Signed-off-by: Callum Styan <callumstyan@gmail.com>
2025-11-25 14:45:05 -08:00
ケイラ 956cbe7751 chore: remove classic parameters frontend code (#20710) 2025-11-25 15:07:21 -07:00
Andrew Aquino 4863812d8c refactor: replace MUI Tooltip component with Tooltip (simple usage) (#20849)
for #19974 

Redo of #20027, this time splitting it into multiple PRs + using our
existing `Tooltip` component instead of creating a new component (see
below). This PR covers the most basic usage of the MUI Tooltip, i.e.,
the tooltip content is a string literal.

~~Adds a global `TooltipProvider` to `AppProviders` and our Storybook
decorators, so that we don't have to render a `TooltipProvider` for
every tooltip instance. Removing redundant `TooltipProvider`s will be
another separate PR~~ <- this was done by #20869
2025-11-25 13:40:26 -08:00
Danny Kopping e340560164 chore: actually store translated token metadata (#20929)
Signed-off-by: Danny Kopping <danny@coder.com>
2025-11-25 16:50:19 +00:00
Mathias Fredriksson e189dc1f81 fix: complete Tasks GA promotion (docs, site) (#20927)
## Summary

Completes the Coder Tasks GA promotion by updating swagger tags and
regenerating API documentation and updating the frontend API structure.

## Related

Follows #20923 and #20921 which promoted Tasks from Beta/Experimental to
GA.

---

🤖 This change was written by Claude Sonnet 4.5 Thinking using
[mux](https://github.com/coder/mux) and reviewed by a human 🏂
2025-11-25 16:46:13 +00:00
Susana Ferreira 2f399eafae feat(site): use display name field for tasks (#20918)
## Description

This PR updates the frontend to use the new `display_name` field for
tasks. In the tasks list view, `display_name` replaces `initial_prompt`,
and in the tasks sidebar, `display_name` replaces `name`. This is a
follow-up to https://github.com/coder/coder/pull/20856, which introduced
the `display_name` field in the backend.

## Changes

- Display `task.display_name` instead of `task.initial_prompt` in the
tasks table
- Display `task.display_name` instead of `task.name` in the task sidebar
view
- Updated mock data to include `display_name` for all test tasks
- Added Storybook story to showcase display name rendering with
different lengths (short, max length with ellipsis, and edge cases)

Follow-up: https://github.com/coder/coder/pull/20856
Closes: https://github.com/coder/coder/issues/20801
2025-11-25 16:29:54 +00:00
Mathias Fredriksson 02bac71421 feat: promote Tasks to GA (#20923)
## Summary

This change promotes Coder Tasks from Beta to GA by removing Beta labels
from:

- TasksPage UI component
- Documentation files

Tasks feature is now ready for general availability!

---

🤖 This change was written by Claude Sonnet 4.5 Thinking using
[mux](https://github.com/coder/mux) and reviewed by a human 🏂
2025-11-25 17:43:31 +02:00
Danielle Maywood b255827a52 chore: promote tasks to stable from experimental (#20921)
- Promote tasks from `/api/experimental` to `/api/v2`.
- Move sdk from `ExperimentalClient` to `Client`.
- Update swagger
2025-11-25 15:24:25 +00:00
Mathias Fredriksson 37fc6646ad perf(coderd/database): limit GetLatestWorkspaceAppStatusByAppID to 1 row (#20917)
## Description

This PR fixes an issue where `GetLatestWorkspaceAppStatusesByAppID`
returned an unbounded number of rows for a given app ID, which could
cause performance issues for noisy or long-running AI tasks.

## Impact

This change reduces database query overhead for workspace app status
updates, particularly for busy AI tasks that update their status
frequently. Previously, fetching the latest status would return all
historical statuses, now it returns only the most recent one.

Fixes #20862

---

🤖 This change was written by Claude Sonnet 4.5 Thinking using [mux](https://github.com/coder/mux) and reviewed by a human 🏄🏻‍♂️
2025-11-25 16:56:42 +02:00
Yevhenii Shcherbina 5213023fe5 chore: update boundary to v0.2.1 (#20920)
Update boundary to v0.2.1 which uses sys-admin permissions to work with
newer kernel versions.
2025-11-25 09:17:20 -05:00
Danny Kopping ae2c94b322 chore: add @jdomeracki-coder as CODEOWNER of .github dir (#20919)
Supply-chain attacks are on the rise; Jakub is going to keep an eye on
things.

Signed-off-by: Danny Kopping <danny@coder.com>
2025-11-25 13:58:22 +00:00
Mathias Fredriksson ad8ba4aac6 feat(cli): promote tasks commands from experimental to GA (#20916)
## Overview

This change promotes the tasks CLI commands from `coder exp task` to
`coder task`, marking them as generally available (GA).

## Migration

Users will need to update their scripts from:

```shell
coder exp task create "my task"
```

To:
```shell
coder task create "my task"
```

---

🤖 This change was written by Claude Sonnet 4.5 Thinking using [mux](https://github.com/coder/mux) and reviewed by a human 🏄🏻‍♂️
2025-11-25 13:50:22 +00:00
Susana Ferreira 3011207519 feat: add display name field for tasks (#20856)
## Problem

Tasks currently only expose a machine-friendly name field (e.g.
`task-python-debug-a1b2`), but this value is primarily an identifier
rather than a clean, descriptive label. We need a separate
display-friendly name for use in the UI.

This PR introduces a new `display_name` field and updates the task-name
generation flow. The Claude system prompt was updated to return valid
JSON with both `name` and `display_name`. The name generation logic
follows a fallback chain (Anthropic > prompt sanitization > random
fallback). To make task names more closely resemble their display names,
the legacy `task-` prefix has been removed. For context, PR
https://github.com/coder/coder/pull/20834 introduced a small Task icon
to the workspace list to help identify workspaces associated to tasks.

## Changes

- Database migration: Added `display_name` column to tasks table
- Updated system prompt to generate both task name and display name as
valid JSON
- Task name generation now follows a fallback chain: Anthropic > prompt
sanitization > random fallback
- Removed `task-` prefix from task names to allow more descriptive names
- Note: PR https://github.com/coder/coder/pull/20834 adds a Task icon to
workspaces in the workspace list to distinguish task-created workspaces

**Note:** UI changes will be addressed in a follow-up PR

Related to: https://github.com/coder/coder/issues/20801
2025-11-25 13:00:59 +00:00
Atif Ali e8bf074022 chore: update AI client compatibility table in AI Bridge documentation (#20915) 2025-11-25 12:29:47 +00:00
Danielle Maywood 7fd9a450c1 docs: update dev containers documentation to reflect GA status (#20847)
Updates the dev containers documentation to accurately reflect that the
feature is generally available and document all configuration options.

Closes https://github.com/coder/internal/issues/1138

---

🤖 PR was written by Claude Sonnet 4.5 Thinking using [Coder
Mux](https://github.com/coder/cmux) and reviewed by a human 👩
2025-11-25 11:29:11 +00:00
Danielle Maywood 82f525baf3 feat(coderd): add task prompt modification endpoint (#20811)
This PR adds the backend implementation for modifying task prompts. Part
of https://github.com/coder/internal/issues/1084

## Changes

- New `UpdateTaskPrompt` database query to update task prompts
- New PATCH `/api/v2/tasks/{task}/prompt` endpoint

## Notes

This is part 1 of a 2-part PR stack. The frontend UI will be added in a
follow-up PR based on this branch
(https://github.com/coder/coder/pull/20812).

---

🤖 PR was written by Claude Sonnet 4.5 Thinking using [Coder
Mux](https://github.com/coder/cmux) and reviewed by a human 👩
2025-11-25 11:13:32 +00:00
Spike Curtis afd40436f0 fix: mock Agent querying OS for listening ports in tests (#20842)
fixes https://github.com/coder/internal/issues/1123

We want to tests that ports are not included after they are no longer used, but this isn't safe on the real OS networking stack because there is no way to guarantee a port _won't_ be used. Instead, we introduce an interface and fake implementation for testing.

On order to leave the filtering logic in the test path, this PR also does some refactoring.

Caching logic is left in the real OS querying implementation and a new test case is added for it in this PR.
2025-11-25 14:25:24 +04:00
Danny Kopping 823009d9ea chore: document key scopes for OpenAI and Anthropic for aibridge (#20903)
Closes https://github.com/coder/internal/issues/1135

---------

Signed-off-by: Danny Kopping <danny@coder.com>
2025-11-25 11:33:07 +02:00
Danielle Maywood c12303f0b2 fix: allow agents to be created on dormant workspaces (#20909)
Closes https://github.com/coder/coder/issues/20711

We now allow agents to be created on dormant workspaces.

I've ran the test with and without the change. I've confirmed that -
without the fix - it triggers the "rbac: unauthorized" error.
2025-11-25 06:24:33 +00:00
Callum Styan 658e8c34a9 perf: improve performance of metricsAggregator path by reducing memory allocations (#20724)
Signed-off-by: Callum Styan <callumstyan@gmail.com>
2025-11-24 15:45:08 -08:00
316 changed files with 12966 additions and 7609 deletions
+126
View File
@@ -0,0 +1,126 @@
# Coder Architecture
This document provides an overview of Coder's architecture and core systems.
## What is Coder?
Coder is a platform for creating, managing, and using remote development environments (also known as Cloud Development Environments or CDEs). It leverages Terraform to define and provision these environments, which are referred to as "workspaces" within the project. The system is designed to be extensible, secure, and provide developers with a seamless remote development experience.
## Core Architecture
The heart of Coder is a control plane that orchestrates the creation and management of workspaces. This control plane interacts with separate Provisioner processes over gRPC to handle workspace builds. The Provisioners consume workspace definitions and use Terraform to create the actual infrastructure.
The CLI package serves dual purposes - it can be used to launch the control plane itself and also provides client functionality for users to interact with an existing control plane instance. All user-facing frontend code is developed in TypeScript using React and lives in the `site/` directory.
The database layer uses PostgreSQL with SQLC for generating type-safe database code. Database migrations are carefully managed to ensure both forward and backward compatibility through paired `.up.sql` and `.down.sql` files.
## API Design
Coder's API architecture combines REST and gRPC approaches. The REST API is defined in `coderd/coderd.go` and uses Chi for HTTP routing. This provides the primary interface for the frontend and external integrations.
Internal communication with Provisioners occurs over gRPC, with service definitions maintained in `.proto` files. This separation allows for efficient binary communication with the components responsible for infrastructure management while providing a standard REST interface for human-facing applications.
## Network Architecture
Coder implements a secure networking layer based on Tailscale's Wireguard implementation. The `tailnet` package provides connectivity between workspace agents and clients through DERP (Designated Encrypted Relay for Packets) servers when direct connections aren't possible. This creates a secure overlay network allowing access to workspaces regardless of network topology, firewalls, or NAT configurations.
### Tailnet and DERP System
The networking system has three key components:
1. **Tailnet**: An overlay network implemented in the `tailnet` package that provides secure, end-to-end encrypted connections between clients, the Coder server, and workspace agents.
2. **DERP Servers**: These relay traffic when direct connections aren't possible. Coder provides several options:
- A built-in DERP server that runs on the Coder control plane
- Integration with Tailscale's global DERP infrastructure
- Support for custom DERP servers for lower latency or offline deployments
3. **Direct Connections**: When possible, the system establishes peer-to-peer connections between clients and workspaces using STUN for NAT traversal. This requires both endpoints to send UDP traffic on ephemeral ports.
### Workspace Proxies
Workspace proxies (in the Enterprise edition) provide regional relay points for browser-based connections, reducing latency for geo-distributed teams. Key characteristics:
- Deployed as independent servers that authenticate with the Coder control plane
- Relay connections for SSH, workspace apps, port forwarding, and web terminals
- Do not make direct database connections
- Managed through the `coder wsproxy` commands
- Implemented primarily in the `enterprise/wsproxy/` package
## Agent System
The workspace agent runs within each provisioned workspace and provides core functionality including:
- SSH access to workspaces via the `agentssh` package
- Port forwarding
- Terminal connectivity via the `pty` package for pseudo-terminal support
- Application serving
- Healthcheck monitoring
- Resource usage reporting
Agents communicate with the control plane using the tailnet system and authenticate using secure tokens.
## Workspace Applications
Workspace applications (or "apps") provide browser-based access to services running within workspaces. The system supports:
- HTTP(S) and WebSocket connections
- Path-based or subdomain-based access URLs
- Health checks to monitor application availability
- Different sharing levels (owner-only, authenticated users, or public)
- Custom icons and display settings
The implementation is primarily in the `coderd/workspaceapps/` directory with components for URL generation, proxying connections, and managing application state.
## Implementation Details
The project structure separates frontend and backend concerns. React components and pages are organized in the `site/src/` directory, with Jest used for testing. The backend is primarily written in Go, with a strong emphasis on error handling patterns and test coverage.
Database interactions are carefully managed through migrations in `coderd/database/migrations/` and queries in `coderd/database/queries/`. All new queries require proper database authorization (dbauthz) implementation to ensure that only users with appropriate permissions can access specific resources.
## Authorization System
The database authorization (dbauthz) system enforces fine-grained access control across all database operations. It uses role-based access control (RBAC) to validate user permissions before executing database operations. The `dbauthz` package wraps the database store and performs authorization checks before returning data. All database operations must pass through this layer to ensure security.
## Testing Framework
The codebase has a comprehensive testing approach with several key components:
1. **Parallel Testing**: All tests must use `t.Parallel()` to run concurrently, which improves test suite performance and helps identify race conditions.
2. **coderdtest Package**: This package in `coderd/coderdtest/` provides utilities for creating test instances of the Coder server, setting up test users and workspaces, and mocking external components.
3. **Integration Tests**: Tests often span multiple components to verify system behavior, such as template creation, workspace provisioning, and agent connectivity.
4. **Enterprise Testing**: Enterprise features have dedicated test utilities in the `coderdenttest` package.
## Open Source and Enterprise Components
The repository contains both open source and enterprise components:
- Enterprise code lives primarily in the `enterprise/` directory
- Enterprise features focus on governance, scalability (high availability), and advanced deployment options like workspace proxies
- The boundary between open source and enterprise is managed through a licensing system
- The same core codebase supports both editions, with enterprise features conditionally enabled
## Development Philosophy
Coder emphasizes clear error handling, with specific patterns required:
- Concise error messages that avoid phrases like "failed to"
- Wrapping errors with `%w` to maintain error chains
- Using sentinel errors with the "err" prefix (e.g., `errNotFound`)
All tests should run in parallel using `t.Parallel()` to ensure efficient testing and expose potential race conditions. The codebase is rigorously linted with golangci-lint to maintain consistent code quality.
Git contributions follow a standard format with commit messages structured as `type: <message>`, where type is one of `feat`, `fix`, or `chore`.
## Development Workflow
Development can be initiated using `scripts/develop.sh` to start the application after making changes. Database schema updates should be performed through the migration system using `create_migration.sh <name>` to generate migration files, with each `.up.sql` migration paired with a corresponding `.down.sql` that properly reverts all changes.
If the development database gets into a bad state, it can be completely reset by removing the PostgreSQL data directory with `rm -rf .coderv2/postgres`. This will destroy all data in the development database, requiring you to recreate any test users, templates, or workspaces after restarting the application.
Code generation for the database layer uses `coderd/database/generate.sh`, and developers should refer to `sqlc.yaml` for the appropriate style and patterns to follow when creating new queries or tables.
The focus should always be on maintaining security through proper database authorization, clean error handling, and comprehensive test coverage to ensure the platform remains robust and reliable.
-124
View File
@@ -1,124 +0,0 @@
# Cursor Rules
This project is called "Coder" - an application for managing remote development environments.
Coder provides a platform for creating, managing, and using remote development environments (also known as Cloud Development Environments or CDEs). It leverages Terraform to define and provision these environments, which are referred to as "workspaces" within the project. The system is designed to be extensible, secure, and provide developers with a seamless remote development experience.
## Core Architecture
The heart of Coder is a control plane that orchestrates the creation and management of workspaces. This control plane interacts with separate Provisioner processes over gRPC to handle workspace builds. The Provisioners consume workspace definitions and use Terraform to create the actual infrastructure.
The CLI package serves dual purposes - it can be used to launch the control plane itself and also provides client functionality for users to interact with an existing control plane instance. All user-facing frontend code is developed in TypeScript using React and lives in the `site/` directory.
The database layer uses PostgreSQL with SQLC for generating type-safe database code. Database migrations are carefully managed to ensure both forward and backward compatibility through paired `.up.sql` and `.down.sql` files.
## API Design
Coder's API architecture combines REST and gRPC approaches. The REST API is defined in `coderd/coderd.go` and uses Chi for HTTP routing. This provides the primary interface for the frontend and external integrations.
Internal communication with Provisioners occurs over gRPC, with service definitions maintained in `.proto` files. This separation allows for efficient binary communication with the components responsible for infrastructure management while providing a standard REST interface for human-facing applications.
## Network Architecture
Coder implements a secure networking layer based on Tailscale's Wireguard implementation. The `tailnet` package provides connectivity between workspace agents and clients through DERP (Designated Encrypted Relay for Packets) servers when direct connections aren't possible. This creates a secure overlay network allowing access to workspaces regardless of network topology, firewalls, or NAT configurations.
### Tailnet and DERP System
The networking system has three key components:
1. **Tailnet**: An overlay network implemented in the `tailnet` package that provides secure, end-to-end encrypted connections between clients, the Coder server, and workspace agents.
2. **DERP Servers**: These relay traffic when direct connections aren't possible. Coder provides several options:
- A built-in DERP server that runs on the Coder control plane
- Integration with Tailscale's global DERP infrastructure
- Support for custom DERP servers for lower latency or offline deployments
3. **Direct Connections**: When possible, the system establishes peer-to-peer connections between clients and workspaces using STUN for NAT traversal. This requires both endpoints to send UDP traffic on ephemeral ports.
### Workspace Proxies
Workspace proxies (in the Enterprise edition) provide regional relay points for browser-based connections, reducing latency for geo-distributed teams. Key characteristics:
- Deployed as independent servers that authenticate with the Coder control plane
- Relay connections for SSH, workspace apps, port forwarding, and web terminals
- Do not make direct database connections
- Managed through the `coder wsproxy` commands
- Implemented primarily in the `enterprise/wsproxy/` package
## Agent System
The workspace agent runs within each provisioned workspace and provides core functionality including:
- SSH access to workspaces via the `agentssh` package
- Port forwarding
- Terminal connectivity via the `pty` package for pseudo-terminal support
- Application serving
- Healthcheck monitoring
- Resource usage reporting
Agents communicate with the control plane using the tailnet system and authenticate using secure tokens.
## Workspace Applications
Workspace applications (or "apps") provide browser-based access to services running within workspaces. The system supports:
- HTTP(S) and WebSocket connections
- Path-based or subdomain-based access URLs
- Health checks to monitor application availability
- Different sharing levels (owner-only, authenticated users, or public)
- Custom icons and display settings
The implementation is primarily in the `coderd/workspaceapps/` directory with components for URL generation, proxying connections, and managing application state.
## Implementation Details
The project structure separates frontend and backend concerns. React components and pages are organized in the `site/src/` directory, with Jest used for testing. The backend is primarily written in Go, with a strong emphasis on error handling patterns and test coverage.
Database interactions are carefully managed through migrations in `coderd/database/migrations/` and queries in `coderd/database/queries/`. All new queries require proper database authorization (dbauthz) implementation to ensure that only users with appropriate permissions can access specific resources.
## Authorization System
The database authorization (dbauthz) system enforces fine-grained access control across all database operations. It uses role-based access control (RBAC) to validate user permissions before executing database operations. The `dbauthz` package wraps the database store and performs authorization checks before returning data. All database operations must pass through this layer to ensure security.
## Testing Framework
The codebase has a comprehensive testing approach with several key components:
1. **Parallel Testing**: All tests must use `t.Parallel()` to run concurrently, which improves test suite performance and helps identify race conditions.
2. **coderdtest Package**: This package in `coderd/coderdtest/` provides utilities for creating test instances of the Coder server, setting up test users and workspaces, and mocking external components.
3. **Integration Tests**: Tests often span multiple components to verify system behavior, such as template creation, workspace provisioning, and agent connectivity.
4. **Enterprise Testing**: Enterprise features have dedicated test utilities in the `coderdenttest` package.
## Open Source and Enterprise Components
The repository contains both open source and enterprise components:
- Enterprise code lives primarily in the `enterprise/` directory
- Enterprise features focus on governance, scalability (high availability), and advanced deployment options like workspace proxies
- The boundary between open source and enterprise is managed through a licensing system
- The same core codebase supports both editions, with enterprise features conditionally enabled
## Development Philosophy
Coder emphasizes clear error handling, with specific patterns required:
- Concise error messages that avoid phrases like "failed to"
- Wrapping errors with `%w` to maintain error chains
- Using sentinel errors with the "err" prefix (e.g., `errNotFound`)
All tests should run in parallel using `t.Parallel()` to ensure efficient testing and expose potential race conditions. The codebase is rigorously linted with golangci-lint to maintain consistent code quality.
Git contributions follow a standard format with commit messages structured as `type: <message>`, where type is one of `feat`, `fix`, or `chore`.
## Development Workflow
Development can be initiated using `scripts/develop.sh` to start the application after making changes. Database schema updates should be performed through the migration system using `create_migration.sh <name>` to generate migration files, with each `.up.sql` migration paired with a corresponding `.down.sql` that properly reverts all changes.
If the development database gets into a bad state, it can be completely reset by removing the PostgreSQL data directory with `rm -rf .coderv2/postgres`. This will destroy all data in the development database, requiring you to recreate any test users, templates, or workspaces after restarting the application.
Code generation for the database layer uses `coderd/database/generate.sh`, and developers should refer to `sqlc.yaml` for the appropriate style and patterns to follow when creating new queries or tables.
The focus should always be on maintaining security through proper database authorization, clean error handling, and comprehensive test coverage to ensure the platform remains robust and reliable.
+1
View File
@@ -0,0 +1 @@
AGENTS.md
+1 -1
View File
@@ -27,7 +27,7 @@ ignorePatterns:
- pattern: "splunk.com"
- pattern: "stackoverflow.com/questions"
- pattern: "developer.hashicorp.com/terraform/language"
- pattern: "platform.openai.com/docs/api-reference"
- pattern: "platform.openai.com"
- pattern: "api.openai.com"
aliveStatusCodes:
- 200
+2 -2
View File
@@ -119,9 +119,9 @@ updates:
commit-message:
prefix: "chore"
groups:
coder:
coder-modules:
patterns:
- "registry.coder.com/coder/*/coder"
- "coder/*/coder"
labels: []
ignore:
- dependency-name: "*"
+20 -20
View File
@@ -40,7 +40,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
@@ -124,7 +124,7 @@ jobs:
# runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
# steps:
# - name: Checkout
# uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
# uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
# with:
# fetch-depth: 1
# # See: https://github.com/stefanzweifel/git-auto-commit-action?tab=readme-ov-file#commits-made-by-this-action-do-not-trigger-new-workflow-runs
@@ -162,7 +162,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
@@ -191,7 +191,7 @@ jobs:
# Check for any typos
- name: Check for typos
uses: crate-ci/typos@626c4bedb751ce0b7f03262ca97ddda9a076ae1c # v1.39.2
uses: crate-ci/typos@2d0ce569feab1f8752f1dde43cc2f2aa53236e06 # v1.40.0
with:
config: .github/workflows/typos.toml
@@ -240,7 +240,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
@@ -297,7 +297,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
@@ -369,7 +369,7 @@ jobs:
uses: coder/setup-ramdisk-action@e1100847ab2d7bcd9d14bcda8f2d1b0f07b36f1b # v0.1.0
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
@@ -537,7 +537,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
@@ -586,7 +586,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
@@ -646,7 +646,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
@@ -673,7 +673,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
@@ -706,7 +706,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
@@ -786,7 +786,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
# 👇 Ensures Chromatic can read your full git history
fetch-depth: 0
@@ -802,7 +802,7 @@ jobs:
# the check to pass. This is desired in PRs, but not in mainline.
- name: Publish to Chromatic (non-mainline)
if: github.ref != 'refs/heads/main' && github.repository_owner == 'coder'
uses: chromaui/action@ac86f2ff0a458ffbce7b40698abd44c0fa34d4b6 # v13.3.3
uses: chromaui/action@4c20b95e9d3209ecfdf9cd6aace6bbde71ba1694 # v13.3.4
env:
NODE_OPTIONS: "--max_old_space_size=4096"
STORYBOOK: true
@@ -834,7 +834,7 @@ jobs:
# infinitely "in progress" in mainline unless we re-review each build.
- name: Publish to Chromatic (mainline)
if: github.ref == 'refs/heads/main' && github.repository_owner == 'coder'
uses: chromaui/action@ac86f2ff0a458ffbce7b40698abd44c0fa34d4b6 # v13.3.3
uses: chromaui/action@4c20b95e9d3209ecfdf9cd6aace6bbde71ba1694 # v13.3.4
env:
NODE_OPTIONS: "--max_old_space_size=4096"
STORYBOOK: true
@@ -867,7 +867,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
# 0 is required here for version.sh to work.
fetch-depth: 0
@@ -971,7 +971,7 @@ jobs:
steps:
# Harden Runner doesn't work on macOS
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -1058,7 +1058,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -1113,7 +1113,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -1510,7 +1510,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
+4 -4
View File
@@ -41,7 +41,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -70,7 +70,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -92,7 +92,7 @@ jobs:
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1
- name: Set up Flux CLI
uses: fluxcd/flux2/action@b6e76ca2534f76dcb8dd94fb057cdfa923c3b641 # v2.7.3
uses: fluxcd/flux2/action@8454b02a32e48d775b9f563cb51fdcb1787b5b93 # v2.7.5
with:
# Keep this and the github action up to date with the version of flux installed in dogfood cluster
version: "2.7.0"
@@ -151,7 +151,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
+1 -1
View File
@@ -162,7 +162,7 @@ jobs:
} >> "${GITHUB_OUTPUT}"
- name: Checkout create-task-action
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
path: ./.github/actions/create-task-action
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
+2 -2
View File
@@ -23,14 +23,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
- name: Setup Node
uses: ./.github/actions/setup-node
- uses: tj-actions/changed-files@70069877f29101175ed2b055d210fe8b1d54d7d7 # v45.0.7
- uses: tj-actions/changed-files@abdd2f68ea150cee8f236d4a9fb4e0f2491abf1b # v45.0.7
id: changed-files
with:
files: |
+2 -2
View File
@@ -31,7 +31,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
@@ -130,7 +130,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
+1 -1
View File
@@ -53,7 +53,7 @@ jobs:
uses: coder/setup-ramdisk-action@e1100847ab2d7bcd9d14bcda8f2d1b0f07b36f1b # v0.1.0
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
+4 -4
View File
@@ -44,7 +44,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
@@ -81,7 +81,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -233,7 +233,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -337,7 +337,7 @@ jobs:
kubectl create namespace "pr${PR_NUMBER}"
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
+4 -4
View File
@@ -65,7 +65,7 @@ jobs:
steps:
# Harden Runner doesn't work on macOS.
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -169,7 +169,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -888,7 +888,7 @@ jobs:
GH_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -976,7 +976,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
persist-credentials: false
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
egress-policy: audit
- name: "Checkout code"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
@@ -47,6 +47,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5
uses: github/codeql-action/upload-sarif@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5
with:
sarif_file: results.sarif
+5 -5
View File
@@ -32,7 +32,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
@@ -40,7 +40,7 @@ jobs:
uses: ./.github/actions/setup-go
- name: Initialize CodeQL
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5
uses: github/codeql-action/init@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5
with:
languages: go, javascript
@@ -50,7 +50,7 @@ jobs:
rm Makefile
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5
uses: github/codeql-action/analyze@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5
- name: Send Slack notification on failure
if: ${{ failure() }}
@@ -74,7 +74,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 0
persist-credentials: false
@@ -154,7 +154,7 @@ jobs:
severity: "CRITICAL,HIGH"
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v3.29.5
uses: github/codeql-action/upload-sarif@fe4161a26a8629af62121b670040955b330f9af2 # v3.29.5
with:
sarif_file: trivy-results.sarif
category: "Trivy"
+1 -1
View File
@@ -101,7 +101,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
- name: Run delete-old-branches-action
+1 -1
View File
@@ -153,7 +153,7 @@ jobs:
} >> "${GITHUB_OUTPUT}"
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: 1
path: ./.github/actions/create-task-action
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
+34
View File
@@ -140,8 +140,42 @@ seems like it should use `time.Sleep`, read through https://github.com/coder/qua
- Follow [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md)
- Commit format: `type(scope): message`
### Writing Comments
Code comments should be clear, well-formatted, and add meaningful context.
**Proper sentence structure**: Comments are sentences and should end with
periods or other appropriate punctuation. This improves readability and
maintains professional code standards.
**Explain why, not what**: Good comments explain the reasoning behind code
rather than describing what the code does. The code itself should be
self-documenting through clear naming and structure. Focus your comments on
non-obvious decisions, edge cases, or business logic that isn't immediately
apparent from reading the implementation.
**Line length and wrapping**: Keep comment lines to 80 characters wide
(including the comment prefix like `//` or `#`). When a comment spans multiple
lines, wrap it naturally at word boundaries rather than writing one sentence
per line. This creates more readable, paragraph-like blocks of documentation.
```go
// Good: Explains the rationale with proper sentence structure.
// We need a custom timeout here because workspace builds can take several
// minutes on slow networks, and the default 30s timeout causes false
// failures during initial template imports.
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
// Bad: Describes what the code does without punctuation or wrapping
// Set a custom timeout
// Workspace builds can take a long time
// Default timeout is too short
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
```
## Detailed Development Guides
@.claude/docs/ARCHITECTURE.md
@.claude/docs/OAUTH2.md
@.claude/docs/TESTING.md
@.claude/docs/TROUBLESHOOTING.md
+2
View File
@@ -27,3 +27,5 @@ coderd/schedule/autostop.go @deansheather @DanielleMaywood
# well as guidance from revenue.
coderd/usage/ @deansheather @spikecurtis
enterprise/coderd/usage/ @deansheather @spikecurtis
.github/ @jdomeracki-coder
+4 -4
View File
@@ -679,7 +679,7 @@ gen/db: $(DB_GEN_FILES)
gen/golden-files: \
agent/unit/testdata/.gen-golden \
cli/testdata/.gen-golden \
coderd/.gen-golden \
coderd/insightsapi/.gen-golden \
coderd/notifications/.gen-golden \
enterprise/cli/testdata/.gen-golden \
enterprise/tailnet/testdata/.gen-golden \
@@ -953,7 +953,7 @@ clean/golden-files:
find \
cli/testdata \
coderd/notifications/testdata \
coderd/testdata \
coderd/insightsapi/testdata \
enterprise/cli/testdata \
enterprise/tailnet/testdata \
helm/coder/tests/testdata \
@@ -991,8 +991,8 @@ helm/provisioner/tests/testdata/.gen-golden: $(wildcard helm/provisioner/tests/t
TZ=UTC go test ./helm/provisioner/tests -run=TestUpdateGoldenFiles -update
touch "$@"
coderd/.gen-golden: $(wildcard coderd/testdata/*/*.golden) $(GO_SRC_FILES) $(wildcard coderd/*_test.go)
TZ=UTC go test ./coderd -run="Test.*Golden$$" -update
coderd/insightsapi/.gen-golden: $(wildcard coderd/insightsapi/testdata/*/*.golden) $(GO_SRC_FILES) $(wildcard coderd/insightsapi/*_test.go)
TZ=UTC go test ./coderd/insightsapi -run="Test.*Golden$$" -update
touch "$@"
coderd/notifications/.gen-golden: $(wildcard coderd/notifications/testdata/*/*.golden) $(GO_SRC_FILES) $(wildcard coderd/notifications/*_test.go)
+96 -49
View File
@@ -8,6 +8,7 @@ import (
"fmt"
"hash/fnv"
"io"
"maps"
"net"
"net/http"
"net/netip"
@@ -40,6 +41,7 @@ import (
"github.com/coder/coder/v2/agent/agentcontainers"
"github.com/coder/coder/v2/agent/agentexec"
"github.com/coder/coder/v2/agent/agentscripts"
"github.com/coder/coder/v2/agent/agentsocket"
"github.com/coder/coder/v2/agent/agentssh"
"github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/agent/proto/resourcesmonitor"
@@ -70,16 +72,21 @@ const (
)
type Options struct {
Filesystem afero.Fs
LogDir string
TempDir string
ScriptDataDir string
Client Client
ReconnectingPTYTimeout time.Duration
EnvironmentVariables map[string]string
Logger slog.Logger
IgnorePorts map[int]string
PortCacheDuration time.Duration
Filesystem afero.Fs
LogDir string
TempDir string
ScriptDataDir string
Client Client
ReconnectingPTYTimeout time.Duration
EnvironmentVariables map[string]string
Logger slog.Logger
// IgnorePorts tells the api handler which ports to ignore when
// listing all listening ports. This is helpful to hide ports that
// are used by the agent, that the user does not care about.
IgnorePorts map[int]string
// ListeningPortsGetter is used to get the list of listening ports. Only
// tests should set this. If unset, a default that queries the OS will be used.
ListeningPortsGetter ListeningPortsGetter
SSHMaxTimeout time.Duration
TailnetListenPort uint16
Subsystems []codersdk.AgentSubsystem
@@ -91,6 +98,8 @@ type Options struct {
Devcontainers bool
DevcontainerAPIOptions []agentcontainers.Option // Enable Devcontainers for these to be effective.
Clock quartz.Clock
SocketServerEnabled bool
SocketPath string // Path for the agent socket server socket
}
type Client interface {
@@ -137,9 +146,7 @@ func New(options Options) Agent {
if options.ServiceBannerRefreshInterval == 0 {
options.ServiceBannerRefreshInterval = 2 * time.Minute
}
if options.PortCacheDuration == 0 {
options.PortCacheDuration = 1 * time.Second
}
if options.Clock == nil {
options.Clock = quartz.NewReal()
}
@@ -153,30 +160,38 @@ func New(options Options) Agent {
options.Execer = agentexec.DefaultExecer
}
if options.ListeningPortsGetter == nil {
options.ListeningPortsGetter = &osListeningPortsGetter{
cacheDuration: 1 * time.Second,
}
}
hardCtx, hardCancel := context.WithCancel(context.Background())
gracefulCtx, gracefulCancel := context.WithCancel(hardCtx)
a := &agent{
clock: options.Clock,
tailnetListenPort: options.TailnetListenPort,
reconnectingPTYTimeout: options.ReconnectingPTYTimeout,
logger: options.Logger,
gracefulCtx: gracefulCtx,
gracefulCancel: gracefulCancel,
hardCtx: hardCtx,
hardCancel: hardCancel,
coordDisconnected: make(chan struct{}),
environmentVariables: options.EnvironmentVariables,
client: options.Client,
filesystem: options.Filesystem,
logDir: options.LogDir,
tempDir: options.TempDir,
scriptDataDir: options.ScriptDataDir,
lifecycleUpdate: make(chan struct{}, 1),
lifecycleReported: make(chan codersdk.WorkspaceAgentLifecycle, 1),
lifecycleStates: []agentsdk.PostLifecycleRequest{{State: codersdk.WorkspaceAgentLifecycleCreated}},
reportConnectionsUpdate: make(chan struct{}, 1),
ignorePorts: options.IgnorePorts,
portCacheDuration: options.PortCacheDuration,
clock: options.Clock,
tailnetListenPort: options.TailnetListenPort,
reconnectingPTYTimeout: options.ReconnectingPTYTimeout,
logger: options.Logger,
gracefulCtx: gracefulCtx,
gracefulCancel: gracefulCancel,
hardCtx: hardCtx,
hardCancel: hardCancel,
coordDisconnected: make(chan struct{}),
environmentVariables: options.EnvironmentVariables,
client: options.Client,
filesystem: options.Filesystem,
logDir: options.LogDir,
tempDir: options.TempDir,
scriptDataDir: options.ScriptDataDir,
lifecycleUpdate: make(chan struct{}, 1),
lifecycleReported: make(chan codersdk.WorkspaceAgentLifecycle, 1),
lifecycleStates: []agentsdk.PostLifecycleRequest{{State: codersdk.WorkspaceAgentLifecycleCreated}},
reportConnectionsUpdate: make(chan struct{}, 1),
listeningPortsHandler: listeningPortsHandler{
getter: options.ListeningPortsGetter,
ignorePorts: maps.Clone(options.IgnorePorts),
},
reportMetadataInterval: options.ReportMetadataInterval,
announcementBannersRefreshInterval: options.ServiceBannerRefreshInterval,
sshMaxTimeout: options.SSHMaxTimeout,
@@ -190,6 +205,8 @@ func New(options Options) Agent {
devcontainers: options.Devcontainers,
containerAPIOptions: options.DevcontainerAPIOptions,
socketPath: options.SocketPath,
socketServerEnabled: options.SocketServerEnabled,
}
// Initially, we have a closed channel, reflecting the fact that we are not initially connected.
// Each time we connect we replace the channel (while holding the closeMutex) with a new one
@@ -202,20 +219,16 @@ func New(options Options) Agent {
}
type agent struct {
clock quartz.Clock
logger slog.Logger
client Client
tailnetListenPort uint16
filesystem afero.Fs
logDir string
tempDir string
scriptDataDir string
// ignorePorts tells the api handler which ports to ignore when
// listing all listening ports. This is helpful to hide ports that
// are used by the agent, that the user does not care about.
ignorePorts map[int]string
portCacheDuration time.Duration
subsystems []codersdk.AgentSubsystem
clock quartz.Clock
logger slog.Logger
client Client
tailnetListenPort uint16
filesystem afero.Fs
logDir string
tempDir string
scriptDataDir string
listeningPortsHandler listeningPortsHandler
subsystems []codersdk.AgentSubsystem
reconnectingPTYTimeout time.Duration
reconnectingPTYServer *reconnectingpty.Server
@@ -271,6 +284,10 @@ type agent struct {
devcontainers bool
containerAPIOptions []agentcontainers.Option
containerAPI *agentcontainers.API
socketServerEnabled bool
socketPath string
socketServer *agentsocket.Server
}
func (a *agent) TailnetConn() *tailnet.Conn {
@@ -350,9 +367,32 @@ func (a *agent) init() {
s.ExperimentalContainers = a.devcontainers
},
)
a.initSocketServer()
go a.runLoop()
}
// initSocketServer initializes server that allows direct communication with a workspace agent using IPC.
func (a *agent) initSocketServer() {
if !a.socketServerEnabled {
a.logger.Info(a.hardCtx, "socket server is disabled")
return
}
server, err := agentsocket.NewServer(
a.logger.Named("socket"),
agentsocket.WithPath(a.socketPath),
)
if err != nil {
a.logger.Warn(a.hardCtx, "failed to create socket server", slog.Error(err), slog.F("path", a.socketPath))
return
}
a.socketServer = server
a.logger.Debug(a.hardCtx, "socket server started", slog.F("path", a.socketPath))
}
// runLoop attempts to start the agent in a retry loop.
// Coder may be offline temporarily, a connection issue
// may be happening, but regardless after the intermittent
@@ -1087,7 +1127,7 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context,
if err != nil {
return xerrors.Errorf("fetch metadata: %w", err)
}
a.logger.Info(ctx, "fetched manifest", slog.F("manifest", mp))
a.logger.Info(ctx, "fetched manifest")
manifest, err := agentsdk.ManifestFromProto(mp)
if err != nil {
a.logger.Critical(ctx, "failed to convert manifest", slog.F("manifest", mp), slog.Error(err))
@@ -1920,6 +1960,7 @@ func (a *agent) Close() error {
lifecycleState = codersdk.WorkspaceAgentLifecycleShutdownError
}
}
a.setLifecycle(lifecycleState)
err = a.scriptRunner.Close()
@@ -1927,6 +1968,12 @@ func (a *agent) Close() error {
a.logger.Error(a.hardCtx, "script runner close", slog.Error(err))
}
if a.socketServer != nil {
if err := a.socketServer.Close(); err != nil {
a.logger.Error(a.hardCtx, "socket server close", slog.Error(err))
}
}
if err := a.containerAPI.Close(); err != nil {
a.logger.Error(a.hardCtx, "container API close", slog.Error(err))
}
+146
View File
@@ -0,0 +1,146 @@
package agentsocket
import (
"context"
"golang.org/x/xerrors"
"storj.io/drpc"
"storj.io/drpc/drpcconn"
"github.com/coder/coder/v2/agent/agentsocket/proto"
"github.com/coder/coder/v2/agent/unit"
)
// Option represents a configuration option for NewClient.
type Option func(*options)
type options struct {
path string
}
// WithPath sets the socket path. If not provided or empty, the client will
// auto-discover the default socket path.
func WithPath(path string) Option {
return func(opts *options) {
if path == "" {
return
}
opts.path = path
}
}
// Client provides a client for communicating with the workspace agentsocket API.
type Client struct {
client proto.DRPCAgentSocketClient
conn drpc.Conn
}
// NewClient creates a new socket client and opens a connection to the socket.
// If path is not provided via WithPath or is empty, it will auto-discover the
// default socket path.
func NewClient(ctx context.Context, opts ...Option) (*Client, error) {
options := &options{}
for _, opt := range opts {
opt(options)
}
conn, err := dialSocket(ctx, options.path)
if err != nil {
return nil, xerrors.Errorf("connect to socket: %w", err)
}
drpcConn := drpcconn.New(conn)
client := proto.NewDRPCAgentSocketClient(drpcConn)
return &Client{
client: client,
conn: drpcConn,
}, nil
}
// Close closes the socket connection.
func (c *Client) Close() error {
return c.conn.Close()
}
// Ping sends a ping request to the agent.
func (c *Client) Ping(ctx context.Context) error {
_, err := c.client.Ping(ctx, &proto.PingRequest{})
return err
}
// SyncStart starts a unit in the dependency graph.
func (c *Client) SyncStart(ctx context.Context, unitName unit.ID) error {
_, err := c.client.SyncStart(ctx, &proto.SyncStartRequest{
Unit: string(unitName),
})
return err
}
// SyncWant declares a dependency between units.
func (c *Client) SyncWant(ctx context.Context, unitName, dependsOn unit.ID) error {
_, err := c.client.SyncWant(ctx, &proto.SyncWantRequest{
Unit: string(unitName),
DependsOn: string(dependsOn),
})
return err
}
// SyncComplete marks a unit as complete in the dependency graph.
func (c *Client) SyncComplete(ctx context.Context, unitName unit.ID) error {
_, err := c.client.SyncComplete(ctx, &proto.SyncCompleteRequest{
Unit: string(unitName),
})
return err
}
// SyncReady requests whether a unit is ready to be started. That is, all dependencies are satisfied.
func (c *Client) SyncReady(ctx context.Context, unitName unit.ID) (bool, error) {
resp, err := c.client.SyncReady(ctx, &proto.SyncReadyRequest{
Unit: string(unitName),
})
return resp.Ready, err
}
// SyncStatus gets the status of a unit and its dependencies.
func (c *Client) SyncStatus(ctx context.Context, unitName unit.ID) (SyncStatusResponse, error) {
resp, err := c.client.SyncStatus(ctx, &proto.SyncStatusRequest{
Unit: string(unitName),
})
if err != nil {
return SyncStatusResponse{}, err
}
var dependencies []DependencyInfo
for _, dep := range resp.Dependencies {
dependencies = append(dependencies, DependencyInfo{
DependsOn: unit.ID(dep.DependsOn),
RequiredStatus: unit.Status(dep.RequiredStatus),
CurrentStatus: unit.Status(dep.CurrentStatus),
IsSatisfied: dep.IsSatisfied,
})
}
return SyncStatusResponse{
UnitName: unitName,
Status: unit.Status(resp.Status),
IsReady: resp.IsReady,
Dependencies: dependencies,
}, nil
}
// SyncStatusResponse contains the status information for a unit.
type SyncStatusResponse struct {
UnitName unit.ID `table:"unit,default_sort" json:"unit_name"`
Status unit.Status `table:"status" json:"status"`
IsReady bool `table:"ready" json:"is_ready"`
Dependencies []DependencyInfo `table:"dependencies" json:"dependencies"`
}
// DependencyInfo contains information about a unit dependency.
type DependencyInfo struct {
DependsOn unit.ID `table:"depends on,default_sort" json:"depends_on"`
RequiredStatus unit.Status `table:"required status" json:"required_status"`
CurrentStatus unit.Status `table:"current status" json:"current_status"`
IsSatisfied bool `table:"satisfied" json:"is_satisfied"`
}
+12 -59
View File
@@ -7,8 +7,6 @@ import (
"sync"
"golang.org/x/xerrors"
"github.com/hashicorp/yamux"
"storj.io/drpc/drpcmux"
"storj.io/drpc/drpcserver"
@@ -33,11 +31,17 @@ type Server struct {
wg sync.WaitGroup
}
func NewServer(path string, logger slog.Logger) (*Server, error) {
// NewServer creates a new agent socket server.
func NewServer(logger slog.Logger, opts ...Option) (*Server, error) {
options := &options{}
for _, opt := range opts {
opt(options)
}
logger = logger.Named("agentsocket-server")
server := &Server{
logger: logger,
path: path,
path: options.path,
service: &DRPCAgentSocketService{
logger: logger,
unitManager: unit.NewManager(),
@@ -61,14 +65,6 @@ func NewServer(path string, logger slog.Logger) (*Server, error) {
},
})
if server.path == "" {
var err error
server.path, err = getDefaultSocketPath()
if err != nil {
return nil, xerrors.Errorf("get default socket path: %w", err)
}
}
listener, err := createSocket(server.path)
if err != nil {
return nil, xerrors.Errorf("create socket: %w", err)
@@ -91,6 +87,7 @@ func NewServer(path string, logger slog.Logger) (*Server, error) {
return server, nil
}
// Close stops the server and cleans up resources.
func (s *Server) Close() error {
s.mu.Lock()
@@ -134,52 +131,8 @@ func (s *Server) acceptConnections() {
return
}
for {
select {
case <-s.ctx.Done():
return
default:
}
conn, err := listener.Accept()
if err != nil {
s.logger.Warn(s.ctx, "error accepting connection", slog.Error(err))
continue
}
s.mu.Lock()
if s.listener == nil {
s.mu.Unlock()
_ = conn.Close()
return
}
s.wg.Add(1)
s.mu.Unlock()
go func() {
defer s.wg.Done()
s.handleConnection(conn)
}()
}
}
func (s *Server) handleConnection(conn net.Conn) {
defer conn.Close()
s.logger.Debug(s.ctx, "new connection accepted", slog.F("remote_addr", conn.RemoteAddr()))
config := yamux.DefaultConfig()
config.LogOutput = nil
config.Logger = slog.Stdlib(s.ctx, s.logger.Named("agentsocket-yamux"), slog.LevelInfo)
session, err := yamux.Server(conn, config)
if err != nil {
s.logger.Warn(s.ctx, "failed to create yamux session", slog.Error(err))
return
}
defer session.Close()
err = s.drpcServer.Serve(s.ctx, session)
if err != nil {
s.logger.Debug(s.ctx, "drpc server finished", slog.Error(err))
err := s.drpcServer.Serve(s.ctx, listener)
if err != nil {
s.logger.Warn(s.ctx, "error serving drpc server", slog.Error(err))
}
}
+90 -4
View File
@@ -1,14 +1,24 @@
package agentsocket_test
import (
"context"
"path/filepath"
"runtime"
"testing"
"github.com/google/uuid"
"github.com/spf13/afero"
"github.com/stretchr/testify/require"
"cdr.dev/slog"
"github.com/coder/coder/v2/agent"
"github.com/coder/coder/v2/agent/agentsocket"
"github.com/coder/coder/v2/agent/agenttest"
agentproto "github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/tailnet"
"github.com/coder/coder/v2/tailnet/tailnettest"
"github.com/coder/coder/v2/testutil"
)
func TestServer(t *testing.T) {
@@ -23,7 +33,7 @@ func TestServer(t *testing.T) {
socketPath := filepath.Join(t.TempDir(), "test.sock")
logger := slog.Make().Leveled(slog.LevelDebug)
server, err := agentsocket.NewServer(socketPath, logger)
server, err := agentsocket.NewServer(logger, agentsocket.WithPath(socketPath))
require.NoError(t, err)
require.NoError(t, server.Close())
})
@@ -33,10 +43,10 @@ func TestServer(t *testing.T) {
socketPath := filepath.Join(t.TempDir(), "test.sock")
logger := slog.Make().Leveled(slog.LevelDebug)
server1, err := agentsocket.NewServer(socketPath, logger)
server1, err := agentsocket.NewServer(logger, agentsocket.WithPath(socketPath))
require.NoError(t, err)
defer server1.Close()
_, err = agentsocket.NewServer(socketPath, logger)
_, err = agentsocket.NewServer(logger, agentsocket.WithPath(socketPath))
require.ErrorContains(t, err, "create socket")
})
@@ -45,8 +55,84 @@ func TestServer(t *testing.T) {
socketPath := filepath.Join(t.TempDir(), "test.sock")
logger := slog.Make().Leveled(slog.LevelDebug)
server, err := agentsocket.NewServer(socketPath, logger)
server, err := agentsocket.NewServer(logger, agentsocket.WithPath(socketPath))
require.NoError(t, err)
require.NoError(t, server.Close())
})
}
func TestServerWindowsNotSupported(t *testing.T) {
t.Parallel()
if runtime.GOOS != "windows" {
t.Skip("this test only runs on Windows")
}
t.Run("NewServer", func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(t.TempDir(), "test.sock")
logger := slog.Make().Leveled(slog.LevelDebug)
_, err := agentsocket.NewServer(logger, agentsocket.WithPath(socketPath))
require.ErrorContains(t, err, "agentsocket is not supported on Windows")
})
t.Run("NewClient", func(t *testing.T) {
t.Parallel()
_, err := agentsocket.NewClient(context.Background(), agentsocket.WithPath("test.sock"))
require.ErrorContains(t, err, "agentsocket is not supported on Windows")
})
}
func TestAgentInitializesOnWindowsWithoutSocketServer(t *testing.T) {
t.Parallel()
if runtime.GOOS != "windows" {
t.Skip("this test only runs on Windows")
}
ctx := testutil.Context(t, testutil.WaitShort)
logger := testutil.Logger(t).Named("agent")
derpMap, _ := tailnettest.RunDERPAndSTUN(t)
coordinator := tailnet.NewCoordinator(logger)
t.Cleanup(func() {
_ = coordinator.Close()
})
statsCh := make(chan *agentproto.Stats, 50)
agentID := uuid.New()
manifest := agentsdk.Manifest{
AgentID: agentID,
AgentName: "test-agent",
WorkspaceName: "test-workspace",
OwnerName: "test-user",
WorkspaceID: uuid.New(),
DERPMap: derpMap,
}
client := agenttest.NewClient(t, logger.Named("agenttest"), agentID, manifest, statsCh, coordinator)
t.Cleanup(client.Close)
options := agent.Options{
Client: client,
Filesystem: afero.NewMemMapFs(),
Logger: logger.Named("agent"),
ReconnectingPTYTimeout: testutil.WaitShort,
EnvironmentVariables: map[string]string{},
SocketPath: "",
}
agnt := agent.New(options)
t.Cleanup(func() {
_ = agnt.Close()
})
startup := testutil.TryReceive(ctx, t, client.GetStartup())
require.NotNil(t, startup, "agent should send startup message")
err := agnt.Close()
require.NoError(t, err, "agent should close cleanly")
}
+12 -2
View File
@@ -15,15 +15,18 @@ var _ proto.DRPCAgentSocketServer = (*DRPCAgentSocketService)(nil)
var ErrUnitManagerNotAvailable = xerrors.New("unit manager not available")
// DRPCAgentSocketService implements the DRPC agent socket service.
type DRPCAgentSocketService struct {
unitManager *unit.Manager
logger slog.Logger
}
// Ping responds to a ping request to check if the service is alive.
func (*DRPCAgentSocketService) Ping(_ context.Context, _ *proto.PingRequest) (*proto.PingResponse, error) {
return &proto.PingResponse{}, nil
}
// SyncStart starts a unit in the dependency graph.
func (s *DRPCAgentSocketService) SyncStart(_ context.Context, req *proto.SyncStartRequest) (*proto.SyncStartResponse, error) {
if s.unitManager == nil {
return nil, xerrors.Errorf("SyncStart: %w", ErrUnitManagerNotAvailable)
@@ -53,6 +56,7 @@ func (s *DRPCAgentSocketService) SyncStart(_ context.Context, req *proto.SyncSta
return &proto.SyncStartResponse{}, nil
}
// SyncWant declares a dependency between units.
func (s *DRPCAgentSocketService) SyncWant(_ context.Context, req *proto.SyncWantRequest) (*proto.SyncWantResponse, error) {
if s.unitManager == nil {
return nil, xerrors.Errorf("cannot add dependency: %w", ErrUnitManagerNotAvailable)
@@ -72,6 +76,7 @@ func (s *DRPCAgentSocketService) SyncWant(_ context.Context, req *proto.SyncWant
return &proto.SyncWantResponse{}, nil
}
// SyncComplete marks a unit as complete in the dependency graph.
func (s *DRPCAgentSocketService) SyncComplete(_ context.Context, req *proto.SyncCompleteRequest) (*proto.SyncCompleteResponse, error) {
if s.unitManager == nil {
return nil, xerrors.Errorf("cannot complete unit: %w", ErrUnitManagerNotAvailable)
@@ -86,6 +91,7 @@ func (s *DRPCAgentSocketService) SyncComplete(_ context.Context, req *proto.Sync
return &proto.SyncCompleteResponse{}, nil
}
// SyncReady checks whether a unit is ready to be started. That is, all dependencies are satisfied.
func (s *DRPCAgentSocketService) SyncReady(_ context.Context, req *proto.SyncReadyRequest) (*proto.SyncReadyResponse, error) {
if s.unitManager == nil {
return nil, xerrors.Errorf("cannot check readiness: %w", ErrUnitManagerNotAvailable)
@@ -102,6 +108,7 @@ func (s *DRPCAgentSocketService) SyncReady(_ context.Context, req *proto.SyncRea
}, nil
}
// SyncStatus gets the status of a unit and lists its dependencies.
func (s *DRPCAgentSocketService) SyncStatus(_ context.Context, req *proto.SyncStatusRequest) (*proto.SyncStatusResponse, error) {
if s.unitManager == nil {
return nil, xerrors.Errorf("cannot get status for unit %q: %w", req.Unit, ErrUnitManagerNotAvailable)
@@ -115,8 +122,11 @@ func (s *DRPCAgentSocketService) SyncStatus(_ context.Context, req *proto.SyncSt
}
dependencies, err := s.unitManager.GetAllDependencies(unitID)
if err != nil {
return nil, xerrors.Errorf("failed to get dependencies: %w", err)
switch {
case errors.Is(err, unit.ErrUnitNotFound):
dependencies = []unit.Dependency{}
case err != nil:
return nil, xerrors.Errorf("cannot get dependencies: %w", err)
}
var depInfos []*proto.DependencyInfo
+92 -173
View File
@@ -5,21 +5,18 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"net"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/hashicorp/yamux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"cdr.dev/slog"
"github.com/coder/coder/v2/agent/agentsocket"
"github.com/coder/coder/v2/agent/agentsocket/proto"
"github.com/coder/coder/v2/agent/unit"
"github.com/coder/coder/v2/codersdk/drpcsdk"
"github.com/coder/coder/v2/testutil"
)
// tempDirUnixSocket returns a temporary directory that can safely hold unix
@@ -47,23 +44,15 @@ func tempDirUnixSocket(t *testing.T) string {
}
// newSocketClient creates a DRPC client connected to the Unix socket at the given path.
func newSocketClient(t *testing.T, socketPath string) proto.DRPCAgentSocketClient {
func newSocketClient(ctx context.Context, t *testing.T, socketPath string) *agentsocket.Client {
t.Helper()
conn, err := net.Dial("unix", socketPath)
require.NoError(t, err)
config := yamux.DefaultConfig()
config.Logger = nil
session, err := yamux.Client(conn, config)
require.NoError(t, err)
client := proto.NewDRPCAgentSocketClient(drpcsdk.MultiplexedConn(session))
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(socketPath))
t.Cleanup(func() {
_ = session.Close()
_ = conn.Close()
_ = client.Close()
})
require.NoError(t, err)
return client
}
@@ -78,17 +67,17 @@ func TestDRPCAgentSocketService(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
_, err = client.Ping(context.Background(), &proto.PingRequest{})
err = client.Ping(ctx)
require.NoError(t, err)
})
@@ -98,147 +87,116 @@ func TestDRPCAgentSocketService(t *testing.T) {
t.Run("NewUnit", func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
_, err = client.SyncStart(context.Background(), &proto.SyncStartRequest{
Unit: "test-unit",
})
err = client.SyncStart(ctx, "test-unit")
require.NoError(t, err)
status, err := client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err := client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Equal(t, "started", status.Status)
require.Equal(t, unit.StatusStarted, status.Status)
})
t.Run("UnitAlreadyStarted", func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
// First Start
_, err = client.SyncStart(context.Background(), &proto.SyncStartRequest{
Unit: "test-unit",
})
err = client.SyncStart(ctx, "test-unit")
require.NoError(t, err)
status, err := client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err := client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Equal(t, "started", status.Status)
require.Equal(t, unit.StatusStarted, status.Status)
// Second Start
_, err = client.SyncStart(context.Background(), &proto.SyncStartRequest{
Unit: "test-unit",
})
err = client.SyncStart(ctx, "test-unit")
require.ErrorContains(t, err, unit.ErrSameStatusAlreadySet.Error())
status, err = client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err = client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Equal(t, "started", status.Status)
require.Equal(t, unit.StatusStarted, status.Status)
})
t.Run("UnitAlreadyCompleted", func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
// First start
_, err = client.SyncStart(context.Background(), &proto.SyncStartRequest{
Unit: "test-unit",
})
err = client.SyncStart(ctx, "test-unit")
require.NoError(t, err)
status, err := client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err := client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Equal(t, "started", status.Status)
require.Equal(t, unit.StatusStarted, status.Status)
// Complete the unit
_, err = client.SyncComplete(context.Background(), &proto.SyncCompleteRequest{
Unit: "test-unit",
})
err = client.SyncComplete(ctx, "test-unit")
require.NoError(t, err)
status, err = client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err = client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Equal(t, "completed", status.Status)
require.Equal(t, unit.StatusComplete, status.Status)
// Second start
_, err = client.SyncStart(context.Background(), &proto.SyncStartRequest{
Unit: "test-unit",
})
err = client.SyncStart(ctx, "test-unit")
require.NoError(t, err)
status, err = client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err = client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Equal(t, "started", status.Status)
require.Equal(t, unit.StatusStarted, status.Status)
})
t.Run("UnitNotReady", func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
_, err = client.SyncWant(context.Background(), &proto.SyncWantRequest{
Unit: "test-unit",
DependsOn: "dependency-unit",
})
err = client.SyncWant(ctx, "test-unit", "dependency-unit")
require.NoError(t, err)
_, err = client.SyncStart(context.Background(), &proto.SyncStartRequest{
Unit: "test-unit",
})
err = client.SyncStart(ctx, "test-unit")
require.ErrorContains(t, err, "unit not ready")
status, err := client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err := client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Equal(t, string(unit.StatusPending), status.Status)
require.Equal(t, unit.StatusPending, status.Status)
require.False(t, status.IsReady)
})
})
@@ -250,107 +208,86 @@ func TestDRPCAgentSocketService(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
// If dependency units are not registered, they are registered automatically
_, err = client.SyncWant(context.Background(), &proto.SyncWantRequest{
Unit: "test-unit",
DependsOn: "dependency-unit",
})
err = client.SyncWant(ctx, "test-unit", "dependency-unit")
require.NoError(t, err)
status, err := client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err := client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Len(t, status.Dependencies, 1)
require.Equal(t, "dependency-unit", status.Dependencies[0].DependsOn)
require.Equal(t, "completed", status.Dependencies[0].RequiredStatus)
require.Equal(t, unit.ID("dependency-unit"), status.Dependencies[0].DependsOn)
require.Equal(t, unit.StatusComplete, status.Dependencies[0].RequiredStatus)
})
t.Run("DependencyAlreadyRegistered", func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
// Start the dependency unit
_, err = client.SyncStart(context.Background(), &proto.SyncStartRequest{
Unit: "dependency-unit",
})
err = client.SyncStart(ctx, "dependency-unit")
require.NoError(t, err)
status, err := client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "dependency-unit",
})
status, err := client.SyncStatus(ctx, "dependency-unit")
require.NoError(t, err)
require.Equal(t, "started", status.Status)
require.Equal(t, unit.StatusStarted, status.Status)
// Add the dependency after the dependency unit has already started
_, err = client.SyncWant(context.Background(), &proto.SyncWantRequest{
Unit: "test-unit",
DependsOn: "dependency-unit",
})
err = client.SyncWant(ctx, "test-unit", "dependency-unit")
// Dependencies can be added even if the dependency unit has already started
require.NoError(t, err)
// The dependency is now reflected in the test unit's status
status, err = client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err = client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Equal(t, "dependency-unit", status.Dependencies[0].DependsOn)
require.Equal(t, "completed", status.Dependencies[0].RequiredStatus)
require.Equal(t, unit.ID("dependency-unit"), status.Dependencies[0].DependsOn)
require.Equal(t, unit.StatusComplete, status.Dependencies[0].RequiredStatus)
})
t.Run("DependencyAddedAfterDependentStarted", func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
// Start the dependent unit
_, err = client.SyncStart(context.Background(), &proto.SyncStartRequest{
Unit: "test-unit",
})
err = client.SyncStart(ctx, "test-unit")
require.NoError(t, err)
status, err := client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err := client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Equal(t, "started", status.Status)
require.Equal(t, unit.StatusStarted, status.Status)
// Add the dependency after the dependency unit has already started
_, err = client.SyncWant(context.Background(), &proto.SyncWantRequest{
Unit: "test-unit",
DependsOn: "dependency-unit",
})
err = client.SyncWant(ctx, "test-unit", "dependency-unit")
// Dependencies can be added even if the dependent unit has already started.
// The dependency applies the next time a unit is started. The current status is not updated.
@@ -359,12 +296,10 @@ func TestDRPCAgentSocketService(t *testing.T) {
require.NoError(t, err)
// The dependency is now reflected in the test unit's status
status, err = client.SyncStatus(context.Background(), &proto.SyncStatusRequest{
Unit: "test-unit",
})
status, err = client.SyncStatus(ctx, "test-unit")
require.NoError(t, err)
require.Equal(t, "dependency-unit", status.Dependencies[0].DependsOn)
require.Equal(t, "completed", status.Dependencies[0].RequiredStatus)
require.Equal(t, unit.ID("dependency-unit"), status.Dependencies[0].DependsOn)
require.Equal(t, unit.StatusComplete, status.Dependencies[0].RequiredStatus)
})
})
@@ -375,96 +310,80 @@ func TestDRPCAgentSocketService(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
response, err := client.SyncReady(context.Background(), &proto.SyncReadyRequest{
Unit: "unregistered-unit",
})
ready, err := client.SyncReady(ctx, "unregistered-unit")
require.NoError(t, err)
require.False(t, response.Ready)
require.True(t, ready)
})
t.Run("UnitNotReady", func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
// Register a unit with an unsatisfied dependency
_, err = client.SyncWant(context.Background(), &proto.SyncWantRequest{
Unit: "test-unit",
DependsOn: "dependency-unit",
})
err = client.SyncWant(ctx, "test-unit", "dependency-unit")
require.NoError(t, err)
// Check readiness - should be false because dependency is not satisfied
response, err := client.SyncReady(context.Background(), &proto.SyncReadyRequest{
Unit: "test-unit",
})
ready, err := client.SyncReady(ctx, "test-unit")
require.NoError(t, err)
require.False(t, response.Ready)
require.False(t, ready)
})
t.Run("UnitReady", func(t *testing.T) {
t.Parallel()
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
ctx := testutil.Context(t, testutil.WaitShort)
server, err := agentsocket.NewServer(
socketPath,
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err)
defer server.Close()
client := newSocketClient(t, socketPath)
client := newSocketClient(ctx, t, socketPath)
// Register a unit with no dependencies - should be ready immediately
_, err = client.SyncStart(context.Background(), &proto.SyncStartRequest{
Unit: "test-unit",
})
err = client.SyncStart(ctx, "test-unit")
require.NoError(t, err)
// Check readiness - should be true
_, err = client.SyncReady(context.Background(), &proto.SyncReadyRequest{
Unit: "test-unit",
})
ready, err := client.SyncReady(ctx, "test-unit")
require.NoError(t, err)
require.True(t, ready)
// Also test a unit with satisfied dependencies
_, err = client.SyncWant(context.Background(), &proto.SyncWantRequest{
Unit: "dependent-unit",
DependsOn: "test-unit",
})
err = client.SyncWant(ctx, "dependent-unit", "test-unit")
require.NoError(t, err)
// Complete the dependency
_, err = client.SyncComplete(context.Background(), &proto.SyncCompleteRequest{
Unit: "test-unit",
})
err = client.SyncComplete(ctx, "test-unit")
require.NoError(t, err)
// Now dependent-unit should be ready
_, err = client.SyncReady(context.Background(), &proto.SyncReadyRequest{
Unit: "dependent-unit",
})
ready, err = client.SyncReady(ctx, "dependent-unit")
require.NoError(t, err)
require.True(t, ready)
})
})
}
+17 -27
View File
@@ -3,8 +3,7 @@
package agentsocket
import (
"crypto/rand"
"encoding/hex"
"context"
"net"
"os"
"path/filepath"
@@ -13,8 +12,13 @@ import (
"golang.org/x/xerrors"
)
// createSocket creates a Unix domain socket listener
const defaultSocketPath = "/tmp/coder-agent.sock"
func createSocket(path string) (net.Listener, error) {
if path == "" {
path = defaultSocketPath
}
if !isSocketAvailable(path) {
return nil, xerrors.Errorf("socket path %s is not available", path)
}
@@ -23,7 +27,6 @@ func createSocket(path string) (net.Listener, error) {
return nil, xerrors.Errorf("remove existing socket: %w", err)
}
// Create parent directory if it doesn't exist
parentDir := filepath.Dir(path)
if err := os.MkdirAll(parentDir, 0o700); err != nil {
return nil, xerrors.Errorf("create socket directory: %w", err)
@@ -41,43 +44,30 @@ func createSocket(path string) (net.Listener, error) {
return listener, nil
}
// getDefaultSocketPath returns the default socket path for Unix-like systems
func getDefaultSocketPath() (string, error) {
randomBytes := make([]byte, 4)
if _, err := rand.Read(randomBytes); err != nil {
return "", xerrors.Errorf("generate random socket name: %w", err)
}
randomSuffix := hex.EncodeToString(randomBytes)
// Try XDG_RUNTIME_DIR first
if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
return filepath.Join(runtimeDir, "coder-agent-"+randomSuffix+".sock"), nil
}
return filepath.Join("/tmp", "coder-agent-"+randomSuffix+".sock"), nil
}
// CleanupSocket removes the socket file
func cleanupSocket(path string) error {
return os.Remove(path)
}
// isSocketAvailable checks if a socket path is available for use
func isSocketAvailable(path string) bool {
// Check if file exists
if _, err := os.Stat(path); os.IsNotExist(err) {
return true
}
// Try to connect to see if it's actually listening
// Try to connect to see if it's actually listening.
dialer := net.Dialer{Timeout: 10 * time.Second}
conn, err := dialer.Dial("unix", path)
if err != nil {
// If we can't connect, the socket is not in use
// Socket is available for use
return true
}
_ = conn.Close()
// Socket is in use
return false
}
func dialSocket(ctx context.Context, path string) (net.Conn, error) {
if path == "" {
path = defaultSocketPath
}
dialer := net.Dialer{}
return dialer.DialContext(ctx, "unix", path)
}
+5 -10
View File
@@ -3,25 +3,20 @@
package agentsocket
import (
"context"
"net"
"golang.org/x/xerrors"
)
// createSocket returns an error indicating that agentsocket is not supported on Windows.
// This feature is unix-only in its current experimental state.
func createSocket(_ string) (net.Listener, error) {
return nil, xerrors.New("agentsocket is not supported on Windows")
}
// getDefaultSocketPath returns an error indicating that agentsocket is not supported on Windows.
// This feature is unix-only in its current experimental state.
func getDefaultSocketPath() (string, error) {
return "", xerrors.New("agentsocket is not supported on Windows")
}
// cleanupSocket is a no-op on Windows since agentsocket is not supported.
func cleanupSocket(_ string) error {
// No-op since agentsocket is not supported on Windows
return nil
}
func dialSocket(_ context.Context, _ string) (net.Conn, error) {
return nil, xerrors.New("agentsocket is not supported on Windows")
}
+33 -31
View File
@@ -2,41 +2,31 @@ package agent
import (
"net/http"
"sync"
"time"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw/loggermw"
"github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/coder/v2/httpmw"
)
func (a *agent) apiHandler() http.Handler {
r := chi.NewRouter()
r.Use(
httpmw.Recover(a.logger),
tracing.StatusWriterMiddleware,
loggermw.Logger(a.logger),
)
r.Get("/", func(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.Response{
Message: "Hello from the agent!",
})
})
// Make a copy to ensure the map is not modified after the handler is
// created.
cpy := make(map[int]string)
for k, b := range a.ignorePorts {
cpy[k] = b
}
cacheDuration := 1 * time.Second
if a.portCacheDuration > 0 {
cacheDuration = a.portCacheDuration
}
lp := &listeningPortsHandler{
ignorePorts: cpy,
cacheDuration: cacheDuration,
}
if a.devcontainers {
r.Mount("/api/v0/containers", a.containerAPI.Routes())
} else if manifest := a.manifest.Load(); manifest != nil && manifest.ParentID != uuid.Nil {
@@ -57,7 +47,7 @@ func (a *agent) apiHandler() http.Handler {
promHandler := PrometheusMetricsHandler(a.prometheusRegistry, a.logger)
r.Get("/api/v0/listening-ports", lp.handler)
r.Get("/api/v0/listening-ports", a.listeningPortsHandler.handler)
r.Get("/api/v0/netcheck", a.HandleNetcheck)
r.Post("/api/v0/list-directory", a.HandleLS)
r.Get("/api/v0/read-file", a.HandleReadFile)
@@ -72,22 +62,21 @@ func (a *agent) apiHandler() http.Handler {
return r
}
type listeningPortsHandler struct {
ignorePorts map[int]string
cacheDuration time.Duration
type ListeningPortsGetter interface {
GetListeningPorts() ([]codersdk.WorkspaceAgentListeningPort, error)
}
//nolint: unused // used on some but not all platforms
mut sync.Mutex
//nolint: unused // used on some but not all platforms
ports []codersdk.WorkspaceAgentListeningPort
//nolint: unused // used on some but not all platforms
mtime time.Time
type listeningPortsHandler struct {
// In production code, this is set to an osListeningPortsGetter, but it can be overridden for
// testing.
getter ListeningPortsGetter
ignorePorts map[int]string
}
// handler returns a list of listening ports. This is tested by coderd's
// TestWorkspaceAgentListeningPorts test.
func (lp *listeningPortsHandler) handler(rw http.ResponseWriter, r *http.Request) {
ports, err := lp.getListeningPorts()
ports, err := lp.getter.GetListeningPorts()
if err != nil {
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
Message: "Could not scan for listening ports.",
@@ -96,7 +85,20 @@ func (lp *listeningPortsHandler) handler(rw http.ResponseWriter, r *http.Request
return
}
filteredPorts := make([]codersdk.WorkspaceAgentListeningPort, 0, len(ports))
for _, port := range ports {
if port.Port < workspacesdk.AgentMinimumListeningPort {
continue
}
// Ignore ports that we've been told to ignore.
if _, ok := lp.ignorePorts[int(port.Port)]; ok {
continue
}
filteredPorts = append(filteredPorts, port)
}
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.WorkspaceAgentListeningPortsResponse{
Ports: ports,
Ports: filteredPorts,
})
}
+10 -8
View File
@@ -3,16 +3,23 @@
package agent
import (
"sync"
"time"
"github.com/cakturk/go-netstat/netstat"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/workspacesdk"
)
func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentListeningPort, error) {
type osListeningPortsGetter struct {
cacheDuration time.Duration
mut sync.Mutex
ports []codersdk.WorkspaceAgentListeningPort
mtime time.Time
}
func (lp *osListeningPortsGetter) GetListeningPorts() ([]codersdk.WorkspaceAgentListeningPort, error) {
lp.mut.Lock()
defer lp.mut.Unlock()
@@ -33,12 +40,7 @@ func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentL
seen := make(map[uint16]struct{}, len(tabs))
ports := []codersdk.WorkspaceAgentListeningPort{}
for _, tab := range tabs {
if tab.LocalAddr == nil || tab.LocalAddr.Port < workspacesdk.AgentMinimumListeningPort {
continue
}
// Ignore ports that we've been told to ignore.
if _, ok := lp.ignorePorts[int(tab.LocalAddr.Port)]; ok {
if tab.LocalAddr == nil {
continue
}
+45
View File
@@ -0,0 +1,45 @@
//go:build linux || (windows && amd64)
package agent
import (
"net"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestOSListeningPortsGetter(t *testing.T) {
t.Parallel()
uut := &osListeningPortsGetter{
cacheDuration: 1 * time.Hour,
}
l, err := net.Listen("tcp", "localhost:0")
require.NoError(t, err)
defer l.Close()
ports, err := uut.GetListeningPorts()
require.NoError(t, err)
found := false
for _, port := range ports {
// #nosec G115 - Safe conversion as TCP port numbers are within uint16 range (0-65535)
if port.Port == uint16(l.Addr().(*net.TCPAddr).Port) {
found = true
break
}
}
require.True(t, found)
// check that we cache the ports
err = l.Close()
require.NoError(t, err)
portsNew, err := uut.GetListeningPorts()
require.NoError(t, err)
require.Equal(t, ports, portsNew)
// note that it's unsafe to try to assert that a port does not exist in the response
// because the OS may reallocate the port very quickly.
}
+10 -2
View File
@@ -2,9 +2,17 @@
package agent
import "github.com/coder/coder/v2/codersdk"
import (
"time"
func (*listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentListeningPort, error) {
"github.com/coder/coder/v2/codersdk"
)
type osListeningPortsGetter struct {
cacheDuration time.Duration
}
func (*osListeningPortsGetter) GetListeningPorts() ([]codersdk.WorkspaceAgentListeningPort, error) {
// Can't scan for ports on non-linux or non-windows_amd64 systems at the
// moment. The UI will not show any "no ports found" message to the user, so
// the user won't suspect a thing.
+11 -1
View File
@@ -2,6 +2,7 @@ package unit
import (
"errors"
"fmt"
"sync"
"golang.org/x/xerrors"
@@ -23,6 +24,15 @@ var (
// Status represents the status of a unit.
type Status string
var _ fmt.Stringer = Status("")
func (s Status) String() string {
if s == StatusNotRegistered {
return "not registered"
}
return string(s)
}
// Status constants for dependency tracking.
const (
StatusNotRegistered Status = ""
@@ -137,7 +147,7 @@ func (m *Manager) IsReady(id ID) (bool, error) {
defer m.mu.RUnlock()
if !m.registered(id) {
return false, nil
return true, nil
}
return m.units[id].ready, nil
+1 -1
View File
@@ -684,7 +684,7 @@ func TestManager_IsReady(t *testing.T) {
// Then: the unit is not ready
isReady, err := manager.IsReady(unitA)
require.NoError(t, err)
assert.False(t, isReady)
assert.True(t, isReady)
})
}
+17
View File
@@ -57,6 +57,8 @@ func workspaceAgent() *serpent.Command {
devcontainers bool
devcontainerProjectDiscovery bool
devcontainerDiscoveryAutostart bool
socketServerEnabled bool
socketPath string
)
agentAuth := &AgentAuth{}
cmd := &serpent.Command{
@@ -317,6 +319,8 @@ func workspaceAgent() *serpent.Command {
agentcontainers.WithProjectDiscovery(devcontainerProjectDiscovery),
agentcontainers.WithDiscoveryAutostart(devcontainerDiscoveryAutostart),
},
SocketPath: socketPath,
SocketServerEnabled: socketServerEnabled,
})
if debugAddress != "" {
@@ -477,6 +481,19 @@ func workspaceAgent() *serpent.Command {
Description: "Allow the agent to autostart devcontainer projects it discovers based on their configuration.",
Value: serpent.BoolOf(&devcontainerDiscoveryAutostart),
},
{
Flag: "socket-server-enabled",
Default: "false",
Env: "CODER_AGENT_SOCKET_SERVER_ENABLED",
Description: "Enable the agent socket server.",
Value: serpent.BoolOf(&socketServerEnabled),
},
{
Flag: "socket-path",
Env: "CODER_AGENT_SOCKET_PATH",
Description: "Specify the path for the agent socket.",
Value: serpent.StringOf(&socketPath),
},
}
agentAuth.AttachOptions(cmd, false)
return cmd
+20 -4
View File
@@ -28,7 +28,9 @@ import (
)
// New creates a CLI instance with a configuration pointed to a
// temporary testing directory.
// temporary testing directory. The invocation is set up to use a
// global config directory for the given testing.TB, and keyring
// usage disabled.
func New(t testing.TB, args ...string) (*serpent.Invocation, config.Root) {
var root cli.RootCmd
@@ -59,6 +61,15 @@ func NewWithCommand(
t testing.TB, cmd *serpent.Command, args ...string,
) (*serpent.Invocation, config.Root) {
configDir := config.Root(t.TempDir())
// Keyring usage is disabled here when --global-config is set because many existing
// tests expect the session token to be stored on disk and is not properly instrumented
// for parallel testing against the actual operating system keyring.
invArgs := append([]string{"--global-config", string(configDir)}, args...)
return setupInvocation(t, cmd, invArgs...), configDir
}
func setupInvocation(t testing.TB, cmd *serpent.Command, args ...string,
) *serpent.Invocation {
// I really would like to fail test on error logs, but realistically, turning on by default
// in all our CLI tests is going to create a lot of flaky noise.
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).
@@ -66,16 +77,21 @@ func NewWithCommand(
Named("cli")
i := &serpent.Invocation{
Command: cmd,
Args: append([]string{"--global-config", string(configDir)}, args...),
Args: args,
Stdin: io.LimitReader(nil, 0),
Stdout: (&logWriter{prefix: "stdout", log: logger}),
Stderr: (&logWriter{prefix: "stderr", log: logger}),
Logger: logger,
}
t.Logf("invoking command: %s %s", cmd.Name(), strings.Join(i.Args, " "))
return i
}
// These can be overridden by the test.
return i, configDir
func NewWithDefaultKeyringCommand(t testing.TB, cmd *serpent.Command, args ...string,
) (*serpent.Invocation, config.Root) {
configDir := config.Root(t.TempDir())
invArgs := append([]string{"--global-config", string(configDir)}, args...)
return setupInvocation(t, cmd, invArgs...), configDir
}
// SetupConfig applies the URL and SessionToken of the client to the config.
+204 -133
View File
@@ -2,62 +2,85 @@ package cli_test
import (
"bytes"
"crypto/rand"
"encoding/binary"
"fmt"
"net/url"
"os"
"path"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/cli/config"
"github.com/coder/coder/v2/cli/sessionstore"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/pty/ptytest"
"github.com/coder/serpent"
)
// mockKeyring is a mock sessionstore.Backend implementation.
type mockKeyring struct {
credentials map[string]string // service name -> credential
}
const mockServiceName = "mock-service-name"
func newMockKeyring() *mockKeyring {
return &mockKeyring{credentials: make(map[string]string)}
}
func (m *mockKeyring) Read(_ *url.URL) (string, error) {
cred, ok := m.credentials[mockServiceName]
if !ok {
return "", os.ErrNotExist
// keyringTestServiceName generates a unique service name for keyring tests
// using the test name and a nanosecond timestamp to prevent collisions.
func keyringTestServiceName(t *testing.T) string {
t.Helper()
var n uint32
err := binary.Read(rand.Reader, binary.BigEndian, &n)
if err != nil {
t.Fatal(err)
}
return cred, nil
return fmt.Sprintf("%s_%v_%d", t.Name(), time.Now().UnixNano(), n)
}
func (m *mockKeyring) Write(_ *url.URL, token string) error {
m.credentials[mockServiceName] = token
return nil
type keyringTestEnv struct {
serviceName string
keyring sessionstore.Keyring
inv *serpent.Invocation
cfg config.Root
clientURL *url.URL
}
func (m *mockKeyring) Delete(_ *url.URL) error {
_, ok := m.credentials[mockServiceName]
if !ok {
return os.ErrNotExist
}
delete(m.credentials, mockServiceName)
return nil
func setupKeyringTestEnv(t *testing.T, clientURL string, args ...string) keyringTestEnv {
t.Helper()
var root cli.RootCmd
cmd, err := root.Command(root.AGPL())
require.NoError(t, err)
serviceName := keyringTestServiceName(t)
root.WithKeyringServiceName(serviceName)
root.UseKeyringWithGlobalConfig()
inv, cfg := clitest.NewWithDefaultKeyringCommand(t, cmd, args...)
parsedURL, err := url.Parse(clientURL)
require.NoError(t, err)
backend := sessionstore.NewKeyringWithService(serviceName)
t.Cleanup(func() {
_ = backend.Delete(parsedURL)
})
return keyringTestEnv{serviceName, backend, inv, cfg, parsedURL}
}
func TestUseKeyring(t *testing.T) {
// Verify that the --use-keyring flag opts into using a keyring backend for
// storing session tokens instead of plain text files.
// Verify that the --use-keyring flag default opts into using a keyring backend
// for storing session tokens instead of plain text files.
t.Parallel()
t.Run("Login", func(t *testing.T) {
t.Parallel()
if runtime.GOOS != "windows" && runtime.GOOS != "darwin" {
t.Skip("keyring is not supported on this OS")
}
// Create a test server
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
@@ -65,25 +88,16 @@ func TestUseKeyring(t *testing.T) {
// Create a pty for interactive prompts
pty := ptytest.New(t)
// Create CLI invocation with --use-keyring flag
inv, cfg := clitest.New(t,
// Create CLI invocation which defaults to using the keyring
env := setupKeyringTestEnv(t, client.URL.String(),
"login",
"--force-tty",
"--use-keyring",
"--no-open",
client.URL.String(),
)
client.URL.String())
inv := env.inv
inv.Stdin = pty.Input()
inv.Stdout = pty.Output()
// Inject the mock backend before running the command
var root cli.RootCmd
cmd, err := root.Command(root.AGPL())
require.NoError(t, err)
mockBackend := newMockKeyring()
root.WithSessionStorageBackend(mockBackend)
inv.Command = cmd
// Run login in background
doneChan := make(chan struct{})
go func() {
@@ -99,19 +113,23 @@ func TestUseKeyring(t *testing.T) {
<-doneChan
// Verify that session file was NOT created (using keyring instead)
sessionFile := path.Join(string(cfg), "session")
_, err = os.Stat(sessionFile)
sessionFile := path.Join(string(env.cfg), "session")
_, err := os.Stat(sessionFile)
require.True(t, os.IsNotExist(err), "session file should not exist when using keyring")
// Verify that the credential IS stored in mock keyring
cred, err := mockBackend.Read(nil)
require.NoError(t, err, "credential should be stored in mock keyring")
// Verify that the credential IS stored in OS keyring
cred, err := env.keyring.Read(env.clientURL)
require.NoError(t, err, "credential should be stored in OS keyring")
require.Equal(t, client.SessionToken(), cred, "stored token should match login token")
})
t.Run("Logout", func(t *testing.T) {
t.Parallel()
if runtime.GOOS != "windows" && runtime.GOOS != "darwin" {
t.Skip("keyring is not supported on this OS")
}
// Create a test server
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
@@ -119,25 +137,17 @@ func TestUseKeyring(t *testing.T) {
// Create a pty for interactive prompts
pty := ptytest.New(t)
// First, login with --use-keyring
loginInv, cfg := clitest.New(t,
// First, login with the keyring (default)
env := setupKeyringTestEnv(t, client.URL.String(),
"login",
"--force-tty",
"--use-keyring",
"--no-open",
client.URL.String(),
)
loginInv := env.inv
loginInv.Stdin = pty.Input()
loginInv.Stdout = pty.Output()
// Inject the mock backend
var loginRoot cli.RootCmd
loginCmd, err := loginRoot.Command(loginRoot.AGPL())
require.NoError(t, err)
mockBackend := newMockKeyring()
loginRoot.WithSessionStorageBackend(mockBackend)
loginInv.Command = loginCmd
doneChan := make(chan struct{})
go func() {
defer close(doneChan)
@@ -150,25 +160,23 @@ func TestUseKeyring(t *testing.T) {
pty.ExpectMatch("Welcome to Coder")
<-doneChan
// Verify credential exists in mock keyring
cred, err := mockBackend.Read(nil)
// Verify credential exists in OS keyring
cred, err := env.keyring.Read(env.clientURL)
require.NoError(t, err, "read credential should succeed before logout")
require.NotEmpty(t, cred, "credential should exist after logout")
require.NotEmpty(t, cred, "credential should exist before logout")
// Now run logout with --use-keyring
logoutInv, _ := clitest.New(t,
"logout",
"--use-keyring",
"--yes",
"--global-config", string(cfg),
)
// Inject the same mock backend
// Now logout using the same keyring service name
var logoutRoot cli.RootCmd
logoutCmd, err := logoutRoot.Command(logoutRoot.AGPL())
require.NoError(t, err)
logoutRoot.WithSessionStorageBackend(mockBackend)
logoutInv.Command = logoutCmd
logoutRoot.WithKeyringServiceName(env.serviceName)
logoutRoot.UseKeyringWithGlobalConfig()
logoutInv, _ := clitest.NewWithDefaultKeyringCommand(t, logoutCmd,
"logout",
"--yes",
"--global-config", string(env.cfg),
)
var logoutOut bytes.Buffer
logoutInv.Stdout = &logoutOut
@@ -176,14 +184,18 @@ func TestUseKeyring(t *testing.T) {
err = logoutInv.Run()
require.NoError(t, err, "logout should succeed")
// Verify the credential was deleted from mock keyring
_, err = mockBackend.Read(nil)
// Verify the credential was deleted from OS keyring
_, err = env.keyring.Read(env.clientURL)
require.ErrorIs(t, err, os.ErrNotExist, "credential should be deleted from keyring after logout")
})
t.Run("OmitFlag", func(t *testing.T) {
t.Run("DefaultFileStorage", func(t *testing.T) {
t.Parallel()
if runtime.GOOS != "linux" {
t.Skip("file storage is the default for Linux")
}
// Create a test server
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
@@ -191,13 +203,13 @@ func TestUseKeyring(t *testing.T) {
// Create a pty for interactive prompts
pty := ptytest.New(t)
// --use-keyring flag omitted (should use file-based storage)
inv, cfg := clitest.New(t,
env := setupKeyringTestEnv(t, client.URL.String(),
"login",
"--force-tty",
"--no-open",
client.URL.String(),
)
inv := env.inv
inv.Stdin = pty.Input()
inv.Stdout = pty.Output()
@@ -214,9 +226,9 @@ func TestUseKeyring(t *testing.T) {
<-doneChan
// Verify that session file WAS created (not using keyring)
sessionFile := path.Join(string(cfg), "session")
sessionFile := path.Join(string(env.cfg), "session")
_, err := os.Stat(sessionFile)
require.NoError(t, err, "session file should exist when NOT using --use-keyring")
require.NoError(t, err, "session file should exist when NOT using --use-keyring on Linux")
// Read and verify the token from file
content, err := os.ReadFile(sessionFile)
@@ -234,24 +246,18 @@ func TestUseKeyring(t *testing.T) {
// Create a pty for interactive prompts
pty := ptytest.New(t)
// Login using CODER_USE_KEYRING environment variable instead of flag
inv, cfg := clitest.New(t,
// Login using CODER_USE_KEYRING environment variable set to disable keyring usage,
// which should have the same behavior on all platforms.
env := setupKeyringTestEnv(t, client.URL.String(),
"login",
"--force-tty",
"--no-open",
client.URL.String(),
)
inv := env.inv
inv.Stdin = pty.Input()
inv.Stdout = pty.Output()
inv.Environ.Set("CODER_USE_KEYRING", "true")
// Inject the mock backend
var root cli.RootCmd
cmd, err := root.Command(root.AGPL())
require.NoError(t, err)
mockBackend := newMockKeyring()
root.WithSessionStorageBackend(mockBackend)
inv.Command = cmd
inv.Environ.Set("CODER_USE_KEYRING", "false")
doneChan := make(chan struct{})
go func() {
@@ -265,21 +271,64 @@ func TestUseKeyring(t *testing.T) {
pty.ExpectMatch("Welcome to Coder")
<-doneChan
// Verify that session file was NOT created (using keyring via env var)
sessionFile := path.Join(string(cfg), "session")
_, err = os.Stat(sessionFile)
require.True(t, os.IsNotExist(err), "session file should not exist when using keyring via env var")
// Verify that session file WAS created (not using keyring)
sessionFile := path.Join(string(env.cfg), "session")
_, err := os.Stat(sessionFile)
require.NoError(t, err, "session file should exist when CODER_USE_KEYRING set to false")
// Verify credential is in mock keyring
cred, err := mockBackend.Read(nil)
require.NoError(t, err, "credential should be stored in keyring when CODER_USE_KEYRING=true")
require.NotEmpty(t, cred)
// Read and verify the token from file
content, err := os.ReadFile(sessionFile)
require.NoError(t, err, "should be able to read session file")
require.Equal(t, client.SessionToken(), string(content), "file should contain the session token")
})
t.Run("DisableKeyringWithFlag", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
pty := ptytest.New(t)
// Login with --use-keyring=false to explicitly disable keyring usage, which
// should have the same behavior on all platforms.
env := setupKeyringTestEnv(t, client.URL.String(),
"login",
"--use-keyring=false",
"--force-tty",
"--no-open",
client.URL.String(),
)
inv := env.inv
inv.Stdin = pty.Input()
inv.Stdout = pty.Output()
doneChan := make(chan struct{})
go func() {
defer close(doneChan)
err := inv.Run()
assert.NoError(t, err)
}()
pty.ExpectMatch("Paste your token here:")
pty.WriteLine(client.SessionToken())
pty.ExpectMatch("Welcome to Coder")
<-doneChan
// Verify that session file WAS created (not using keyring)
sessionFile := path.Join(string(env.cfg), "session")
_, err := os.Stat(sessionFile)
require.NoError(t, err, "session file should exist when --use-keyring=false is specified")
// Read and verify the token from file
content, err := os.ReadFile(sessionFile)
require.NoError(t, err, "should be able to read session file")
require.Equal(t, client.SessionToken(), string(content), "file should contain the session token")
})
}
func TestUseKeyringUnsupportedOS(t *testing.T) {
// Verify that trying to use --use-keyring on an unsupported operating system produces
// a helpful error message.
// Verify that on unsupported operating systems, file-based storage is used
// automatically even when --use-keyring is set to true (the default).
t.Parallel()
// Only run this on an unsupported OS.
@@ -287,43 +336,60 @@ func TestUseKeyringUnsupportedOS(t *testing.T) {
t.Skipf("Skipping unsupported OS test on %s where keyring is supported", runtime.GOOS)
}
const expMessage = "keyring storage is not supported on this operating system; remove the --use-keyring flag"
t.Run("LoginWithUnsupportedKeyring", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
// Try to login with --use-keyring on an unsupported OS
inv, _ := clitest.New(t,
"login",
"--use-keyring",
client.URL.String(),
)
// The error should occur immediately, before any prompts
loginErr := inv.Run()
// Verify we got an error about unsupported OS
require.Error(t, loginErr)
require.Contains(t, loginErr.Error(), expMessage)
})
t.Run("LogoutWithUnsupportedKeyring", func(t *testing.T) {
t.Run("LoginWithDefaultKeyring", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
pty := ptytest.New(t)
// First login without keyring to create a session
loginInv, cfg := clitest.New(t,
env := setupKeyringTestEnv(t, client.URL.String(),
"login",
"--force-tty",
"--no-open",
client.URL.String(),
)
inv := env.inv
inv.Stdin = pty.Input()
inv.Stdout = pty.Output()
doneChan := make(chan struct{})
go func() {
defer close(doneChan)
err := inv.Run()
assert.NoError(t, err)
}()
pty.ExpectMatch("Paste your token here:")
pty.WriteLine(client.SessionToken())
pty.ExpectMatch("Welcome to Coder")
<-doneChan
// Verify that session file WAS created (automatic fallback to file storage)
sessionFile := path.Join(string(env.cfg), "session")
_, err := os.Stat(sessionFile)
require.NoError(t, err, "session file should exist due to automatic fallback to file storage")
content, err := os.ReadFile(sessionFile)
require.NoError(t, err, "should be able to read session file")
require.Equal(t, client.SessionToken(), string(content), "file should contain the session token")
})
t.Run("LogoutWithDefaultKeyring", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)
pty := ptytest.New(t)
// First login to create a session (will use file storage due to automatic fallback)
env := setupKeyringTestEnv(t, client.URL.String(),
"login",
"--force-tty",
"--no-open",
client.URL.String(),
)
loginInv := env.inv
loginInv.Stdin = pty.Input()
loginInv.Stdout = pty.Output()
@@ -339,17 +405,22 @@ func TestUseKeyringUnsupportedOS(t *testing.T) {
pty.ExpectMatch("Welcome to Coder")
<-doneChan
// Now try to logout with --use-keyring on an unsupported OS
logoutInv, _ := clitest.New(t,
// Verify session file exists
sessionFile := path.Join(string(env.cfg), "session")
_, err := os.Stat(sessionFile)
require.NoError(t, err, "session file should exist before logout")
// Now logout - should succeed and delete the file
logoutEnv := setupKeyringTestEnv(t, client.URL.String(),
"logout",
"--use-keyring",
"--yes",
"--global-config", string(cfg),
"--global-config", string(env.cfg),
)
err := logoutInv.Run()
// Verify we got an error about unsupported OS
require.Error(t, err)
require.Contains(t, err.Error(), expMessage)
err = logoutEnv.inv.Run()
require.NoError(t, err, "logout should succeed with automatic file storage fallback")
_, err = os.Stat(sessionFile)
require.True(t, os.IsNotExist(err), "session file should be deleted after logout")
})
}
+3 -3
View File
@@ -154,9 +154,9 @@ func (r *RootCmd) login() *serpent.Command {
cmd := &serpent.Command{
Use: "login [<url>]",
Short: "Authenticate with Coder deployment",
Long: "By default, the session token is stored in a plain text file. Use the " +
"--use-keyring flag or set CODER_USE_KEYRING=true to store the token in " +
"the operating system keyring instead.",
Long: "By default, the session token is stored in the operating system keyring on " +
"macOS and Windows and a plain text file on Linux. Use the --use-keyring flag " +
"or CODER_USE_KEYRING environment variable to change the storage mechanism.",
Middleware: serpent.RequireRangeArgs(0, 1),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
+40 -14
View File
@@ -56,7 +56,7 @@ var (
// anything.
ErrSilent = xerrors.New("silent error")
errKeyringNotSupported = xerrors.New("keyring storage is not supported on this operating system; remove the --use-keyring flag to use file-based storage")
errKeyringNotSupported = xerrors.New("keyring storage is not supported on this operating system; omit --use-keyring to use file-based storage")
)
const (
@@ -104,6 +104,7 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
r.resetPassword(),
r.sharing(),
r.state(),
r.tasksCommand(),
r.templates(),
r.tokens(),
r.users(),
@@ -149,7 +150,7 @@ func (r *RootCmd) AGPLExperimental() []*serpent.Command {
r.mcpCommand(),
r.promptExample(),
r.rptyCommand(),
r.tasksCommand(),
r.syncCommand(),
r.boundary(),
}
}
@@ -483,10 +484,12 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
Flag: varUseKeyring,
Env: envUseKeyring,
Description: "Store and retrieve session tokens using the operating system " +
"keyring. Currently only supported on Windows. By default, tokens are " +
"stored in plain text files.",
Value: serpent.BoolOf(&r.useKeyring),
Group: globalGroup,
"keyring. This flag is ignored and file-based storage is used when " +
"--global-config is set or keyring usage is not supported on the current " +
"platform. Set to false to force file-based storage on supported platforms.",
Default: "true",
Value: serpent.BoolOf(&r.useKeyring),
Group: globalGroup,
},
{
Flag: "debug-http",
@@ -534,10 +537,12 @@ type RootCmd struct {
disableDirect bool
debugHTTP bool
disableNetworkTelemetry bool
noVersionCheck bool
noFeatureWarning bool
useKeyring bool
disableNetworkTelemetry bool
noVersionCheck bool
noFeatureWarning bool
useKeyring bool
keyringServiceName string
useKeyringWithGlobalConfig bool
}
// InitClient creates and configures a new client with authentication, telemetry,
@@ -718,8 +723,19 @@ func (r *RootCmd) createUnauthenticatedClient(ctx context.Context, serverURL *ur
// flag.
func (r *RootCmd) ensureTokenBackend() sessionstore.Backend {
if r.tokenBackend == nil {
if r.useKeyring {
r.tokenBackend = sessionstore.NewKeyring()
// Checking for the --global-config directory being set is a bit wonky but necessary
// to allow extensions that invoke the CLI with this flag (e.g. VS code) to continue
// working without modification. In the future we should modify these extensions to
// either access the credential in the keyring (like Coder Desktop) or some other
// approach that doesn't rely on the session token being stored on disk.
assumeExtensionInUse := r.globalConfig != config.DefaultDir() && !r.useKeyringWithGlobalConfig
keyringSupported := runtime.GOOS == "windows" || runtime.GOOS == "darwin"
if r.useKeyring && !assumeExtensionInUse && keyringSupported {
serviceName := sessionstore.DefaultServiceName
if r.keyringServiceName != "" {
serviceName = r.keyringServiceName
}
r.tokenBackend = sessionstore.NewKeyringWithService(serviceName)
} else {
r.tokenBackend = sessionstore.NewFile(r.createConfig)
}
@@ -727,8 +743,18 @@ func (r *RootCmd) ensureTokenBackend() sessionstore.Backend {
return r.tokenBackend
}
func (r *RootCmd) WithSessionStorageBackend(backend sessionstore.Backend) {
r.tokenBackend = backend
// WithKeyringServiceName sets a custom keyring service name for testing purposes.
// This allows tests to use isolated keyring storage while still exercising the
// genuine storage backend selection logic in ensureTokenBackend().
func (r *RootCmd) WithKeyringServiceName(serviceName string) {
r.keyringServiceName = serviceName
}
// UseKeyringWithGlobalConfig enables the use of the keyring storage backend
// when the --global-config directory is set. This is only intended as an override
// for tests, which require specifying the global config directory for test isolation.
func (r *RootCmd) UseKeyringWithGlobalConfig() {
r.useKeyringWithGlobalConfig = true
}
type AgentAuth struct {
+25
View File
@@ -72,6 +72,31 @@ func TestCommandHelp(t *testing.T) {
Name: "coder provisioner jobs list --output json",
Cmd: []string{"provisioner", "jobs", "list", "--output", "json"},
},
// TODO (SasSwart): Remove these once the sync commands are promoted out of experimental.
clitest.CommandHelpCase{
Name: "coder exp sync --help",
Cmd: []string{"exp", "sync", "--help"},
},
clitest.CommandHelpCase{
Name: "coder exp sync ping --help",
Cmd: []string{"exp", "sync", "ping", "--help"},
},
clitest.CommandHelpCase{
Name: "coder exp sync start --help",
Cmd: []string{"exp", "sync", "start", "--help"},
},
clitest.CommandHelpCase{
Name: "coder exp sync want --help",
Cmd: []string{"exp", "sync", "want", "--help"},
},
clitest.CommandHelpCase{
Name: "coder exp sync complete --help",
Cmd: []string{"exp", "sync", "complete", "--help"},
},
clitest.CommandHelpCase{
Name: "coder exp sync status --help",
Cmd: []string{"exp", "sync", "status", "--help"},
},
))
}
+4 -12
View File
@@ -47,9 +47,9 @@ var (
)
const (
// defaultServiceName is the service name used in keyrings for storing Coder CLI session
// DefaultServiceName is the service name used in keyrings for storing Coder CLI session
// tokens.
defaultServiceName = "coder-v2-credentials"
DefaultServiceName = "coder-v2-credentials"
)
// keyringProvider represents an operating system keyring. The expectation
@@ -108,17 +108,9 @@ type Keyring struct {
serviceName string
}
// NewKeyring creates a Keyring with the default service name for production use.
func NewKeyring() Keyring {
return Keyring{
provider: operatingSystemKeyring{},
serviceName: defaultServiceName,
}
}
// NewKeyringWithService creates a Keyring Backend that stores credentials under the
// specified service name. This is primarily intended for testing to avoid conflicts
// with production credentials and collisions between tests.
// specified service name. Generally, DefaultServiceName should be provided as the service
// name except in tests which may need parameterization to avoid conflicting keyring use.
func NewKeyringWithService(serviceName string) Keyring {
return Keyring{
provider: operatingSystemKeyring{},
+35
View File
@@ -0,0 +1,35 @@
package cli
import (
"github.com/coder/serpent"
)
func (r *RootCmd) syncCommand() *serpent.Command {
var socketPath string
cmd := &serpent.Command{
Use: "sync",
Short: "Manage unit dependencies for coordinated startup",
Long: "Commands for orchestrating unit startup order in workspaces. Units are most commonly coder scripts. Use these commands to declare dependencies between units, coordinate their startup sequence, and ensure units start only after their dependencies are ready. This helps prevent race conditions and startup failures.",
Handler: func(i *serpent.Invocation) error {
return i.Command.HelpHandler(i)
},
Children: []*serpent.Command{
r.syncPing(&socketPath),
r.syncStart(&socketPath),
r.syncWant(&socketPath),
r.syncComplete(&socketPath),
r.syncStatus(&socketPath),
},
Options: serpent.OptionSet{
{
Flag: "socket-path",
Env: "CODER_AGENT_SOCKET_PATH",
Description: "Specify the path for the agent socket.",
Value: serpent.StringOf(&socketPath),
},
},
}
return cmd
}
+47
View File
@@ -0,0 +1,47 @@
package cli
import (
"golang.org/x/xerrors"
"github.com/coder/coder/v2/agent/agentsocket"
"github.com/coder/coder/v2/agent/unit"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/serpent"
)
func (*RootCmd) syncComplete(socketPath *string) *serpent.Command {
cmd := &serpent.Command{
Use: "complete <unit>",
Short: "Mark a unit as complete",
Long: "Mark a unit as complete. Indicating to other units that it has completed its work. This allows units that depend on it to proceed with their startup.",
Handler: func(i *serpent.Invocation) error {
ctx := i.Context()
if len(i.Args) != 1 {
return xerrors.New("exactly one unit name is required")
}
unit := unit.ID(i.Args[0])
opts := []agentsocket.Option{}
if *socketPath != "" {
opts = append(opts, agentsocket.WithPath(*socketPath))
}
client, err := agentsocket.NewClient(ctx, opts...)
if err != nil {
return xerrors.Errorf("connect to agent socket: %w", err)
}
defer client.Close()
if err := client.SyncComplete(ctx, unit); err != nil {
return xerrors.Errorf("complete unit failed: %w", err)
}
cliui.Info(i.Stdout, "Success")
return nil
},
}
return cmd
}
+42
View File
@@ -0,0 +1,42 @@
package cli
import (
"golang.org/x/xerrors"
"github.com/coder/coder/v2/agent/agentsocket"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/serpent"
)
func (*RootCmd) syncPing(socketPath *string) *serpent.Command {
cmd := &serpent.Command{
Use: "ping",
Short: "Test agent socket connectivity and health",
Long: "Test connectivity to the local Coder agent socket to verify the agent is running and responsive. Useful for troubleshooting startup issues or verifying the agent is accessible before running other sync commands.",
Handler: func(i *serpent.Invocation) error {
ctx := i.Context()
opts := []agentsocket.Option{}
if *socketPath != "" {
opts = append(opts, agentsocket.WithPath(*socketPath))
}
client, err := agentsocket.NewClient(ctx, opts...)
if err != nil {
return xerrors.Errorf("connect to agent socket: %w", err)
}
defer client.Close()
err = client.Ping(ctx)
if err != nil {
return xerrors.Errorf("ping failed: %w", err)
}
cliui.Info(i.Stdout, "Success")
return nil
},
}
return cmd
}
+101
View File
@@ -0,0 +1,101 @@
package cli
import (
"context"
"time"
"golang.org/x/xerrors"
"github.com/coder/serpent"
"github.com/coder/coder/v2/agent/agentsocket"
"github.com/coder/coder/v2/agent/unit"
"github.com/coder/coder/v2/cli/cliui"
)
const (
syncPollInterval = 1 * time.Second
)
func (*RootCmd) syncStart(socketPath *string) *serpent.Command {
var timeout time.Duration
cmd := &serpent.Command{
Use: "start <unit>",
Short: "Wait until all unit dependencies are satisfied",
Long: "Wait until all dependencies are satisfied, consider the unit to have started, then allow it to proceed. This command polls until dependencies are ready, then marks the unit as started.",
Handler: func(i *serpent.Invocation) error {
ctx := i.Context()
if len(i.Args) != 1 {
return xerrors.New("exactly one unit name is required")
}
unitName := unit.ID(i.Args[0])
if timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, timeout)
defer cancel()
}
opts := []agentsocket.Option{}
if *socketPath != "" {
opts = append(opts, agentsocket.WithPath(*socketPath))
}
client, err := agentsocket.NewClient(ctx, opts...)
if err != nil {
return xerrors.Errorf("connect to agent socket: %w", err)
}
defer client.Close()
ready, err := client.SyncReady(ctx, unitName)
if err != nil {
return xerrors.Errorf("error checking dependencies: %w", err)
}
if !ready {
cliui.Infof(i.Stdout, "Waiting for dependencies of unit '%s' to be satisfied...", unitName)
ticker := time.NewTicker(syncPollInterval)
defer ticker.Stop()
pollLoop:
for {
select {
case <-ctx.Done():
if ctx.Err() == context.DeadlineExceeded {
return xerrors.Errorf("timeout waiting for dependencies of unit '%s'", unitName)
}
return ctx.Err()
case <-ticker.C:
ready, err := client.SyncReady(ctx, unitName)
if err != nil {
return xerrors.Errorf("error checking dependencies: %w", err)
}
if ready {
break pollLoop
}
}
}
}
if err := client.SyncStart(ctx, unitName); err != nil {
return xerrors.Errorf("start unit failed: %w", err)
}
cliui.Info(i.Stdout, "Success")
return nil
},
}
cmd.Options = append(cmd.Options, serpent.Option{
Flag: "timeout",
Description: "Maximum time to wait for dependencies (e.g., 30s, 5m). 5m by default.",
Value: serpent.DurationOf(&timeout),
Default: "5m",
})
return cmd
}
+88
View File
@@ -0,0 +1,88 @@
package cli
import (
"fmt"
"golang.org/x/xerrors"
"github.com/coder/serpent"
"github.com/coder/coder/v2/agent/agentsocket"
"github.com/coder/coder/v2/agent/unit"
"github.com/coder/coder/v2/cli/cliui"
)
func (*RootCmd) syncStatus(socketPath *string) *serpent.Command {
formatter := cliui.NewOutputFormatter(
cliui.ChangeFormatterData(
cliui.TableFormat(
[]agentsocket.DependencyInfo{},
[]string{
"depends on",
"required status",
"current status",
"satisfied",
},
),
func(data any) (any, error) {
resp, ok := data.(agentsocket.SyncStatusResponse)
if !ok {
return nil, xerrors.Errorf("expected agentsocket.SyncStatusResponse, got %T", data)
}
return resp.Dependencies, nil
}),
cliui.JSONFormat(),
)
cmd := &serpent.Command{
Use: "status <unit>",
Short: "Show unit status and dependency state",
Long: "Show the current status of a unit, whether it is ready to start, and lists its dependencies. Shows which dependencies are satisfied and which are still pending. Supports multiple output formats.",
Handler: func(i *serpent.Invocation) error {
ctx := i.Context()
if len(i.Args) != 1 {
return xerrors.New("exactly one unit name is required")
}
unit := unit.ID(i.Args[0])
opts := []agentsocket.Option{}
if *socketPath != "" {
opts = append(opts, agentsocket.WithPath(*socketPath))
}
client, err := agentsocket.NewClient(ctx, opts...)
if err != nil {
return xerrors.Errorf("connect to agent socket: %w", err)
}
defer client.Close()
statusResp, err := client.SyncStatus(ctx, unit)
if err != nil {
return xerrors.Errorf("get status failed: %w", err)
}
var out string
header := fmt.Sprintf("Unit: %s\nStatus: %s\nReady: %t\n\nDependencies:\n", unit, statusResp.Status, statusResp.IsReady)
if formatter.FormatID() == "table" && len(statusResp.Dependencies) == 0 {
out = header + "No dependencies found"
} else {
out, err = formatter.Format(ctx, statusResp)
if err != nil {
return xerrors.Errorf("format status: %w", err)
}
if formatter.FormatID() == "table" {
out = header + out
}
}
_, _ = fmt.Fprintln(i.Stdout, out)
return nil
},
}
formatter.AttachOptions(&cmd.Options)
return cmd
}
+330
View File
@@ -0,0 +1,330 @@
//go:build !windows
package cli_test
import (
"bytes"
"context"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"cdr.dev/slog"
"github.com/coder/coder/v2/agent/agentsocket"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/testutil"
)
// setupSocketServer creates an agentsocket server at a temporary path for testing.
// Returns the socket path and a cleanup function. The path should be passed to
// sync commands via the --socket-path flag.
func setupSocketServer(t *testing.T) (path string, cleanup func()) {
t.Helper()
// Use a temporary socket path for each test
socketPath := filepath.Join(tempDirUnixSocket(t), "test.sock")
// Create parent directory if needed
parentDir := filepath.Dir(socketPath)
err := os.MkdirAll(parentDir, 0o700)
require.NoError(t, err, "create socket directory")
server, err := agentsocket.NewServer(
slog.Make().Leveled(slog.LevelDebug),
agentsocket.WithPath(socketPath),
)
require.NoError(t, err, "create socket server")
// Return cleanup function
return socketPath, func() {
err := server.Close()
require.NoError(t, err, "close socket server")
_ = os.Remove(socketPath)
}
}
func TestSyncCommands_Golden(t *testing.T) {
t.Parallel()
t.Run("ping", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "ping", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/ping_success", outBuf.Bytes(), nil)
})
t.Run("start_no_dependencies", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "start", "test-unit", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/start_no_dependencies", outBuf.Bytes(), nil)
})
t.Run("start_with_dependencies", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
// Set up dependency: test-unit depends on dep-unit
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(path))
require.NoError(t, err)
// Declare dependency
err = client.SyncWant(ctx, "test-unit", "dep-unit")
require.NoError(t, err)
client.Close()
// Start a goroutine to complete the dependency after a short delay
// This simulates the dependency being satisfied while start is waiting
// The delay ensures the "Waiting..." message appears in the output
done := make(chan error, 1)
go func() {
// Wait a moment to let the start command begin waiting and print the message
time.Sleep(100 * time.Millisecond)
compCtx := context.Background()
compClient, err := agentsocket.NewClient(compCtx, agentsocket.WithPath(path))
if err != nil {
done <- err
return
}
defer compClient.Close()
// Start and complete the dependency unit
err = compClient.SyncStart(compCtx, "dep-unit")
if err != nil {
done <- err
return
}
err = compClient.SyncComplete(compCtx, "dep-unit")
done <- err
}()
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "start", "test-unit", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
// Run the start command - it should wait for the dependency
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
// Ensure the completion goroutine finished
select {
case err := <-done:
require.NoError(t, err, "complete dependency")
case <-time.After(time.Second):
// Goroutine should have finished by now
}
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/start_with_dependencies", outBuf.Bytes(), nil)
})
t.Run("want", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "want", "test-unit", "dep-unit", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/want_success", outBuf.Bytes(), nil)
})
t.Run("complete", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
// First start the unit
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(path))
require.NoError(t, err)
err = client.SyncStart(ctx, "test-unit")
require.NoError(t, err)
client.Close()
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "complete", "test-unit", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/complete_success", outBuf.Bytes(), nil)
})
t.Run("status_pending", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
// Set up a unit with unsatisfied dependency
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(path))
require.NoError(t, err)
err = client.SyncWant(ctx, "test-unit", "dep-unit")
require.NoError(t, err)
client.Close()
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "status", "test-unit", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/status_pending", outBuf.Bytes(), nil)
})
t.Run("status_started", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
// Start a unit
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(path))
require.NoError(t, err)
err = client.SyncStart(ctx, "test-unit")
require.NoError(t, err)
client.Close()
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "status", "test-unit", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/status_started", outBuf.Bytes(), nil)
})
t.Run("status_completed", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
// Start and complete a unit
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(path))
require.NoError(t, err)
err = client.SyncStart(ctx, "test-unit")
require.NoError(t, err)
err = client.SyncComplete(ctx, "test-unit")
require.NoError(t, err)
client.Close()
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "status", "test-unit", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/status_completed", outBuf.Bytes(), nil)
})
t.Run("status_with_dependencies", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
// Set up a unit with dependencies, some satisfied, some not
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(path))
require.NoError(t, err)
err = client.SyncWant(ctx, "test-unit", "dep-1")
require.NoError(t, err)
err = client.SyncWant(ctx, "test-unit", "dep-2")
require.NoError(t, err)
// Complete dep-1, leave dep-2 incomplete
err = client.SyncStart(ctx, "dep-1")
require.NoError(t, err)
err = client.SyncComplete(ctx, "dep-1")
require.NoError(t, err)
client.Close()
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "status", "test-unit", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/status_with_dependencies", outBuf.Bytes(), nil)
})
t.Run("status_json_format", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
// Set up a unit with dependencies
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(path))
require.NoError(t, err)
err = client.SyncWant(ctx, "test-unit", "dep-unit")
require.NoError(t, err)
err = client.SyncStart(ctx, "dep-unit")
require.NoError(t, err)
err = client.SyncComplete(ctx, "dep-unit")
require.NoError(t, err)
client.Close()
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "status", "test-unit", "--output", "json", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/status_json_format", outBuf.Bytes(), nil)
})
}
+49
View File
@@ -0,0 +1,49 @@
package cli
import (
"golang.org/x/xerrors"
"github.com/coder/serpent"
"github.com/coder/coder/v2/agent/agentsocket"
"github.com/coder/coder/v2/agent/unit"
"github.com/coder/coder/v2/cli/cliui"
)
func (*RootCmd) syncWant(socketPath *string) *serpent.Command {
cmd := &serpent.Command{
Use: "want <unit> <depends-on>",
Short: "Declare that a unit depends on another unit completing before it can start",
Long: "Declare that a unit depends on another unit completing before it can start. The unit specified first will not start until the second has signaled that it has completed.",
Handler: func(i *serpent.Invocation) error {
ctx := i.Context()
if len(i.Args) != 2 {
return xerrors.New("exactly two arguments are required: unit and depends-on")
}
dependentUnit := unit.ID(i.Args[0])
dependsOn := unit.ID(i.Args[1])
opts := []agentsocket.Option{}
if *socketPath != "" {
opts = append(opts, agentsocket.WithPath(*socketPath))
}
client, err := agentsocket.NewClient(ctx, opts...)
if err != nil {
return xerrors.Errorf("connect to agent socket: %w", err)
}
defer client.Close()
if err := client.SyncWant(ctx, dependentUnit, dependsOn); err != nil {
return xerrors.Errorf("declare dependency failed: %w", err)
}
cliui.Info(i.Stdout, "Success")
return nil
},
}
return cmd
}
+1 -1
View File
@@ -8,7 +8,7 @@ func (r *RootCmd) tasksCommand() *serpent.Command {
cmd := &serpent.Command{
Use: "task",
Aliases: []string{"tasks"},
Short: "Experimental task commands.",
Short: "Manage tasks",
Handler: func(i *serpent.Invocation) error {
return i.Command.HelpHandler(i)
},
@@ -28,27 +28,27 @@ func (r *RootCmd) taskCreate() *serpent.Command {
cmd := &serpent.Command{
Use: "create [input]",
Short: "Create an experimental task",
Short: "Create a task",
Long: FormatExamples(
Example{
Description: "Create a task with direct input",
Command: "coder exp task create \"Add authentication to the user service\"",
Command: "coder task create \"Add authentication to the user service\"",
},
Example{
Description: "Create a task with stdin input",
Command: "echo \"Add authentication to the user service\" | coder exp task create",
Command: "echo \"Add authentication to the user service\" | coder task create",
},
Example{
Description: "Create a task with a specific name",
Command: "coder exp task create --name task1 \"Add authentication to the user service\"",
Command: "coder task create --name task1 \"Add authentication to the user service\"",
},
Example{
Description: "Create a task from a specific template / preset",
Command: "coder exp task create --template backend-dev --preset \"My Preset\" \"Add authentication to the user service\"",
Command: "coder task create --template backend-dev --preset \"My Preset\" \"Add authentication to the user service\"",
},
Example{
Description: "Create a task for another user (requires appropriate permissions)",
Command: "coder exp task create --owner user@example.com \"Add authentication to the user service\"",
Command: "coder task create --owner user@example.com \"Add authentication to the user service\"",
},
),
Middleware: serpent.Chain(
@@ -111,8 +111,7 @@ func (r *RootCmd) taskCreate() *serpent.Command {
}
var (
ctx = inv.Context()
expClient = codersdk.NewExperimentalClient(client)
ctx = inv.Context()
taskInput string
templateVersionID uuid.UUID
@@ -208,7 +207,7 @@ func (r *RootCmd) taskCreate() *serpent.Command {
templateVersionPresetID = preset.ID
}
task, err := expClient.CreateTask(ctx, ownerArg, codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, ownerArg, codersdk.CreateTaskRequest{
Name: taskName,
TemplateVersionID: templateVersionID,
TemplateVersionPresetID: templateVersionPresetID,
@@ -69,7 +69,7 @@ func TestTaskCreate(t *testing.T) {
ActiveVersionID: templateVersionID,
},
})
case fmt.Sprintf("/api/experimental/tasks/%s", username):
case fmt.Sprintf("/api/v2/tasks/%s", username):
var req codersdk.CreateTaskRequest
if !httpapi.Read(ctx, w, r, &req) {
return
@@ -329,7 +329,7 @@ func TestTaskCreate(t *testing.T) {
ctx = testutil.Context(t, testutil.WaitShort)
srv = httptest.NewServer(tt.handler(t, ctx))
client = codersdk.New(testutil.MustURL(t, srv.URL))
args = []string{"exp", "task", "create"}
args = []string{"task", "create"}
sb strings.Builder
err error
)
@@ -17,19 +17,19 @@ import (
func (r *RootCmd) taskDelete() *serpent.Command {
cmd := &serpent.Command{
Use: "delete <task> [<task> ...]",
Short: "Delete experimental tasks",
Short: "Delete tasks",
Long: FormatExamples(
Example{
Description: "Delete a single task.",
Command: "$ coder exp task delete task1",
Command: "$ coder task delete task1",
},
Example{
Description: "Delete multiple tasks.",
Command: "$ coder exp task delete task1 task2 task3",
Command: "$ coder task delete task1 task2 task3",
},
Example{
Description: "Delete a task without confirmation.",
Command: "$ coder exp task delete task4 --yes",
Command: "$ coder task delete task4 --yes",
},
),
Middleware: serpent.Chain(
@@ -44,11 +44,10 @@ func (r *RootCmd) taskDelete() *serpent.Command {
if err != nil {
return err
}
exp := codersdk.NewExperimentalClient(client)
var tasks []codersdk.Task
for _, identifier := range inv.Args {
task, err := exp.TaskByIdentifier(ctx, identifier)
task, err := client.TaskByIdentifier(ctx, identifier)
if err != nil {
return xerrors.Errorf("resolve task %q: %w", identifier, err)
}
@@ -71,7 +70,7 @@ func (r *RootCmd) taskDelete() *serpent.Command {
for i, task := range tasks {
display := displayList[i]
if err := exp.DeleteTask(ctx, task.OwnerName, task.ID); err != nil {
if err := client.DeleteTask(ctx, task.OwnerName, task.ID); err != nil {
return xerrors.Errorf("delete task %q: %w", display, err)
}
_, _ = fmt.Fprintln(
@@ -56,7 +56,7 @@ func TestExpTaskDelete(t *testing.T) {
taskID := uuid.MustParse(id1)
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks/me/exists":
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks/me/exists":
c.nameResolves.Add(1)
httpapi.Write(r.Context(), w, http.StatusOK,
codersdk.Task{
@@ -64,7 +64,7 @@ func TestExpTaskDelete(t *testing.T) {
Name: "exists",
OwnerName: "me",
})
case r.Method == http.MethodDelete && r.URL.Path == "/api/experimental/tasks/me/"+id1:
case r.Method == http.MethodDelete && r.URL.Path == "/api/v2/tasks/me/"+id1:
c.deleteCalls.Add(1)
w.WriteHeader(http.StatusAccepted)
default:
@@ -82,13 +82,13 @@ func TestExpTaskDelete(t *testing.T) {
buildHandler: func(c *testCounters) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks/me/"+id2:
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks/me/"+id2:
httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse(id2),
OwnerName: "me",
Name: "uuid-task",
})
case r.Method == http.MethodDelete && r.URL.Path == "/api/experimental/tasks/me/"+id2:
case r.Method == http.MethodDelete && r.URL.Path == "/api/v2/tasks/me/"+id2:
c.deleteCalls.Add(1)
w.WriteHeader(http.StatusAccepted)
default:
@@ -104,24 +104,24 @@ func TestExpTaskDelete(t *testing.T) {
buildHandler: func(c *testCounters) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks/me/first":
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks/me/first":
c.nameResolves.Add(1)
httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse(id3),
Name: "first",
OwnerName: "me",
})
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks/me/"+id4:
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks/me/"+id4:
c.nameResolves.Add(1)
httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse(id4),
OwnerName: "me",
Name: "uuid-task-4",
})
case r.Method == http.MethodDelete && r.URL.Path == "/api/experimental/tasks/me/"+id3:
case r.Method == http.MethodDelete && r.URL.Path == "/api/v2/tasks/me/"+id3:
c.deleteCalls.Add(1)
w.WriteHeader(http.StatusAccepted)
case r.Method == http.MethodDelete && r.URL.Path == "/api/experimental/tasks/me/"+id4:
case r.Method == http.MethodDelete && r.URL.Path == "/api/v2/tasks/me/"+id4:
c.deleteCalls.Add(1)
w.WriteHeader(http.StatusAccepted)
default:
@@ -140,7 +140,7 @@ func TestExpTaskDelete(t *testing.T) {
buildHandler: func(_ *testCounters) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks" && r.URL.Query().Get("q") == "owner:\"me\"":
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks" && r.URL.Query().Get("q") == "owner:\"me\"":
httpapi.Write(r.Context(), w, http.StatusOK, struct {
Tasks []codersdk.Task `json:"tasks"`
Count int `json:"count"`
@@ -163,14 +163,14 @@ func TestExpTaskDelete(t *testing.T) {
taskID := uuid.MustParse(id5)
return func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/experimental/tasks/me/bad":
case r.Method == http.MethodGet && r.URL.Path == "/api/v2/tasks/me/bad":
c.nameResolves.Add(1)
httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Task{
ID: taskID,
Name: "bad",
OwnerName: "me",
})
case r.Method == http.MethodDelete && r.URL.Path == "/api/experimental/tasks/me/bad":
case r.Method == http.MethodDelete && r.URL.Path == "/api/v2/tasks/me/bad":
httpapi.InternalServerError(w, xerrors.New("boom"))
default:
httpapi.InternalServerError(w, xerrors.New("unwanted path: "+r.Method+" "+r.URL.Path))
@@ -193,7 +193,7 @@ func TestExpTaskDelete(t *testing.T) {
client := codersdk.New(testutil.MustURL(t, srv.URL))
args := append([]string{"exp", "task", "delete"}, tc.args...)
args := append([]string{"task", "delete"}, tc.args...)
inv, root := clitest.New(t, args...)
inv = inv.WithContext(ctx)
clitest.SetupConfig(t, client, root)
+7 -8
View File
@@ -69,27 +69,27 @@ func (r *RootCmd) taskList() *serpent.Command {
cmd := &serpent.Command{
Use: "list",
Short: "List experimental tasks",
Short: "List tasks",
Long: FormatExamples(
Example{
Description: "List tasks for the current user.",
Command: "coder exp task list",
Command: "coder task list",
},
Example{
Description: "List tasks for a specific user.",
Command: "coder exp task list --user someone-else",
Command: "coder task list --user someone-else",
},
Example{
Description: "List all tasks you can view.",
Command: "coder exp task list --all",
Command: "coder task list --all",
},
Example{
Description: "List all your running tasks.",
Command: "coder exp task list --status running",
Command: "coder task list --status running",
},
Example{
Description: "As above, but only show IDs.",
Command: "coder exp task list --status running --quiet",
Command: "coder task list --status running --quiet",
},
),
Aliases: []string{"ls"},
@@ -135,14 +135,13 @@ func (r *RootCmd) taskList() *serpent.Command {
}
ctx := inv.Context()
exp := codersdk.NewExperimentalClient(client)
targetUser := strings.TrimSpace(user)
if targetUser == "" && !all {
targetUser = codersdk.Me
}
tasks, err := exp.Tasks(ctx, &codersdk.TasksFilter{
tasks, err := client.Tasks(ctx, &codersdk.TasksFilter{
Owner: targetUser,
Status: codersdk.TaskStatus(statusFilter),
})
@@ -69,7 +69,7 @@ func TestExpTaskList(t *testing.T) {
owner := coderdtest.CreateFirstUser(t, client)
memberClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
inv, root := clitest.New(t, "exp", "task", "list")
inv, root := clitest.New(t, "task", "list")
clitest.SetupConfig(t, memberClient, root)
pty := ptytest.New(t).Attach(inv)
@@ -93,7 +93,7 @@ func TestExpTaskList(t *testing.T) {
wantPrompt := "build me a web app"
task := makeAITask(t, db, owner.OrganizationID, owner.UserID, memberUser.ID, database.WorkspaceTransitionStart, wantPrompt)
inv, root := clitest.New(t, "exp", "task", "list", "--column", "id,name,status,initial prompt")
inv, root := clitest.New(t, "task", "list", "--column", "id,name,status,initial prompt")
clitest.SetupConfig(t, memberClient, root)
pty := ptytest.New(t).Attach(inv)
@@ -122,7 +122,7 @@ func TestExpTaskList(t *testing.T) {
pausedTask := makeAITask(t, db, owner.OrganizationID, owner.UserID, memberUser.ID, database.WorkspaceTransitionStop, "stop me please")
// Use JSON output to reliably validate filtering.
inv, root := clitest.New(t, "exp", "task", "list", "--status=paused", "--output=json")
inv, root := clitest.New(t, "task", "list", "--status=paused", "--output=json")
clitest.SetupConfig(t, memberClient, root)
ctx := testutil.Context(t, testutil.WaitShort)
@@ -153,7 +153,7 @@ func TestExpTaskList(t *testing.T) {
_ = makeAITask(t, db, owner.OrganizationID, owner.UserID, memberUser.ID, database.WorkspaceTransitionStart, "other-task")
task := makeAITask(t, db, owner.OrganizationID, owner.UserID, owner.UserID, database.WorkspaceTransitionStart, "me-task")
inv, root := clitest.New(t, "exp", "task", "list", "--user", "me")
inv, root := clitest.New(t, "task", "list", "--user", "me")
//nolint:gocritic // Owner client is intended here smoke test the member task not showing up.
clitest.SetupConfig(t, client, root)
@@ -180,7 +180,7 @@ func TestExpTaskList(t *testing.T) {
task2 := makeAITask(t, db, owner.OrganizationID, owner.UserID, memberUser.ID, database.WorkspaceTransitionStop, "stop me please")
// Given: We add the `--quiet` flag
inv, root := clitest.New(t, "exp", "task", "list", "--quiet")
inv, root := clitest.New(t, "task", "list", "--quiet")
clitest.SetupConfig(t, memberClient, root)
ctx := testutil.Context(t, testutil.WaitShort)
@@ -224,7 +224,7 @@ func TestExpTaskList_OwnerCanListOthers(t *testing.T) {
t.Parallel()
// As the owner, list only member A tasks.
inv, root := clitest.New(t, "exp", "task", "list", "--user", memberAUser.Username, "--output=json")
inv, root := clitest.New(t, "task", "list", "--user", memberAUser.Username, "--output=json")
//nolint:gocritic // Owner client is intended here to allow member tasks to be listed.
clitest.SetupConfig(t, ownerClient, root)
@@ -252,7 +252,7 @@ func TestExpTaskList_OwnerCanListOthers(t *testing.T) {
// As the owner, list all tasks to verify both member tasks are present.
// Use JSON output to reliably validate filtering.
inv, root := clitest.New(t, "exp", "task", "list", "--all", "--output=json")
inv, root := clitest.New(t, "task", "list", "--all", "--output=json")
//nolint:gocritic // Owner client is intended here to allow all tasks to be listed.
clitest.SetupConfig(t, ownerClient, root)
+3 -4
View File
@@ -28,7 +28,7 @@ func (r *RootCmd) taskLogs() *serpent.Command {
Long: FormatExamples(
Example{
Description: "Show logs for a given task.",
Command: "coder exp task logs task1",
Command: "coder task logs task1",
}),
Middleware: serpent.Chain(
serpent.RequireNArgs(1),
@@ -41,16 +41,15 @@ func (r *RootCmd) taskLogs() *serpent.Command {
var (
ctx = inv.Context()
exp = codersdk.NewExperimentalClient(client)
identifier = inv.Args[0]
)
task, err := exp.TaskByIdentifier(ctx, identifier)
task, err := client.TaskByIdentifier(ctx, identifier)
if err != nil {
return xerrors.Errorf("resolve task %q: %w", identifier, err)
}
logs, err := exp.TaskLogs(ctx, codersdk.Me, task.ID)
logs, err := client.TaskLogs(ctx, codersdk.Me, task.ID)
if err != nil {
return xerrors.Errorf("get task logs: %w", err)
}
@@ -46,7 +46,7 @@ func Test_TaskLogs(t *testing.T) {
userClient := client // user already has access to their own workspace
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "logs", task.Name, "--output", "json")
inv, root := clitest.New(t, "task", "logs", task.Name, "--output", "json")
inv.Stdout = &stdout
clitest.SetupConfig(t, userClient, root)
@@ -72,7 +72,7 @@ func Test_TaskLogs(t *testing.T) {
userClient := client
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "logs", task.ID.String(), "--output", "json")
inv, root := clitest.New(t, "task", "logs", task.ID.String(), "--output", "json")
inv.Stdout = &stdout
clitest.SetupConfig(t, userClient, root)
@@ -98,7 +98,7 @@ func Test_TaskLogs(t *testing.T) {
userClient := client
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "logs", task.ID.String())
inv, root := clitest.New(t, "task", "logs", task.ID.String())
inv.Stdout = &stdout
clitest.SetupConfig(t, userClient, root)
@@ -121,7 +121,7 @@ func Test_TaskLogs(t *testing.T) {
userClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "logs", "doesnotexist")
inv, root := clitest.New(t, "task", "logs", "doesnotexist")
inv.Stdout = &stdout
clitest.SetupConfig(t, userClient, root)
@@ -139,7 +139,7 @@ func Test_TaskLogs(t *testing.T) {
userClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "logs", uuid.Nil.String())
inv, root := clitest.New(t, "task", "logs", uuid.Nil.String())
inv.Stdout = &stdout
clitest.SetupConfig(t, userClient, root)
@@ -155,7 +155,7 @@ func Test_TaskLogs(t *testing.T) {
client, task := setupCLITaskTest(ctx, t, fakeAgentAPITaskLogsErr(assert.AnError))
userClient := client
inv, root := clitest.New(t, "exp", "task", "logs", task.ID.String())
inv, root := clitest.New(t, "task", "logs", task.ID.String())
clitest.SetupConfig(t, userClient, root)
err := inv.WithContext(ctx).Run()
+4 -5
View File
@@ -17,10 +17,10 @@ func (r *RootCmd) taskSend() *serpent.Command {
Short: "Send input to a task",
Long: FormatExamples(Example{
Description: "Send direct input to a task.",
Command: "coder exp task send task1 \"Please also add unit tests\"",
Command: "coder task send task1 \"Please also add unit tests\"",
}, Example{
Description: "Send input from stdin to a task.",
Command: "echo \"Please also add unit tests\" | coder exp task send task1 --stdin",
Command: "echo \"Please also add unit tests\" | coder task send task1 --stdin",
}),
Middleware: serpent.RequireRangeArgs(1, 2),
Options: serpent.OptionSet{
@@ -39,7 +39,6 @@ func (r *RootCmd) taskSend() *serpent.Command {
var (
ctx = inv.Context()
exp = codersdk.NewExperimentalClient(client)
identifier = inv.Args[0]
taskInput string
@@ -60,12 +59,12 @@ func (r *RootCmd) taskSend() *serpent.Command {
taskInput = inv.Args[1]
}
task, err := exp.TaskByIdentifier(ctx, identifier)
task, err := client.TaskByIdentifier(ctx, identifier)
if err != nil {
return xerrors.Errorf("resolve task: %w", err)
}
if err = exp.TaskSend(ctx, codersdk.Me, task.ID, codersdk.TaskSendRequest{Input: taskInput}); err != nil {
if err = client.TaskSend(ctx, codersdk.Me, task.ID, codersdk.TaskSendRequest{Input: taskInput}); err != nil {
return xerrors.Errorf("send input to task: %w", err)
}
@@ -30,7 +30,7 @@ func Test_TaskSend(t *testing.T) {
userClient := client
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "send", task.Name, "carry on with the task")
inv, root := clitest.New(t, "task", "send", task.Name, "carry on with the task")
inv.Stdout = &stdout
clitest.SetupConfig(t, userClient, root)
@@ -46,7 +46,7 @@ func Test_TaskSend(t *testing.T) {
userClient := client
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "send", task.ID.String(), "carry on with the task")
inv, root := clitest.New(t, "task", "send", task.ID.String(), "carry on with the task")
inv.Stdout = &stdout
clitest.SetupConfig(t, userClient, root)
@@ -62,7 +62,7 @@ func Test_TaskSend(t *testing.T) {
userClient := client
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "send", task.Name, "--stdin")
inv, root := clitest.New(t, "task", "send", task.Name, "--stdin")
inv.Stdout = &stdout
inv.Stdin = strings.NewReader("carry on with the task")
clitest.SetupConfig(t, userClient, root)
@@ -80,7 +80,7 @@ func Test_TaskSend(t *testing.T) {
userClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "send", "doesnotexist", "some task input")
inv, root := clitest.New(t, "task", "send", "doesnotexist", "some task input")
inv.Stdout = &stdout
clitest.SetupConfig(t, userClient, root)
@@ -98,7 +98,7 @@ func Test_TaskSend(t *testing.T) {
userClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "send", uuid.Nil.String(), "some task input")
inv, root := clitest.New(t, "task", "send", uuid.Nil.String(), "some task input")
inv.Stdout = &stdout
clitest.SetupConfig(t, userClient, root)
@@ -114,7 +114,7 @@ func Test_TaskSend(t *testing.T) {
userClient, task := setupCLITaskTest(ctx, t, fakeAgentAPITaskSendErr(t, assert.AnError))
var stdout strings.Builder
inv, root := clitest.New(t, "exp", "task", "send", task.Name, "some task input")
inv, root := clitest.New(t, "task", "send", task.Name, "some task input")
inv.Stdout = &stdout
clitest.SetupConfig(t, userClient, root)
@@ -47,11 +47,11 @@ func (r *RootCmd) taskStatus() *serpent.Command {
Long: FormatExamples(
Example{
Description: "Show the status of a given task.",
Command: "coder exp task status task1",
Command: "coder task status task1",
},
Example{
Description: "Watch the status of a given task until it completes (idle or stopped).",
Command: "coder exp task status task1 --watch",
Command: "coder task status task1 --watch",
},
),
Use: "status",
@@ -83,10 +83,9 @@ func (r *RootCmd) taskStatus() *serpent.Command {
}
ctx := i.Context()
exp := codersdk.NewExperimentalClient(client)
identifier := i.Args[0]
task, err := exp.TaskByIdentifier(ctx, identifier)
task, err := client.TaskByIdentifier(ctx, identifier)
if err != nil {
return err
}
@@ -107,7 +106,7 @@ func (r *RootCmd) taskStatus() *serpent.Command {
// TODO: implement streaming updates instead of polling
lastStatusRow := tsr
for range t.C {
task, err := exp.TaskByID(ctx, task.ID)
task, err := client.TaskByID(ctx, task.ID)
if err != nil {
return err
}
@@ -36,7 +36,7 @@ func Test_TaskStatus(t *testing.T) {
hf: func(ctx context.Context, _ time.Time) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/experimental/tasks/me/doesnotexist":
case "/api/v2/tasks/me/doesnotexist":
httpapi.ResourceNotFound(w)
return
default:
@@ -52,7 +52,7 @@ func Test_TaskStatus(t *testing.T) {
hf: func(ctx context.Context, now time.Time) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/experimental/tasks/me/exists":
case "/api/v2/tasks/me/exists":
httpapi.Write(ctx, w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
WorkspaceStatus: codersdk.WorkspaceStatusRunning,
@@ -88,7 +88,7 @@ func Test_TaskStatus(t *testing.T) {
var calls atomic.Int64
return func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/experimental/tasks/me/exists":
case "/api/v2/tasks/me/exists":
httpapi.Write(ctx, w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
Name: "exists",
@@ -103,7 +103,7 @@ func Test_TaskStatus(t *testing.T) {
Status: codersdk.TaskStatusPending,
})
return
case "/api/experimental/tasks/me/11111111-1111-1111-1111-111111111111":
case "/api/v2/tasks/me/11111111-1111-1111-1111-111111111111":
defer calls.Add(1)
switch calls.Load() {
case 0:
@@ -189,6 +189,7 @@ func Test_TaskStatus(t *testing.T) {
"owner_id": "00000000-0000-0000-0000-000000000000",
"owner_name": "me",
"name": "exists",
"display_name": "Task exists",
"template_id": "00000000-0000-0000-0000-000000000000",
"template_version_id": "00000000-0000-0000-0000-000000000000",
"template_name": "",
@@ -218,11 +219,12 @@ func Test_TaskStatus(t *testing.T) {
ts := time.Date(2025, 8, 26, 12, 34, 56, 0, time.UTC)
return func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/experimental/tasks/me/exists":
case "/api/v2/tasks/me/exists":
httpapi.Write(ctx, w, http.StatusOK, codersdk.Task{
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
Name: "exists",
OwnerName: "me",
ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
Name: "exists",
DisplayName: "Task exists",
OwnerName: "me",
WorkspaceAgentHealth: &codersdk.WorkspaceAgentHealth{
Healthy: true,
},
@@ -254,7 +256,7 @@ func Test_TaskStatus(t *testing.T) {
srv = httptest.NewServer(http.HandlerFunc(tc.hf(ctx, now)))
client = codersdk.New(testutil.MustURL(t, srv.URL))
sb = strings.Builder{}
args = []string{"exp", "task", "status", "--watch-interval", testutil.IntervalFast.String()}
args = []string{"task", "status", "--watch-interval", testutil.IntervalFast.String()}
)
t.Cleanup(srv.Close)
+8 -10
View File
@@ -60,14 +60,14 @@ func Test_Tasks(t *testing.T) {
}{
{
name: "create task",
cmdArgs: []string{"exp", "task", "create", "test task input for " + t.Name(), "--name", taskName, "--template", taskTpl.Name},
cmdArgs: []string{"task", "create", "test task input for " + t.Name(), "--name", taskName, "--template", taskTpl.Name},
assertFn: func(stdout string, userClient *codersdk.Client) {
require.Contains(t, stdout, taskName, "task name should be in output")
},
},
{
name: "list tasks after create",
cmdArgs: []string{"exp", "task", "list", "--output", "json"},
cmdArgs: []string{"task", "list", "--output", "json"},
assertFn: func(stdout string, userClient *codersdk.Client) {
var tasks []codersdk.Task
err := json.NewDecoder(strings.NewReader(stdout)).Decode(&tasks)
@@ -88,7 +88,7 @@ func Test_Tasks(t *testing.T) {
},
{
name: "get task status after create",
cmdArgs: []string{"exp", "task", "status", taskName, "--output", "json"},
cmdArgs: []string{"task", "status", taskName, "--output", "json"},
assertFn: func(stdout string, userClient *codersdk.Client) {
var task codersdk.Task
require.NoError(t, json.NewDecoder(strings.NewReader(stdout)).Decode(&task), "should unmarshal task status")
@@ -98,12 +98,12 @@ func Test_Tasks(t *testing.T) {
},
{
name: "send task message",
cmdArgs: []string{"exp", "task", "send", taskName, "hello"},
cmdArgs: []string{"task", "send", taskName, "hello"},
// Assertions for this happen in the fake agent API handler.
},
{
name: "read task logs",
cmdArgs: []string{"exp", "task", "logs", taskName, "--output", "json"},
cmdArgs: []string{"task", "logs", taskName, "--output", "json"},
assertFn: func(stdout string, userClient *codersdk.Client) {
var logs []codersdk.TaskLogEntry
require.NoError(t, json.NewDecoder(strings.NewReader(stdout)).Decode(&logs), "should unmarshal task logs")
@@ -118,12 +118,11 @@ func Test_Tasks(t *testing.T) {
},
{
name: "delete task",
cmdArgs: []string{"exp", "task", "delete", taskName, "--yes"},
cmdArgs: []string{"task", "delete", taskName, "--yes"},
assertFn: func(stdout string, userClient *codersdk.Client) {
// The task should eventually no longer show up in the list of tasks
testutil.Eventually(ctx, t, func(ctx context.Context) bool {
expClient := codersdk.NewExperimentalClient(userClient)
tasks, err := expClient.Tasks(ctx, &codersdk.TasksFilter{})
tasks, err := userClient.Tasks(ctx, &codersdk.TasksFilter{})
if !assert.NoError(t, err) {
return false
}
@@ -248,8 +247,7 @@ func setupCLITaskTest(ctx context.Context, t *testing.T, agentAPIHandlers map[st
template := createAITaskTemplate(t, client, owner.OrganizationID, withSidebarURL(fakeAPI.URL()), withAgentToken(authToken))
wantPrompt := "test prompt"
exp := codersdk.NewExperimentalClient(userClient)
task, err := exp.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
task, err := userClient.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: wantPrompt,
Name: "test-task",
@@ -0,0 +1 @@
Success
@@ -0,0 +1 @@
Success
@@ -0,0 +1 @@
Success
@@ -0,0 +1,2 @@
Waiting for dependencies of unit 'test-unit' to be satisfied...
Success
@@ -0,0 +1,6 @@
Unit: test-unit
Status: completed
Ready: true
Dependencies:
No dependencies found
@@ -0,0 +1,13 @@
{
"unit_name": "test-unit",
"status": "pending",
"is_ready": true,
"dependencies": [
{
"depends_on": "dep-unit",
"required_status": "completed",
"current_status": "completed",
"is_satisfied": true
}
]
}
@@ -0,0 +1,7 @@
Unit: test-unit
Status: pending
Ready: false
Dependencies:
DEPENDS ON REQUIRED STATUS CURRENT STATUS SATISFIED
dep-unit completed not registered false
@@ -0,0 +1,6 @@
Unit: test-unit
Status: started
Ready: true
Dependencies:
No dependencies found
@@ -0,0 +1,8 @@
Unit: test-unit
Status: pending
Ready: false
Dependencies:
DEPENDS ON REQUIRED STATUS CURRENT STATUS SATISFIED
dep-1 completed completed true
dep-2 completed not registered false
@@ -0,0 +1 @@
Success
+6 -3
View File
@@ -53,6 +53,7 @@ SUBCOMMANDS:
stop Stop a workspace
support Commands for troubleshooting issues with a Coder
deployment.
task Manage tasks
templates Manage templates
tokens Manage personal access tokens
unfavorite Remove a workspace from your favorites
@@ -108,10 +109,12 @@ variables or flags.
--url url, $CODER_URL
URL to a deployment.
--use-keyring bool, $CODER_USE_KEYRING
--use-keyring bool, $CODER_USE_KEYRING (default: true)
Store and retrieve session tokens using the operating system keyring.
Currently only supported on Windows. By default, tokens are stored in
plain text files.
This flag is ignored and file-based storage is used when
--global-config is set or keyring usage is not supported on the
current platform. Set to false to force file-based storage on
supported platforms.
-v, --verbose bool, $CODER_VERBOSE
Enable verbose output.
+6
View File
@@ -67,6 +67,12 @@ OPTIONS:
--script-data-dir string, $CODER_AGENT_SCRIPT_DATA_DIR (default: /tmp)
Specify the location for storing script data.
--socket-path string, $CODER_AGENT_SOCKET_PATH
Specify the path for the agent socket.
--socket-server-enabled bool, $CODER_AGENT_SOCKET_SERVER_ENABLED (default: false)
Enable the agent socket server.
--ssh-max-timeout duration, $CODER_AGENT_SSH_MAX_TIMEOUT (default: 72h)
Specify the max timeout for a SSH connection, it is advisable to set
it to a minimum of 60s, but no more than 72h.
+27
View File
@@ -0,0 +1,27 @@
coder v0.0.0-devel
USAGE:
coder exp sync [flags]
Manage unit dependencies for coordinated startup
Commands for orchestrating unit startup order in workspaces. Units are most
commonly coder scripts. Use these commands to declare dependencies between
units, coordinate their startup sequence, and ensure units start only after
their dependencies are ready. This helps prevent race conditions and startup
failures.
SUBCOMMANDS:
complete Mark a unit as complete
ping Test agent socket connectivity and health
start Wait until all unit dependencies are satisfied
status Show unit status and dependency state
want Declare that a unit depends on another unit completing before it
can start
OPTIONS:
--socket-path string, $CODER_AGENT_SOCKET_PATH
Specify the path for the agent socket.
———
Run `coder --help` for a list of global options.
+12
View File
@@ -0,0 +1,12 @@
coder v0.0.0-devel
USAGE:
coder exp sync complete <unit>
Mark a unit as complete
Mark a unit as complete. Indicating to other units that it has completed its
work. This allows units that depend on it to proceed with their startup.
———
Run `coder --help` for a list of global options.
+13
View File
@@ -0,0 +1,13 @@
coder v0.0.0-devel
USAGE:
coder exp sync ping
Test agent socket connectivity and health
Test connectivity to the local Coder agent socket to verify the agent is
running and responsive. Useful for troubleshooting startup issues or verifying
the agent is accessible before running other sync commands.
———
Run `coder --help` for a list of global options.
+17
View File
@@ -0,0 +1,17 @@
coder v0.0.0-devel
USAGE:
coder exp sync start [flags] <unit>
Wait until all unit dependencies are satisfied
Wait until all dependencies are satisfied, consider the unit to have started,
then allow it to proceed. This command polls until dependencies are ready,
then marks the unit as started.
OPTIONS:
--timeout duration (default: 5m)
Maximum time to wait for dependencies (e.g., 30s, 5m). 5m by default.
———
Run `coder --help` for a list of global options.
+20
View File
@@ -0,0 +1,20 @@
coder v0.0.0-devel
USAGE:
coder exp sync status [flags] <unit>
Show unit status and dependency state
Show the current status of a unit, whether it is ready to start, and lists its
dependencies. Shows which dependencies are satisfied and which are still
pending. Supports multiple output formats.
OPTIONS:
-c, --column [depends on|required status|current status|satisfied] (default: depends on,required status,current status,satisfied)
Columns to display in table output.
-o, --output table|json (default: table)
Output format.
———
Run `coder --help` for a list of global options.
+13
View File
@@ -0,0 +1,13 @@
coder v0.0.0-devel
USAGE:
coder exp sync want <unit> <depends-on>
Declare that a unit depends on another unit completing before it can start
Declare that a unit depends on another unit completing before it can start.
The unit specified first will not start until the second has signaled that it
has completed.
———
Run `coder --help` for a list of global options.
+3 -3
View File
@@ -5,9 +5,9 @@ USAGE:
Authenticate with Coder deployment
By default, the session token is stored in a plain text file. Use the
--use-keyring flag or set CODER_USE_KEYRING=true to store the token in the
operating system keyring instead.
By default, the session token is stored in the operating system keyring on
macOS and Windows and a plain text file on Linux. Use the --use-keyring flag
or CODER_USE_KEYRING environment variable to change the storage mechanism.
OPTIONS:
--first-user-email string, $CODER_FIRST_USER_EMAIL
+19
View File
@@ -0,0 +1,19 @@
coder v0.0.0-devel
USAGE:
coder task
Manage tasks
Aliases: tasks
SUBCOMMANDS:
create Create a task
delete Delete tasks
list List tasks
logs Show a task's logs
send Send input to a task
status Show the status of a task.
———
Run `coder --help` for a list of global options.
+51
View File
@@ -0,0 +1,51 @@
coder v0.0.0-devel
USAGE:
coder task create [flags] [input]
Create a task
- Create a task with direct input:
$ coder task create "Add authentication to the user service"
- Create a task with stdin input:
$ echo "Add authentication to the user service" | coder task create
- Create a task with a specific name:
$ coder task create --name task1 "Add authentication to the user service"
- Create a task from a specific template / preset:
$ coder task create --template backend-dev --preset "My Preset" "Add
authentication to the user service"
- Create a task for another user (requires appropriate permissions):
$ coder task create --owner user@example.com "Add authentication to the
user service"
OPTIONS:
-O, --org string, $CODER_ORGANIZATION
Select which organization (uuid or name) to use.
--name string
Specify the name of the task. If you do not specify one, a name will
be generated for you.
--owner string (default: me)
Specify the owner of the task. Defaults to the current user.
--preset string, $CODER_TASK_PRESET_NAME (default: none)
-q, --quiet bool
Only display the created task's ID.
--stdin bool
Reads from stdin for the task input.
--template string, $CODER_TASK_TEMPLATE_NAME
--template-version string, $CODER_TASK_TEMPLATE_VERSION
———
Run `coder --help` for a list of global options.
+27
View File
@@ -0,0 +1,27 @@
coder v0.0.0-devel
USAGE:
coder task delete [flags] <task> [<task> ...]
Delete tasks
Aliases: rm
- Delete a single task.:
$ $ coder task delete task1
- Delete multiple tasks.:
$ $ coder task delete task1 task2 task3
- Delete a task without confirmation.:
$ $ coder task delete task4 --yes
OPTIONS:
-y, --yes bool
Bypass prompts.
———
Run `coder --help` for a list of global options.
+50
View File
@@ -0,0 +1,50 @@
coder v0.0.0-devel
USAGE:
coder task list [flags]
List tasks
Aliases: ls
- List tasks for the current user.:
$ coder task list
- List tasks for a specific user.:
$ coder task list --user someone-else
- List all tasks you can view.:
$ coder task list --all
- List all your running tasks.:
$ coder task list --status running
- As above, but only show IDs.:
$ coder task list --status running --quiet
OPTIONS:
-a, --all bool (default: false)
List tasks for all users you can view.
-c, --column [id|organization id|owner id|owner name|owner avatar url|name|display name|template id|template version id|template name|template display name|template icon|workspace id|workspace name|workspace status|workspace build number|workspace agent id|workspace agent lifecycle|workspace agent health|workspace app id|initial prompt|status|state|message|created at|updated at|state changed] (default: name,status,state,state changed,message)
Columns to display in table output.
-o, --output table|json (default: table)
Output format.
-q, --quiet bool (default: false)
Only display task IDs.
--status pending|initializing|active|paused|error|unknown
Filter by task status.
--user string
List tasks for the specified user (username, "me").
———
Run `coder --help` for a list of global options.
+20
View File
@@ -0,0 +1,20 @@
coder v0.0.0-devel
USAGE:
coder task logs [flags] <task>
Show a task's logs
- Show logs for a given task.:
$ coder task logs task1
OPTIONS:
-c, --column [id|content|type|time] (default: type,content)
Columns to display in table output.
-o, --output table|json (default: table)
Output format.
———
Run `coder --help` for a list of global options.
+21
View File
@@ -0,0 +1,21 @@
coder v0.0.0-devel
USAGE:
coder task send [flags] <task> [<input> | --stdin]
Send input to a task
- Send direct input to a task.:
$ coder task send task1 "Please also add unit tests"
- Send input from stdin to a task.:
$ echo "Please also add unit tests" | coder task send task1 --stdin
OPTIONS:
--stdin bool
Reads the input from stdin.
———
Run `coder --help` for a list of global options.
+30
View File
@@ -0,0 +1,30 @@
coder v0.0.0-devel
USAGE:
coder task status [flags]
Show the status of a task.
Aliases: stat
- Show the status of a given task.:
$ coder task status task1
- Watch the status of a given task until it completes (idle or stopped).:
$ coder task status task1 --watch
OPTIONS:
-c, --column [id|organization id|owner id|owner name|owner avatar url|name|display name|template id|template version id|template name|template display name|template icon|workspace id|workspace name|workspace status|workspace build number|workspace agent id|workspace agent lifecycle|workspace agent health|workspace app id|initial prompt|status|state|message|created at|updated at|state changed|healthy] (default: state changed,status,healthy,state,message)
Columns to display in table output.
-o, --output table|json (default: table)
Output format.
--watch bool (default: false)
Watch the task status output. This will stream updates to the terminal
until the underlying workspace is stopped.
———
Run `coder --help` for a list of global options.
+73 -6
View File
@@ -36,6 +36,8 @@ import (
"github.com/coder/quartz"
)
const workspaceCacheRefreshInterval = 5 * time.Minute
// API implements the DRPC agent API interface from agent/proto. This struct is
// instantiated once per agent connection and kept alive for the duration of the
// session.
@@ -54,6 +56,8 @@ type API struct {
*SubAgentAPI
*tailnet.DRPCService
cachedWorkspaceFields *CachedWorkspaceFields
mu sync.Mutex
}
@@ -65,7 +69,7 @@ type Options struct {
WorkspaceID uuid.UUID
OrganizationID uuid.UUID
Ctx context.Context
AuthenticatedCtx context.Context
Log slog.Logger
Clock quartz.Clock
Database database.Store
@@ -92,7 +96,7 @@ type Options struct {
UpdateAgentMetricsFn func(ctx context.Context, labels prometheusmetrics.AgentMetricLabels, metrics []*agentproto.Stats_Metric)
}
func New(opts Options) *API {
func New(opts Options, workspace database.Workspace) *API {
if opts.Clock == nil {
opts.Clock = quartz.NewReal()
}
@@ -114,6 +118,13 @@ func New(opts Options) *API {
WorkspaceID: opts.WorkspaceID,
}
// Don't cache details for prebuilds, though the cached fields will eventually be updated
// by the refresh routine once the prebuild workspace is claimed.
api.cachedWorkspaceFields = &CachedWorkspaceFields{}
if !workspace.IsPrebuild() {
api.cachedWorkspaceFields.UpdateValues(workspace)
}
api.AnnouncementBannerAPI = &AnnouncementBannerAPI{
appearanceFetcher: opts.AppearanceFetcher,
}
@@ -139,6 +150,7 @@ func New(opts Options) *API {
api.StatsAPI = &StatsAPI{
AgentFn: api.agent,
Workspace: api.cachedWorkspaceFields,
Database: opts.Database,
Log: opts.Log,
StatsReporter: opts.StatsReporter,
@@ -162,10 +174,11 @@ func New(opts Options) *API {
}
api.MetadataAPI = &MetadataAPI{
AgentFn: api.agent,
Database: opts.Database,
Pubsub: opts.Pubsub,
Log: opts.Log,
AgentFn: api.agent,
Workspace: api.cachedWorkspaceFields,
Database: opts.Database,
Pubsub: opts.Pubsub,
Log: opts.Log,
}
api.LogsAPI = &LogsAPI{
@@ -205,6 +218,10 @@ func New(opts Options) *API {
Database: opts.Database,
}
// Start background cache refresh loop to handle workspace changes
// like prebuild claims where owner_id and other fields may be modified in the DB.
go api.startCacheRefreshLoop(opts.AuthenticatedCtx)
return api
}
@@ -254,6 +271,56 @@ func (a *API) agent(ctx context.Context) (database.WorkspaceAgent, error) {
return agent, nil
}
// refreshCachedWorkspace periodically updates the cached workspace fields.
// This ensures that changes like prebuild claims (which modify owner_id, name, etc.)
// are eventually reflected in the cache without requiring agent reconnection.
func (a *API) refreshCachedWorkspace(ctx context.Context) {
ws, err := a.opts.Database.GetWorkspaceByID(ctx, a.opts.WorkspaceID)
if err != nil {
a.opts.Log.Warn(ctx, "failed to refresh cached workspace fields", slog.Error(err))
a.cachedWorkspaceFields.Clear()
return
}
if ws.IsPrebuild() {
return
}
// If we still have the same values, skip the update and logging calls.
if a.cachedWorkspaceFields.identity.Equal(database.WorkspaceIdentityFromWorkspace(ws)) {
return
}
// Update fields that can change during workspace lifecycle (e.g., AutostartSchedule)
a.cachedWorkspaceFields.UpdateValues(ws)
a.opts.Log.Debug(ctx, "refreshed cached workspace fields",
slog.F("workspace_id", ws.ID),
slog.F("owner_id", ws.OwnerID),
slog.F("name", ws.Name))
}
// startCacheRefreshLoop runs a background goroutine that periodically refreshes
// the cached workspace fields. This is primarily needed to handle prebuild claims
// where the owner_id and other fields change while the agent connection persists.
func (a *API) startCacheRefreshLoop(ctx context.Context) {
// Refresh every 5 minutes. This provides a reasonable balance between:
// - Keeping cache fresh for prebuild claims and other workspace updates
// - Minimizing unnecessary database queries
ticker := a.opts.Clock.TickerFunc(ctx, workspaceCacheRefreshInterval, func() error {
a.refreshCachedWorkspace(ctx)
return nil
}, "cache_refresh")
// We need to wait on the ticker exiting.
_ = ticker.Wait()
a.opts.Log.Debug(ctx, "cache refresh loop exited, invalidating the workspace cache on agent API",
slog.F("workspace_id", a.cachedWorkspaceFields.identity.ID),
slog.F("owner_id", a.cachedWorkspaceFields.identity.OwnerUsername),
slog.F("name", a.cachedWorkspaceFields.identity.Name))
a.cachedWorkspaceFields.Clear()
}
func (a *API) publishWorkspaceUpdate(ctx context.Context, agent *database.WorkspaceAgent, kind wspubsub.WorkspaceEventKind) error {
a.opts.PublishWorkspaceUpdateFn(ctx, a.opts.OwnerID, wspubsub.WorkspaceEvent{
Kind: kind,
+52
View File
@@ -0,0 +1,52 @@
package agentapi
import (
"sync"
"github.com/coder/coder/v2/coderd/database"
)
// CachedWorkspaceFields contains workspace data that is safe to cache for the
// duration of an agent connection. These fields are used to reduce database calls
// in high-frequency operations like stats reporting and metadata updates.
// Prebuild workspaces should not be cached using this struct within the API struct,
// however some of these fields for a workspace can be updated live so there is a
// routine in the API for refreshing the workspace on a timed interval.
//
// IMPORTANT: ACL fields (GroupACL, UserACL) are NOT cached because they can be
// modified in the database and we must use fresh data for authorization checks.
type CachedWorkspaceFields struct {
lock sync.RWMutex
identity database.WorkspaceIdentity
}
func (cws *CachedWorkspaceFields) Clear() {
cws.lock.Lock()
defer cws.lock.Unlock()
cws.identity = database.WorkspaceIdentity{}
}
func (cws *CachedWorkspaceFields) UpdateValues(ws database.Workspace) {
cws.lock.Lock()
defer cws.lock.Unlock()
cws.identity.ID = ws.ID
cws.identity.OwnerID = ws.OwnerID
cws.identity.OrganizationID = ws.OrganizationID
cws.identity.TemplateID = ws.TemplateID
cws.identity.Name = ws.Name
cws.identity.OwnerUsername = ws.OwnerUsername
cws.identity.TemplateName = ws.TemplateName
cws.identity.AutostartSchedule = ws.AutostartSchedule
}
// Returns the Workspace, true, unless the workspace has not been cached (nuked or was a prebuild).
func (cws *CachedWorkspaceFields) AsWorkspaceIdentity() (database.WorkspaceIdentity, bool) {
cws.lock.RLock()
defer cws.lock.RUnlock()
// Should we be more explicit about all fields being set to be valid?
if cws.identity.Equal(database.WorkspaceIdentity{}) {
return database.WorkspaceIdentity{}, false
}
return cws.identity, true
}
+97
View File
@@ -0,0 +1,97 @@
package agentapi_test
import (
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/agentapi"
"github.com/coder/coder/v2/coderd/database"
)
func TestCacheClear(t *testing.T) {
t.Parallel()
var (
user = database.User{
ID: uuid.New(),
Username: "bill",
}
template = database.Template{
ID: uuid.New(),
Name: "tpl",
}
workspace = database.Workspace{
ID: uuid.New(),
OwnerID: user.ID,
OwnerUsername: user.Username,
TemplateID: template.ID,
Name: "xyz",
TemplateName: template.Name,
}
workspaceAsCacheFields = agentapi.CachedWorkspaceFields{}
)
workspaceAsCacheFields.UpdateValues(database.Workspace{
ID: workspace.ID,
OwnerID: workspace.OwnerID,
OwnerUsername: workspace.OwnerUsername,
TemplateID: workspace.TemplateID,
Name: workspace.Name,
TemplateName: workspace.TemplateName,
AutostartSchedule: workspace.AutostartSchedule,
},
)
emptyCws := agentapi.CachedWorkspaceFields{}
workspaceAsCacheFields.Clear()
wsi, ok := workspaceAsCacheFields.AsWorkspaceIdentity()
require.False(t, ok)
ecwsi, ok := emptyCws.AsWorkspaceIdentity()
require.False(t, ok)
require.True(t, ecwsi.Equal(wsi))
}
func TestCacheUpdate(t *testing.T) {
t.Parallel()
var (
user = database.User{
ID: uuid.New(),
Username: "bill",
}
template = database.Template{
ID: uuid.New(),
Name: "tpl",
}
workspace = database.Workspace{
ID: uuid.New(),
OwnerID: user.ID,
OwnerUsername: user.Username,
TemplateID: template.ID,
Name: "xyz",
TemplateName: template.Name,
}
workspaceAsCacheFields = agentapi.CachedWorkspaceFields{}
)
workspaceAsCacheFields.UpdateValues(database.Workspace{
ID: workspace.ID,
OwnerID: workspace.OwnerID,
OwnerUsername: workspace.OwnerUsername,
TemplateID: workspace.TemplateID,
Name: workspace.Name,
TemplateName: workspace.TemplateName,
AutostartSchedule: workspace.AutostartSchedule,
},
)
cws := agentapi.CachedWorkspaceFields{}
cws.UpdateValues(workspace)
wsi, ok := workspaceAsCacheFields.AsWorkspaceIdentity()
require.True(t, ok)
cwsi, ok := cws.AsWorkspaceIdentity()
require.True(t, ok)
require.True(t, wsi.Equal(cwsi))
}
+19 -5
View File
@@ -12,15 +12,17 @@ import (
"cdr.dev/slog"
agentproto "github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/database/pubsub"
)
type MetadataAPI struct {
AgentFn func(context.Context) (database.WorkspaceAgent, error)
Database database.Store
Pubsub pubsub.Pubsub
Log slog.Logger
AgentFn func(context.Context) (database.WorkspaceAgent, error)
Workspace *CachedWorkspaceFields
Database database.Store
Pubsub pubsub.Pubsub
Log slog.Logger
TimeNowFn func() time.Time // defaults to dbtime.Now()
}
@@ -107,7 +109,19 @@ func (a *MetadataAPI) BatchUpdateMetadata(ctx context.Context, req *agentproto.B
)
}
err = a.Database.UpdateWorkspaceAgentMetadata(ctx, dbUpdate)
// Inject RBAC object into context for dbauthz fast path, avoid having to
// call GetWorkspaceByAgentID on every metadata update.
rbacCtx := ctx
if dbws, ok := a.Workspace.AsWorkspaceIdentity(); ok {
rbacCtx, err = dbauthz.WithWorkspaceRBAC(ctx, dbws.RBACObject())
if err != nil {
// Don't error level log here, will exit the function. We want to fall back to GetWorkspaceByAgentID.
//nolint:gocritic
a.Log.Debug(ctx, "Cached workspace was present but RBAC object was invalid", slog.F("err", err))
}
}
err = a.Database.UpdateWorkspaceAgentMetadata(rbacCtx, dbUpdate)
if err != nil {
return nil, xerrors.Errorf("update workspace agent metadata in database: %w", err)
}
+435 -9
View File
@@ -2,12 +2,14 @@ package agentapi_test
import (
"context"
"database/sql"
"encoding/json"
"sync/atomic"
"testing"
"time"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -15,10 +17,14 @@ import (
agentproto "github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/coderd/agentapi"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbmock"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/database/pubsub"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/testutil"
"github.com/coder/quartz"
)
type fakePublisher struct {
@@ -84,9 +90,10 @@ func TestBatchUpdateMetadata(t *testing.T) {
AgentFn: func(context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Database: dbM,
Pubsub: pub,
Log: testutil.Logger(t),
Workspace: &agentapi.CachedWorkspaceFields{},
Database: dbM,
Pubsub: pub,
Log: testutil.Logger(t),
TimeNowFn: func() time.Time {
return now
},
@@ -169,9 +176,10 @@ func TestBatchUpdateMetadata(t *testing.T) {
AgentFn: func(context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Database: dbM,
Pubsub: pub,
Log: testutil.Logger(t),
Workspace: &agentapi.CachedWorkspaceFields{},
Database: dbM,
Pubsub: pub,
Log: testutil.Logger(t),
TimeNowFn: func() time.Time {
return now
},
@@ -238,9 +246,10 @@ func TestBatchUpdateMetadata(t *testing.T) {
AgentFn: func(context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Database: dbM,
Pubsub: pub,
Log: testutil.Logger(t),
Workspace: &agentapi.CachedWorkspaceFields{},
Database: dbM,
Pubsub: pub,
Log: testutil.Logger(t),
TimeNowFn: func() time.Time {
return now
},
@@ -272,4 +281,421 @@ func TestBatchUpdateMetadata(t *testing.T) {
Keys: []string{req.Metadata[0].Key, req.Metadata[1].Key, req.Metadata[2].Key},
}, gotEvent)
})
// Test RBAC fast path with valid RBAC object - should NOT call GetWorkspaceByAgentID
// This test verifies that when a valid RBAC object is present in context, the dbauthz layer
// uses the fast path and skips the GetWorkspaceByAgentID database call.
t.Run("WorkspaceCached_SkipsDBCall", func(t *testing.T) {
t.Parallel()
var (
ctrl = gomock.NewController(t)
dbM = dbmock.NewMockStore(ctrl)
pub = &fakePublisher{}
now = dbtime.Now()
// Set up consistent IDs that represent a valid workspace->agent relationship
workspaceID = uuid.MustParse("12345678-1234-1234-1234-123456789012")
ownerID = uuid.MustParse("87654321-4321-4321-4321-210987654321")
orgID = uuid.MustParse("11111111-1111-1111-1111-111111111111")
agentID = uuid.MustParse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
)
agent := database.WorkspaceAgent{
ID: agentID,
// In a real scenario, this agent would belong to a resource in the workspace above
}
req := &agentproto.BatchUpdateMetadataRequest{
Metadata: []*agentproto.Metadata{
{
Key: "test_key",
Result: &agentproto.WorkspaceAgentMetadata_Result{
CollectedAt: timestamppb.New(now.Add(-time.Second)),
Age: 1,
Value: "test_value",
},
},
},
}
// Expect UpdateWorkspaceAgentMetadata to be called
dbM.EXPECT().UpdateWorkspaceAgentMetadata(gomock.Any(), database.UpdateWorkspaceAgentMetadataParams{
WorkspaceAgentID: agent.ID,
Key: []string{"test_key"},
Value: []string{"test_value"},
Error: []string{""},
CollectedAt: []time.Time{now},
}).Return(nil)
// DO NOT expect GetWorkspaceByAgentID - the fast path should skip this call
// If GetWorkspaceByAgentID is called, the test will fail with "unexpected call"
// dbauthz will call Wrappers() to check for wrapped databases
dbM.EXPECT().Wrappers().Return([]string{}).AnyTimes()
// Set up dbauthz to test the actual authorization layer
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
accessControlStore := &atomic.Pointer[dbauthz.AccessControlStore]{}
var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{}
accessControlStore.Store(&acs)
api := &agentapi.MetadataAPI{
AgentFn: func(_ context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Workspace: &agentapi.CachedWorkspaceFields{},
Database: dbauthz.New(dbM, auth, testutil.Logger(t), accessControlStore),
Pubsub: pub,
Log: testutil.Logger(t),
TimeNowFn: func() time.Time {
return now
},
}
api.Workspace.UpdateValues(database.Workspace{
ID: workspaceID,
OwnerID: ownerID,
OrganizationID: orgID,
})
// Create context with system actor so authorization passes
ctx := dbauthz.AsSystemRestricted(context.Background())
resp, err := api.BatchUpdateMetadata(ctx, req)
require.NoError(t, err)
require.NotNil(t, resp)
})
// Test RBAC slow path - invalid RBAC object should fall back to GetWorkspaceByAgentID
// This test verifies that when the RBAC object has invalid IDs (nil UUIDs), the dbauthz layer
// falls back to the slow path and calls GetWorkspaceByAgentID.
t.Run("InvalidWorkspaceCached_RequiresDBCall", func(t *testing.T) {
t.Parallel()
var (
ctrl = gomock.NewController(t)
dbM = dbmock.NewMockStore(ctrl)
pub = &fakePublisher{}
now = dbtime.Now()
workspaceID = uuid.MustParse("12345678-1234-1234-1234-123456789012")
ownerID = uuid.MustParse("87654321-4321-4321-4321-210987654321")
orgID = uuid.MustParse("11111111-1111-1111-1111-111111111111")
agentID = uuid.MustParse("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb")
)
agent := database.WorkspaceAgent{
ID: agentID,
}
req := &agentproto.BatchUpdateMetadataRequest{
Metadata: []*agentproto.Metadata{
{
Key: "test_key",
Result: &agentproto.WorkspaceAgentMetadata_Result{
CollectedAt: timestamppb.New(now.Add(-time.Second)),
Age: 1,
Value: "test_value",
},
},
},
}
// EXPECT GetWorkspaceByAgentID to be called because the RBAC fast path validation fails
dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agentID).Return(database.Workspace{
ID: workspaceID,
OwnerID: ownerID,
OrganizationID: orgID,
}, nil)
// Expect UpdateWorkspaceAgentMetadata to be called after authorization
dbM.EXPECT().UpdateWorkspaceAgentMetadata(gomock.Any(), database.UpdateWorkspaceAgentMetadataParams{
WorkspaceAgentID: agent.ID,
Key: []string{"test_key"},
Value: []string{"test_value"},
Error: []string{""},
CollectedAt: []time.Time{now},
}).Return(nil)
// dbauthz will call Wrappers() to check for wrapped databases
dbM.EXPECT().Wrappers().Return([]string{}).AnyTimes()
// Set up dbauthz to test the actual authorization layer
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
accessControlStore := &atomic.Pointer[dbauthz.AccessControlStore]{}
var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{}
accessControlStore.Store(&acs)
api := &agentapi.MetadataAPI{
AgentFn: func(_ context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Workspace: &agentapi.CachedWorkspaceFields{},
Database: dbauthz.New(dbM, auth, testutil.Logger(t), accessControlStore),
Pubsub: pub,
Log: testutil.Logger(t),
TimeNowFn: func() time.Time {
return now
},
}
// Create an invalid RBAC object with nil UUIDs for owner/org
// This will fail dbauthz fast path validation and trigger GetWorkspaceByAgentID
api.Workspace.UpdateValues(database.Workspace{
ID: uuid.MustParse("cccccccc-cccc-cccc-cccc-cccccccccccc"),
OwnerID: uuid.Nil, // Invalid: fails dbauthz fast path validation
OrganizationID: uuid.Nil, // Invalid: fails dbauthz fast path validation
})
// Create context with system actor so authorization passes
ctx := dbauthz.AsSystemRestricted(context.Background())
resp, err := api.BatchUpdateMetadata(ctx, req)
require.NoError(t, err)
require.NotNil(t, resp)
})
// Test RBAC slow path - no RBAC object in context
// This test verifies that when no RBAC object is present in context, the dbauthz layer
// falls back to the slow path and calls GetWorkspaceByAgentID.
t.Run("WorkspaceNotCached_RequiresDBCall", func(t *testing.T) {
t.Parallel()
var (
ctrl = gomock.NewController(t)
dbM = dbmock.NewMockStore(ctrl)
pub = &fakePublisher{}
now = dbtime.Now()
workspaceID = uuid.MustParse("12345678-1234-1234-1234-123456789012")
ownerID = uuid.MustParse("87654321-4321-4321-4321-210987654321")
orgID = uuid.MustParse("11111111-1111-1111-1111-111111111111")
agentID = uuid.MustParse("dddddddd-dddd-dddd-dddd-dddddddddddd")
)
agent := database.WorkspaceAgent{
ID: agentID,
}
req := &agentproto.BatchUpdateMetadataRequest{
Metadata: []*agentproto.Metadata{
{
Key: "test_key",
Result: &agentproto.WorkspaceAgentMetadata_Result{
CollectedAt: timestamppb.New(now.Add(-time.Second)),
Age: 1,
Value: "test_value",
},
},
},
}
// EXPECT GetWorkspaceByAgentID to be called because no RBAC object is in context
dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agentID).Return(database.Workspace{
ID: workspaceID,
OwnerID: ownerID,
OrganizationID: orgID,
}, nil)
// Expect UpdateWorkspaceAgentMetadata to be called after authorization
dbM.EXPECT().UpdateWorkspaceAgentMetadata(gomock.Any(), database.UpdateWorkspaceAgentMetadataParams{
WorkspaceAgentID: agent.ID,
Key: []string{"test_key"},
Value: []string{"test_value"},
Error: []string{""},
CollectedAt: []time.Time{now},
}).Return(nil)
// dbauthz will call Wrappers() to check for wrapped databases
dbM.EXPECT().Wrappers().Return([]string{}).AnyTimes()
// Set up dbauthz to test the actual authorization layer
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
accessControlStore := &atomic.Pointer[dbauthz.AccessControlStore]{}
var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{}
accessControlStore.Store(&acs)
api := &agentapi.MetadataAPI{
AgentFn: func(_ context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Workspace: &agentapi.CachedWorkspaceFields{},
Database: dbauthz.New(dbM, auth, testutil.Logger(t), accessControlStore),
Pubsub: pub,
Log: testutil.Logger(t),
TimeNowFn: func() time.Time {
return now
},
}
// Create context with system actor so authorization passes
ctx := dbauthz.AsSystemRestricted(context.Background())
resp, err := api.BatchUpdateMetadata(ctx, req)
require.NoError(t, err)
require.NotNil(t, resp)
})
// Test cache refresh - AutostartSchedule updated
// This test verifies that the cache refresh mechanism actually calls GetWorkspaceByID
// and updates the cached workspace fields when the workspace is modified (e.g., autostart schedule changes).
t.Run("CacheRefreshed_AutostartScheduleUpdated", func(t *testing.T) {
t.Parallel()
var (
ctrl = gomock.NewController(t)
dbM = dbmock.NewMockStore(ctrl)
pub = &fakePublisher{}
now = dbtime.Now()
mClock = quartz.NewMock(t)
tickerTrap = mClock.Trap().TickerFunc("cache_refresh")
workspaceID = uuid.MustParse("12345678-1234-1234-1234-123456789012")
ownerID = uuid.MustParse("87654321-4321-4321-4321-210987654321")
orgID = uuid.MustParse("11111111-1111-1111-1111-111111111111")
templateID = uuid.MustParse("aaaabbbb-cccc-dddd-eeee-ffffffff0000")
agentID = uuid.MustParse("ffffffff-ffff-ffff-ffff-ffffffffffff")
)
agent := database.WorkspaceAgent{
ID: agentID,
}
// Initial workspace - has Monday-Friday 9am autostart
initialWorkspace := database.Workspace{
ID: workspaceID,
OwnerID: ownerID,
OrganizationID: orgID,
TemplateID: templateID,
Name: "my-workspace",
OwnerUsername: "testuser",
TemplateName: "test-template",
AutostartSchedule: sql.NullString{Valid: true, String: "CRON_TZ=UTC 0 9 * * 1-5"},
}
// Updated workspace - user changed autostart to 5pm and renamed workspace
updatedWorkspace := database.Workspace{
ID: workspaceID,
OwnerID: ownerID,
OrganizationID: orgID,
TemplateID: templateID,
Name: "my-workspace-renamed", // Changed!
OwnerUsername: "testuser",
TemplateName: "test-template",
AutostartSchedule: sql.NullString{Valid: true, String: "CRON_TZ=UTC 0 17 * * 1-5"}, // Changed!
DormantAt: sql.NullTime{},
}
req := &agentproto.BatchUpdateMetadataRequest{
Metadata: []*agentproto.Metadata{
{
Key: "test_key",
Result: &agentproto.WorkspaceAgentMetadata_Result{
CollectedAt: timestamppb.New(now.Add(-time.Second)),
Age: 1,
Value: "test_value",
},
},
},
}
// EXPECT GetWorkspaceByID to be called during cache refresh
// This is the key assertion - proves the refresh mechanism is working
dbM.EXPECT().GetWorkspaceByID(gomock.Any(), workspaceID).Return(updatedWorkspace, nil)
// API needs to fetch the agent when calling metadata update
dbM.EXPECT().GetWorkspaceAgentByID(gomock.Any(), agentID).Return(agent, nil)
// After refresh, metadata update should work with updated cache
dbM.EXPECT().UpdateWorkspaceAgentMetadata(gomock.Any(), gomock.Any()).DoAndReturn(
func(ctx context.Context, params database.UpdateWorkspaceAgentMetadataParams) error {
require.Equal(t, agent.ID, params.WorkspaceAgentID)
require.Equal(t, []string{"test_key"}, params.Key)
require.Equal(t, []string{"test_value"}, params.Value)
require.Equal(t, []string{""}, params.Error)
require.Len(t, params.CollectedAt, 1)
return nil
},
).AnyTimes()
// May call GetWorkspaceByAgentID if slow path is used before refresh
dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agentID).Return(updatedWorkspace, nil).AnyTimes()
// dbauthz will call Wrappers()
dbM.EXPECT().Wrappers().Return([]string{}).AnyTimes()
// Set up dbauthz
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
accessControlStore := &atomic.Pointer[dbauthz.AccessControlStore]{}
var acs dbauthz.AccessControlStore = dbauthz.AGPLTemplateAccessControlStore{}
accessControlStore.Store(&acs)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create roles with workspace permissions
userRoles := rbac.Roles([]rbac.Role{
{
Identifier: rbac.RoleMember(),
User: []rbac.Permission{
{
Negate: false,
ResourceType: rbac.ResourceWorkspace.Type,
Action: policy.WildcardSymbol,
},
},
ByOrgID: map[string]rbac.OrgPermissions{
orgID.String(): {
Member: []rbac.Permission{
{
Negate: false,
ResourceType: rbac.ResourceWorkspace.Type,
Action: policy.WildcardSymbol,
},
},
},
},
},
})
agentScope := rbac.WorkspaceAgentScope(rbac.WorkspaceAgentScopeParams{
WorkspaceID: workspaceID,
OwnerID: ownerID,
TemplateID: templateID,
VersionID: uuid.New(),
})
ctxWithActor := dbauthz.As(ctx, rbac.Subject{
Type: rbac.SubjectTypeUser,
FriendlyName: "testuser",
Email: "testuser@example.com",
ID: ownerID.String(),
Roles: userRoles,
Groups: []string{orgID.String()},
Scope: agentScope,
}.WithCachedASTValue())
// Create full API with cached workspace fields (initial state)
api := agentapi.New(agentapi.Options{
AuthenticatedCtx: ctxWithActor,
AgentID: agentID,
WorkspaceID: workspaceID,
OwnerID: ownerID,
OrganizationID: orgID,
Database: dbauthz.New(dbM, auth, testutil.Logger(t), accessControlStore),
Log: testutil.Logger(t),
Clock: mClock,
Pubsub: pub,
}, initialWorkspace) // Cache is initialized with 9am schedule and "my-workspace" name
// Wait for ticker to be set up and release it so it can fire
tickerTrap.MustWait(ctx).MustRelease(ctx)
tickerTrap.Close()
// Advance clock to trigger cache refresh and wait for it to complete
_, aw := mClock.AdvanceNext()
aw.MustWait(ctx)
// At this point, GetWorkspaceByID should have been called and cache updated
// The cache now has the 5pm schedule and "my-workspace-renamed" name
// Now call metadata update to verify the refreshed cache works
resp, err := api.MetadataAPI.BatchUpdateMetadata(ctxWithActor, req)
require.NoError(t, err)
require.NotNil(t, resp)
})
}
+14 -7
View File
@@ -17,6 +17,7 @@ import (
type StatsAPI struct {
AgentFn func(context.Context) (database.WorkspaceAgent, error)
Workspace *CachedWorkspaceFields
Database database.Store
Log slog.Logger
StatsReporter *workspacestats.Reporter
@@ -46,14 +47,21 @@ func (a *StatsAPI) UpdateStats(ctx context.Context, req *agentproto.UpdateStatsR
if err != nil {
return nil, err
}
getWorkspaceAgentByIDRow, err := a.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID)
if err != nil {
return nil, xerrors.Errorf("get workspace by agent ID %q: %w", workspaceAgent.ID, err)
// If cache is empty (prebuild or invalid), fall back to DB
var ws database.WorkspaceIdentity
var ok bool
if ws, ok = a.Workspace.AsWorkspaceIdentity(); !ok {
w, err := a.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID)
if err != nil {
return nil, xerrors.Errorf("get workspace by agent ID %q: %w", workspaceAgent.ID, err)
}
ws = database.WorkspaceIdentityFromWorkspace(w)
}
workspace := getWorkspaceAgentByIDRow
a.Log.Debug(ctx, "read stats report",
slog.F("interval", a.AgentStatsRefreshInterval),
slog.F("workspace_id", workspace.ID),
slog.F("workspace_id", ws.ID),
slog.F("payload", req),
)
@@ -70,9 +78,8 @@ func (a *StatsAPI) UpdateStats(ctx context.Context, req *agentproto.UpdateStatsR
err = a.StatsReporter.ReportAgentStats(
ctx,
a.now(),
workspace,
ws,
workspaceAgent,
getWorkspaceAgentByIDRow.TemplateName,
req.Stats,
false,
)
+26 -17
View File
@@ -52,8 +52,19 @@ func TestUpdateStates(t *testing.T) {
ID: uuid.New(),
Name: "abc",
}
workspaceAsCacheFields = agentapi.CachedWorkspaceFields{}
)
workspaceAsCacheFields.UpdateValues(database.Workspace{
ID: workspace.ID,
OwnerID: workspace.OwnerID,
OwnerUsername: workspace.OwnerUsername,
TemplateID: workspace.TemplateID,
Name: workspace.Name,
TemplateName: workspace.TemplateName,
AutostartSchedule: workspace.AutostartSchedule,
})
t.Run("OK", func(t *testing.T) {
t.Parallel()
@@ -111,7 +122,8 @@ func TestUpdateStates(t *testing.T) {
AgentFn: func(context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Database: dbM,
Workspace: &workspaceAsCacheFields,
Database: dbM,
StatsReporter: workspacestats.NewReporter(workspacestats.ReporterOptions{
Database: dbM,
Pubsub: ps,
@@ -136,9 +148,6 @@ func TestUpdateStates(t *testing.T) {
}
defer wut.Close()
// Workspace gets fetched.
dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(workspace, nil)
// We expect an activity bump because ConnectionCount > 0.
dbM.EXPECT().ActivityBumpWorkspace(gomock.Any(), database.ActivityBumpWorkspaceParams{
WorkspaceID: workspace.ID,
@@ -223,7 +232,8 @@ func TestUpdateStates(t *testing.T) {
AgentFn: func(context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Database: dbM,
Workspace: &workspaceAsCacheFields,
Database: dbM,
StatsReporter: workspacestats.NewReporter(workspacestats.ReporterOptions{
Database: dbM,
Pubsub: ps,
@@ -239,9 +249,6 @@ func TestUpdateStates(t *testing.T) {
},
}
// Workspace gets fetched.
dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(workspace, nil)
_, err := api.UpdateStats(context.Background(), req)
require.NoError(t, err)
})
@@ -260,7 +267,8 @@ func TestUpdateStates(t *testing.T) {
AgentFn: func(context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Database: dbM,
Workspace: &workspaceAsCacheFields,
Database: dbM,
StatsReporter: workspacestats.NewReporter(workspacestats.ReporterOptions{
Database: dbM,
Pubsub: ps,
@@ -333,11 +341,17 @@ func TestUpdateStates(t *testing.T) {
},
}
)
// need to overwrite the cached fields for this test, but the struct has a lock
ws := agentapi.CachedWorkspaceFields{}
ws.UpdateValues(workspace)
// ws.AutostartSchedule = workspace.AutostartSchedule
api := agentapi.StatsAPI{
AgentFn: func(context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Database: dbM,
Workspace: &ws,
Database: dbM,
StatsReporter: workspacestats.NewReporter(workspacestats.ReporterOptions{
Database: dbM,
Pubsub: ps,
@@ -362,9 +376,6 @@ func TestUpdateStates(t *testing.T) {
}
defer wut.Close()
// Workspace gets fetched.
dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(workspace, nil)
// We expect an activity bump because ConnectionCount > 0. However, the
// next autostart time will be set on the bump.
dbM.EXPECT().ActivityBumpWorkspace(gomock.Any(), database.ActivityBumpWorkspaceParams{
@@ -451,7 +462,8 @@ func TestUpdateStates(t *testing.T) {
AgentFn: func(context.Context) (database.WorkspaceAgent, error) {
return agent, nil
},
Database: dbM,
Workspace: &workspaceAsCacheFields,
Database: dbM,
StatsReporter: workspacestats.NewReporter(workspacestats.ReporterOptions{
Database: dbM,
Pubsub: ps,
@@ -478,9 +490,6 @@ func TestUpdateStates(t *testing.T) {
},
}
// Workspace gets fetched.
dbM.EXPECT().GetWorkspaceByAgentID(gomock.Any(), agent.ID).Return(workspace, nil)
// We expect an activity bump because ConnectionCount > 0.
dbM.EXPECT().ActivityBumpWorkspace(gomock.Any(), database.ActivityBumpWorkspaceParams{
WorkspaceID: workspace.ID,
+135 -72
View File
@@ -7,13 +7,15 @@ import (
"net/http"
"net/url"
"slices"
"strings"
"time"
"github.com/google/uuid"
"golang.org/x/xerrors"
"cdr.dev/slog"
"github.com/coder/coder/v2/coderd/taskname"
aiagentapi "github.com/coder/agentapi-sdk-go"
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
@@ -23,26 +25,21 @@ import (
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/coderd/searchquery"
"github.com/coder/coder/v2/coderd/taskname"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"
aiagentapi "github.com/coder/agentapi-sdk-go"
)
// @Summary Create a new AI task
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID create-task
// @ID create-a-new-ai-task
// @Security CoderSessionToken
// @Tags Experimental
// @Accept json
// @Produce json
// @Tags Tasks
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param request body codersdk.CreateTaskRequest true "Create task request"
// @Success 201 {object} codersdk.Task
// @Router /api/experimental/tasks/{user} [post]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// This endpoint creates a new task for the given user.
// @Router /tasks/{user} [post]
func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
@@ -110,18 +107,25 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
}
}
if taskName == "" {
taskName = taskname.GenerateFallback()
taskDisplayName := strings.TrimSpace(req.DisplayName)
if taskDisplayName != "" {
if len(taskDisplayName) > 64 {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Display name must be 64 characters or less.",
})
return
}
}
if anthropicAPIKey := taskname.GetAnthropicAPIKeyFromEnv(); anthropicAPIKey != "" {
anthropicModel := taskname.GetAnthropicModelFromEnv()
// Generate task name and display name if either is not provided
if taskName == "" || taskDisplayName == "" {
generatedTaskName := taskname.Generate(ctx, api.Logger, req.Input)
generatedName, err := taskname.Generate(ctx, req.Input, taskname.WithAPIKey(anthropicAPIKey), taskname.WithModel(anthropicModel))
if err != nil {
api.Logger.Error(ctx, "unable to generate task name", slog.Error(err))
} else {
taskName = generatedName
}
if taskName == "" {
taskName = generatedTaskName.Name
}
if taskDisplayName == "" {
taskDisplayName = generatedTaskName.DisplayName
}
}
@@ -214,6 +218,7 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
OrganizationID: templateVersion.OrganizationID,
OwnerID: owner.ID,
Name: taskName,
DisplayName: taskDisplayName,
WorkspaceID: uuid.NullUUID{}, // Will be set after workspace creation.
TemplateVersionID: templateVersion.ID,
TemplateParameters: []byte("{}"),
@@ -303,6 +308,7 @@ func taskFromDBTaskAndWorkspace(dbTask database.Task, ws codersdk.Workspace) cod
OwnerName: dbTask.OwnerUsername,
OwnerAvatarURL: dbTask.OwnerAvatarUrl,
Name: dbTask.Name,
DisplayName: dbTask.DisplayName,
TemplateID: ws.TemplateID,
TemplateVersionID: dbTask.TemplateVersionID,
TemplateName: ws.TemplateName,
@@ -392,16 +398,13 @@ func deriveTaskCurrentState(
}
// @Summary List AI tasks
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID list-tasks
// @ID list-ai-tasks
// @Security CoderSessionToken
// @Tags Experimental
// @Produce json
// @Tags Tasks
// @Param q query string false "Search query for filtering tasks. Supports: owner:<username/uuid/me>, organization:<org-name/uuid>, status:<status>"
// @Success 200 {object} codersdk.TasksListResponse
// @Router /api/experimental/tasks [get]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// tasksList is an experimental endpoint to list tasks.
// @Router /tasks [get]
func (api *API) tasksList(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@@ -494,20 +497,15 @@ func (api *API) convertTasks(ctx context.Context, requesterID uuid.UUID, dbTasks
return result, nil
}
// @Summary Get AI task by ID
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID get-task
// @Summary Get AI task by ID or name
// @ID get-ai-task-by-id-or-name
// @Security CoderSessionToken
// @Tags Experimental
// @Produce json
// @Tags Tasks
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID" format(uuid)
// @Param task path string true "Task ID, or task name"
// @Success 200 {object} codersdk.Task
// @Router /api/experimental/tasks/{user}/{task} [get]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// taskGet is an experimental endpoint to fetch a single AI task by ID
// (workspace ID). It returns a synthesized task response including
// prompt and status.
// @Router /tasks/{user}/{task} [get]
func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@@ -572,20 +570,14 @@ func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, taskResp)
}
// @Summary Delete AI task by ID
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID delete-task
// @Summary Delete AI task
// @ID delete-ai-task
// @Security CoderSessionToken
// @Tags Experimental
// @Tags Tasks
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID" format(uuid)
// @Success 202 "Task deletion initiated"
// @Router /api/experimental/tasks/{user}/{task} [delete]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// taskDelete is an experimental endpoint to delete a task by ID.
// It creates a delete workspace build and returns 202 Accepted if the build was
// created.
// @Param task path string true "Task ID, or task name"
// @Success 202
// @Router /tasks/{user}/{task} [delete]
func (api *API) taskDelete(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)
@@ -646,21 +638,96 @@ func (api *API) taskDelete(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusAccepted)
}
// @Summary Send input to AI task
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID send-task-input
// @Summary Update AI task input
// @ID update-ai-task-input
// @Security CoderSessionToken
// @Tags Experimental
// @Accept json
// @Tags Tasks
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID" format(uuid)
// @Param task path string true "Task ID, or task name"
// @Param request body codersdk.UpdateTaskInputRequest true "Update task input request"
// @Success 204
// @Router /tasks/{user}/{task}/input [patch]
func (api *API) taskUpdateInput(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
task = httpmw.TaskParam(r)
auditor = api.Auditor.Load()
taskResourceInfo = audit.AdditionalFields{}
)
aReq, commitAudit := audit.InitRequest[database.TaskTable](rw, &audit.RequestParams{
Audit: *auditor,
Log: api.Logger,
Request: r,
Action: database.AuditActionWrite,
AdditionalFields: taskResourceInfo,
})
defer commitAudit()
aReq.Old = task.TaskTable()
aReq.UpdateOrganizationID(task.OrganizationID)
var req codersdk.UpdateTaskInputRequest
if !httpapi.Read(ctx, rw, r, &req) {
return
}
if strings.TrimSpace(req.Input) == "" {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Task input is required.",
})
return
}
var updatedTask database.TaskTable
if err := api.Database.InTx(func(tx database.Store) error {
task, err := tx.GetTaskByID(ctx, task.ID)
if err != nil {
return httperror.NewResponseError(http.StatusInternalServerError, codersdk.Response{
Message: "Failed to fetch task.",
Detail: err.Error(),
})
}
if task.Status != database.TaskStatusPaused {
return httperror.NewResponseError(http.StatusConflict, codersdk.Response{
Message: "Unable to update task input, task must be paused.",
Detail: "Please stop the task's workspace before updating the input.",
})
}
updatedTask, err = tx.UpdateTaskPrompt(ctx, database.UpdateTaskPromptParams{
ID: task.ID,
Prompt: req.Input,
})
if err != nil {
return httperror.NewResponseError(http.StatusInternalServerError, codersdk.Response{
Message: "Failed to update task input.",
Detail: err.Error(),
})
}
return nil
}, nil); err != nil {
httperror.WriteResponseError(ctx, rw, err)
return
}
aReq.New = updatedTask
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
}
// @Summary Send input to AI task
// @ID send-input-to-ai-task
// @Security CoderSessionToken
// @Accept json
// @Tags Tasks
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID, or task name"
// @Param request body codersdk.TaskSendRequest true "Task input request"
// @Success 204 "Input sent successfully"
// @Router /api/experimental/tasks/{user}/{task}/send [post]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// taskSend submits task input to the task app by dialing the agent
// directly over the tailnet. We enforce ApplicationConnect RBAC on the
// workspace and validate the task app health.
// @Success 204
// @Router /tasks/{user}/{task}/send [post]
func (api *API) taskSend(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
task := httpmw.TaskParam(r)
@@ -721,18 +788,14 @@ func (api *API) taskSend(rw http.ResponseWriter, r *http.Request) {
}
// @Summary Get AI task logs
// @Description: EXPERIMENTAL: this endpoint is experimental and not guaranteed to be stable.
// @ID get-task-logs
// @ID get-ai-task-logs
// @Security CoderSessionToken
// @Tags Experimental
// @Produce json
// @Tags Tasks
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
// @Param task path string true "Task ID" format(uuid)
// @Param task path string true "Task ID, or task name"
// @Success 200 {object} codersdk.TaskLogsResponse
// @Router /api/experimental/tasks/{user}/{task}/logs [get]
//
// EXPERIMENTAL: This endpoint is experimental and not guaranteed to be stable.
// taskLogs reads task output by dialing the agent directly over the tailnet.
// We enforce ApplicationConnect RBAC on the workspace and validate the task app health.
// @Router /tasks/{user}/{task}/logs [get]
func (api *API) taskLogs(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
task := httpmw.TaskParam(r)
+296 -84
View File
@@ -23,6 +23,7 @@ import (
"github.com/coder/coder/v2/coderd/database/dbfake"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/notifications"
"github.com/coder/coder/v2/coderd/notifications/notificationstest"
"github.com/coder/coder/v2/coderd/util/slice"
@@ -123,8 +124,7 @@ func TestTasks(t *testing.T) {
// Create a task with a specific prompt using the new data model.
wantPrompt := "build me a web app"
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: wantPrompt,
})
@@ -140,7 +140,7 @@ func TestTasks(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
// List tasks via experimental API and verify the prompt and status mapping.
tasks, err := exp.Tasks(ctx, &codersdk.TasksFilter{Owner: codersdk.Me})
tasks, err := client.Tasks(ctx, &codersdk.TasksFilter{Owner: codersdk.Me})
require.NoError(t, err)
got, ok := slice.Find(tasks, func(t codersdk.Task) bool { return t.ID == task.ID })
@@ -163,10 +163,9 @@ func TestTasks(t *testing.T) {
anotherUser, _ = coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
template = createAITemplate(t, client, user)
wantPrompt = "review my code"
exp = codersdk.NewExperimentalClient(client)
)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: wantPrompt,
})
@@ -200,7 +199,7 @@ func TestTasks(t *testing.T) {
require.NoError(t, err)
// Fetch the task by ID via experimental API and verify fields.
updated, err := exp.TaskByID(ctx, task.ID)
updated, err := client.TaskByID(ctx, task.ID)
require.NoError(t, err)
assert.Equal(t, task.ID, updated.ID, "task ID should match")
@@ -214,19 +213,18 @@ func TestTasks(t *testing.T) {
assert.NotEmpty(t, updated.WorkspaceStatus, "task status should not be empty")
// Fetch the task by name and verify the same result
byName, err := exp.TaskByOwnerAndName(ctx, codersdk.Me, task.Name)
byName, err := client.TaskByOwnerAndName(ctx, codersdk.Me, task.Name)
require.NoError(t, err)
require.Equal(t, byName, updated)
// Another member user should not be able to fetch the task
otherClient := codersdk.NewExperimentalClient(anotherUser)
_, err = otherClient.TaskByID(ctx, task.ID)
_, err = anotherUser.TaskByID(ctx, task.ID)
require.Error(t, err, "fetching task should fail by ID for another member user")
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr)
require.Equal(t, http.StatusNotFound, sdkErr.StatusCode())
// Also test by name
_, err = otherClient.TaskByOwnerAndName(ctx, task.OwnerName, task.Name)
_, err = anotherUser.TaskByOwnerAndName(ctx, task.OwnerName, task.Name)
require.Error(t, err, "fetching task should fail by name for another member user")
require.ErrorAs(t, err, &sdkErr)
require.Equal(t, http.StatusNotFound, sdkErr.StatusCode())
@@ -235,7 +233,7 @@ func TestTasks(t *testing.T) {
coderdtest.MustTransitionWorkspace(t, client, task.WorkspaceID.UUID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop)
// Verify that the previous status still remains
updated, err = exp.TaskByID(ctx, task.ID)
updated, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
assert.NotNil(t, updated.CurrentState, "current state should not be nil")
assert.Equal(t, "all done", updated.CurrentState.Message)
@@ -247,7 +245,7 @@ func TestTasks(t *testing.T) {
// Verify that the status from the previous build has been cleared
// and replaced by the agent initialization status.
updated, err = exp.TaskByID(ctx, task.ID)
updated, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
assert.NotEqual(t, previousCurrentState, updated.CurrentState)
assert.Equal(t, codersdk.TaskStateWorking, updated.CurrentState.State)
@@ -266,8 +264,7 @@ func TestTasks(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "delete me",
})
@@ -280,7 +277,7 @@ func TestTasks(t *testing.T) {
}
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
err = exp.DeleteTask(ctx, "me", task.ID)
err = client.DeleteTask(ctx, "me", task.ID)
require.NoError(t, err, "delete task request should be accepted")
// Poll until the workspace is deleted.
@@ -302,8 +299,7 @@ func TestTasks(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitShort)
exp := codersdk.NewExperimentalClient(client)
err := exp.DeleteTask(ctx, "me", uuid.New())
err := client.DeleteTask(ctx, "me", uuid.New())
var sdkErr *codersdk.Error
require.Error(t, err, "expected an error for non-existent task")
@@ -329,8 +325,7 @@ func TestTasks(t *testing.T) {
}
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
exp := codersdk.NewExperimentalClient(client)
err := exp.DeleteTask(ctx, "me", ws.ID)
err := client.DeleteTask(ctx, "me", ws.ID)
var sdkErr *codersdk.Error
require.Error(t, err, "expected an error for non-task workspace delete via tasks endpoint")
@@ -349,8 +344,7 @@ func TestTasks(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitShort)
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "delete me not",
})
@@ -362,10 +356,9 @@ func TestTasks(t *testing.T) {
// Another regular org member without elevated permissions.
otherClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
expOther := codersdk.NewExperimentalClient(otherClient)
// Attempt to delete the owner's task as a non-owner without permissions.
err = expOther.DeleteTask(ctx, "me", task.ID)
err = otherClient.DeleteTask(ctx, "me", task.ID)
var authErr *codersdk.Error
require.Error(t, err, "expected an authorization error when deleting another user's task")
@@ -383,8 +376,7 @@ func TestTasks(t *testing.T) {
user := coderdtest.CreateFirstUser(t, client)
template := createAITemplate(t, client, user)
ctx := testutil.Context(t, testutil.WaitLong)
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "delete me",
})
@@ -403,9 +395,9 @@ func TestTasks(t *testing.T) {
// Provisionerdserver will attempt delete the related task when deleting a workspace.
// This test ensures that we can still handle the case where, for some reason, the
// task has not been marked as deleted, but the workspace has.
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err, "fetching a task should still work if its related workspace is deleted")
err = exp.DeleteTask(ctx, task.OwnerID.String(), task.ID)
err = client.DeleteTask(ctx, task.OwnerID.String(), task.ID)
require.NoError(t, err, "should be possible to delete a task with no workspace")
})
@@ -418,8 +410,7 @@ func TestTasks(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
exp := codersdk.NewExperimentalClient(client)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "delete me",
})
@@ -435,7 +426,7 @@ func TestTasks(t *testing.T) {
// When; the task workspace is deleted
coderdtest.MustTransitionWorkspace(t, client, ws.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionDelete)
// Then: the task associated with the workspace is also deleted
_, err = exp.TaskByID(ctx, task.ID)
_, err = client.TaskByID(ctx, task.ID)
require.Error(t, err, "expected an error fetching the task")
var sdkErr *codersdk.Error
require.ErrorAs(t, err, &sdkErr, "expected a codersdk.Error")
@@ -494,10 +485,9 @@ func TestTasks(t *testing.T) {
userClient, _ = coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
agentAuthToken = uuid.NewString()
template = createAITemplate(t, client, owner, withAgentToken(agentAuthToken), withSidebarURL(srv.URL))
exp = codersdk.NewExperimentalClient(userClient)
)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := userClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "send me food",
})
@@ -510,7 +500,7 @@ func TestTasks(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, ws.LatestBuild.ID)
// Fetch the task by ID via experimental API and verify fields.
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
require.NotZero(t, task.WorkspaceBuildNumber)
require.True(t, task.WorkspaceAgentID.Valid)
@@ -536,7 +526,7 @@ func TestTasks(t *testing.T) {
coderdtest.NewWorkspaceAgentWaiter(t, userClient, ws.ID).WaitFor(coderdtest.AgentsReady)
// Fetch the task by ID via experimental API and verify fields.
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
// Make the sidebar app unhealthy initially.
@@ -546,7 +536,7 @@ func TestTasks(t *testing.T) {
})
require.NoError(t, err)
err = exp.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
err = client.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
Input: "Hello, Agent!",
})
require.Error(t, err, "wanted error due to unhealthy sidebar app")
@@ -560,7 +550,7 @@ func TestTasks(t *testing.T) {
statusResponse = agentapisdk.AgentStatus("bad")
err = exp.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
err = client.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
Input: "Hello, Agent!",
})
require.Error(t, err, "wanted error due to bad status")
@@ -569,7 +559,7 @@ func TestTasks(t *testing.T) {
//nolint:tparallel // Not intended to run in parallel.
t.Run("SendOK", func(t *testing.T) {
err = exp.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
err = client.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
Input: "Hello, Agent!",
})
require.NoError(t, err, "wanted no error due to healthy sidebar app and stable status")
@@ -577,7 +567,7 @@ func TestTasks(t *testing.T) {
//nolint:tparallel // Not intended to run in parallel.
t.Run("MissingContent", func(t *testing.T) {
err = exp.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
err = client.TaskSend(ctx, "me", task.ID, codersdk.TaskSendRequest{
Input: "",
})
require.Error(t, err, "wanted error due to missing content")
@@ -595,8 +585,7 @@ func TestTasks(t *testing.T) {
_ = coderdtest.CreateFirstUser(t, client)
ctx := testutil.Context(t, testutil.WaitShort)
exp := codersdk.NewExperimentalClient(client)
err := exp.TaskSend(ctx, "me", uuid.New(), codersdk.TaskSendRequest{
err := client.TaskSend(ctx, "me", uuid.New(), codersdk.TaskSendRequest{
Input: "hi",
})
@@ -662,10 +651,9 @@ func TestTasks(t *testing.T) {
owner = coderdtest.CreateFirstUser(t, client)
agentAuthToken = uuid.NewString()
template = createAITemplate(t, client, owner, withAgentToken(agentAuthToken), withSidebarURL(srv.URL))
exp = codersdk.NewExperimentalClient(client)
)
task, err := exp.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "show logs",
})
@@ -678,7 +666,7 @@ func TestTasks(t *testing.T) {
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
// Fetch the task by ID via experimental API and verify fields.
task, err = exp.TaskByIdentifier(ctx, task.ID.String())
task, err = client.TaskByIdentifier(ctx, task.ID.String())
require.NoError(t, err)
require.NotZero(t, task.WorkspaceBuildNumber)
require.True(t, task.WorkspaceAgentID.Valid)
@@ -704,13 +692,13 @@ func TestTasks(t *testing.T) {
coderdtest.NewWorkspaceAgentWaiter(t, client, ws.ID).WaitFor(coderdtest.AgentsReady)
// Fetch the task by ID via experimental API and verify fields.
task, err = exp.TaskByID(ctx, task.ID)
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
//nolint:tparallel // Not intended to run in parallel.
t.Run("OK", func(t *testing.T) {
// Fetch logs.
resp, err := exp.TaskLogs(ctx, "me", task.ID)
resp, err := client.TaskLogs(ctx, "me", task.ID)
require.NoError(t, err)
require.Len(t, resp.Logs, 3)
assert.Equal(t, 0, resp.Logs[0].ID)
@@ -730,7 +718,7 @@ func TestTasks(t *testing.T) {
t.Run("UpstreamError", func(t *testing.T) {
shouldReturnError = true
t.Cleanup(func() { shouldReturnError = false })
_, err := exp.TaskLogs(ctx, "me", task.ID)
_, err := client.TaskLogs(ctx, "me", task.ID)
var sdkErr *codersdk.Error
require.Error(t, err)
@@ -738,6 +726,205 @@ func TestTasks(t *testing.T) {
require.Equal(t, http.StatusBadGateway, sdkErr.StatusCode())
})
})
t.Run("UpdateInput", func(t *testing.T) {
tests := []struct {
name string
disableProvisioner bool
transition database.WorkspaceTransition
cancelTransition bool
deleteTask bool
taskInput string
wantStatus codersdk.TaskStatus
wantErr string
wantErrStatusCode int
}{
{
name: "TaskStatusInitializing",
// We want to disable the provisioner so that the task
// never gets provisioned (ensuring it stays in Initializing).
disableProvisioner: true,
taskInput: "Valid prompt",
wantStatus: codersdk.TaskStatusInitializing,
wantErr: "Unable to update",
wantErrStatusCode: http.StatusConflict,
},
{
name: "TaskStatusPaused",
transition: database.WorkspaceTransitionStop,
taskInput: "Valid prompt",
wantStatus: codersdk.TaskStatusPaused,
},
{
name: "TaskStatusError",
transition: database.WorkspaceTransitionStart,
cancelTransition: true,
taskInput: "Valid prompt",
wantStatus: codersdk.TaskStatusError,
wantErr: "Unable to update",
wantErrStatusCode: http.StatusConflict,
},
{
name: "EmptyPrompt",
transition: database.WorkspaceTransitionStop,
// We want to ensure an empty prompt is rejected.
taskInput: "",
wantStatus: codersdk.TaskStatusPaused,
wantErr: "Task input is required.",
wantErrStatusCode: http.StatusBadRequest,
},
{
name: "TaskDeleted",
transition: database.WorkspaceTransitionStop,
deleteTask: true,
taskInput: "Valid prompt",
wantErr: httpapi.ResourceNotFoundResponse.Message,
wantErrStatusCode: http.StatusNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
client, provisioner := coderdtest.NewWithProvisionerCloser(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
ctx := testutil.Context(t, testutil.WaitLong)
template := createAITemplate(t, client, user)
if tt.disableProvisioner {
provisioner.Close()
}
// Given: We create a task
task, err := client.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "initial prompt",
})
require.NoError(t, err)
require.True(t, task.WorkspaceID.Valid, "task should have a workspace ID")
if !tt.disableProvisioner {
// Given: The Task is running
workspace, err := client.Workspace(ctx, task.WorkspaceID.UUID)
require.NoError(t, err)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
// Given: We transition the task's workspace
build := coderdtest.CreateWorkspaceBuild(t, client, workspace, tt.transition)
if tt.cancelTransition {
// Given: We cancel the workspace build
err := client.CancelWorkspaceBuild(ctx, build.ID, codersdk.CancelWorkspaceBuildParams{})
require.NoError(t, err)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, build.ID)
// Then: We expect it to be canceled
build, err = client.WorkspaceBuild(ctx, build.ID)
require.NoError(t, err)
require.Equal(t, codersdk.WorkspaceStatusCanceled, build.Status)
} else {
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, build.ID)
}
}
if tt.deleteTask {
err = client.DeleteTask(ctx, codersdk.Me, task.ID)
require.NoError(t, err)
} else {
// Given: Task has expected status
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
require.Equal(t, tt.wantStatus, task.Status)
}
// When: We attempt to update the task input
err = client.UpdateTaskInput(ctx, task.OwnerName, task.ID, codersdk.UpdateTaskInputRequest{
Input: tt.taskInput,
})
if tt.wantErr != "" {
require.ErrorContains(t, err, tt.wantErr)
if tt.wantErrStatusCode != 0 {
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, tt.wantErrStatusCode, apiErr.StatusCode())
}
if !tt.deleteTask {
// Then: We expect the input to **not** be updated
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
require.NotEqual(t, tt.taskInput, task.InitialPrompt)
}
} else {
require.NoError(t, err)
if !tt.deleteTask {
// Then: We expect the input to be updated
task, err = client.TaskByID(ctx, task.ID)
require.NoError(t, err)
require.Equal(t, tt.taskInput, task.InitialPrompt)
}
}
})
}
t.Run("NonExistentTask", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
ctx := testutil.Context(t, testutil.WaitShort)
// Attempt to update prompt for non-existent task
err := client.UpdateTaskInput(ctx, user.UserID.String(), uuid.New(), codersdk.UpdateTaskInputRequest{
Input: "Should fail",
})
require.Error(t, err)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
t.Run("UnauthorizedUser", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user := coderdtest.CreateFirstUser(t, client)
anotherUser, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
ctx := testutil.Context(t, testutil.WaitLong)
template := createAITemplate(t, client, user)
// Create a task as the first user
task, err := client.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "initial prompt",
})
require.NoError(t, err)
require.True(t, task.WorkspaceID.Valid)
// Wait for workspace to complete
workspace, err := client.Workspace(ctx, task.WorkspaceID.UUID)
require.NoError(t, err)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
// Stop the workspace
build := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, build.ID)
// Attempt to update prompt as another user should fail with 404 Not Found
err = anotherUser.UpdateTaskInput(ctx, task.OwnerName, task.ID, codersdk.UpdateTaskInputRequest{
Input: "Should fail - unauthorized",
})
require.Error(t, err)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})
})
}
func TestTasksCreate(t *testing.T) {
@@ -767,9 +954,7 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: taskPrompt,
})
@@ -814,10 +999,8 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
// When: We attempt to create a Task.
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: taskPrompt,
})
@@ -844,14 +1027,17 @@ func TestTasksCreate(t *testing.T) {
t.Parallel()
tests := []struct {
name string
taskName string
expectFallbackName bool
expectError string
name string
taskName string
taskDisplayName string
expectFallbackName bool
expectFallbackDisplayName bool
expectError string
}{
{
name: "ValidName",
taskName: "a-valid-task-name",
name: "ValidName",
taskName: "a-valid-task-name",
expectFallbackDisplayName: true,
},
{
name: "NotValidName",
@@ -861,8 +1047,37 @@ func TestTasksCreate(t *testing.T) {
{
name: "NoNameProvided",
taskName: "",
taskDisplayName: "A valid task display name",
expectFallbackName: true,
},
{
name: "ValidDisplayName",
taskDisplayName: "A valid task display name",
expectFallbackName: true,
},
{
name: "NotValidDisplayName",
taskDisplayName: "This is a task display name with a length greater than 64 characters.",
expectError: "Display name must be 64 characters or less.",
},
{
name: "NoDisplayNameProvided",
taskName: "a-valid-task-name",
taskDisplayName: "",
expectFallbackDisplayName: true,
},
{
name: "ValidNameAndDisplayName",
taskName: "a-valid-task-name",
taskDisplayName: "A valid task display name",
},
{
name: "NoNameAndDisplayNameProvided",
taskName: "",
taskDisplayName: "",
expectFallbackName: true,
expectFallbackDisplayName: true,
},
}
for _, tt := range tests {
@@ -870,11 +1085,10 @@ func TestTasksCreate(t *testing.T) {
t.Parallel()
var (
ctx = testutil.Context(t, testutil.WaitShort)
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
expClient = codersdk.NewExperimentalClient(client)
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
ctx = testutil.Context(t, testutil.WaitShort)
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
user = coderdtest.CreateFirstUser(t, client)
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: echo.ApplyComplete,
ProvisionPlan: []*proto.Response{
@@ -889,10 +1103,11 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
// When: We attempt to create a Task.
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "Some prompt",
Name: tt.taskName,
DisplayName: tt.taskDisplayName,
})
if tt.expectError == "" {
require.NoError(t, err)
@@ -906,8 +1121,17 @@ func TestTasksCreate(t *testing.T) {
if !tt.expectFallbackName {
require.Equal(t, tt.taskName, task.Name)
}
// Then: We expect the correct display name to have been picked.
require.NotEmpty(t, task.DisplayName)
if !tt.expectFallbackDisplayName {
require.Equal(t, tt.taskDisplayName, task.DisplayName)
}
} else {
require.ErrorContains(t, err, tt.expectError)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
require.Equal(t, apiErr.Message, tt.expectError)
}
})
}
@@ -930,10 +1154,8 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
// When: We attempt to create a Task.
_, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
_, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: taskPrompt,
})
@@ -962,10 +1184,8 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
_ = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
// When: We attempt to create a Task with an invalid template version ID.
_, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
_, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: uuid.New(),
Input: taskPrompt,
})
@@ -1001,9 +1221,7 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: taskPrompt,
})
@@ -1060,9 +1278,7 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: taskPrompt,
Name: taskName,
@@ -1096,16 +1312,14 @@ func TestTasksCreate(t *testing.T) {
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expClient := codersdk.NewExperimentalClient(client)
task1, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task1, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "First task",
Name: "task-1",
})
require.NoError(t, err)
task2, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task2, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: template.ActiveVersionID,
Input: "Second task",
Name: "task-2",
@@ -1159,11 +1373,9 @@ func TestTasksCreate(t *testing.T) {
}, template.ID)
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version2.ID)
expClient := codersdk.NewExperimentalClient(client)
// Create a task using version 2 to verify the template_version_id is
// stored correctly.
task, err := expClient.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
task, err := client.CreateTask(ctx, "me", codersdk.CreateTaskRequest{
TemplateVersionID: version2.ID,
Input: "Use version 2",
})
+396 -227
View File
@@ -136,233 +136,6 @@ const docTemplate = `{
}
}
},
"/api/experimental/tasks": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "List AI tasks",
"operationId": "list-tasks",
"parameters": [
{
"type": "string",
"description": "Search query for filtering tasks. Supports: owner:\u003cusername/uuid/me\u003e, organization:\u003corg-name/uuid\u003e, status:\u003cstatus\u003e",
"name": "q",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TasksListResponse"
}
}
}
}
},
"/api/experimental/tasks/{user}": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Create a new AI task",
"operationId": "create-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"description": "Create task request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.CreateTaskRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
}
},
"/api/experimental/tasks/{user}/{task}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Get AI task by ID",
"operationId": "get-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
},
"delete": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Delete AI task by ID",
"operationId": "delete-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"202": {
"description": "Task deletion initiated"
}
}
}
},
"/api/experimental/tasks/{user}/{task}/logs": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Get AI task logs",
"operationId": "get-task-logs",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TaskLogsResponse"
}
}
}
}
},
"/api/experimental/tasks/{user}/{task}/send": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Experimental"
],
"summary": "Send input to AI task",
"operationId": "send-task-input",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"format": "uuid",
"description": "Task ID",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.TaskSendRequest"
}
}
],
"responses": {
"204": {
"description": "Input sent successfully"
}
}
}
},
"/appearance": {
"get": {
"security": [
@@ -5679,6 +5452,294 @@ const docTemplate = `{
}
}
},
"/tasks": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Tasks"
],
"summary": "List AI tasks",
"operationId": "list-ai-tasks",
"parameters": [
{
"type": "string",
"description": "Search query for filtering tasks. Supports: owner:\u003cusername/uuid/me\u003e, organization:\u003corg-name/uuid\u003e, status:\u003cstatus\u003e",
"name": "q",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TasksListResponse"
}
}
}
}
},
"/tasks/{user}": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Tasks"
],
"summary": "Create a new AI task",
"operationId": "create-a-new-ai-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"description": "Create task request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.CreateTaskRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
}
},
"/tasks/{user}/{task}": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Tasks"
],
"summary": "Get AI task by ID or name",
"operationId": "get-ai-task-by-id-or-name",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.Task"
}
}
}
},
"delete": {
"security": [
{
"CoderSessionToken": []
}
],
"tags": [
"Tasks"
],
"summary": "Delete AI task",
"operationId": "delete-ai-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"202": {
"description": "Accepted"
}
}
}
},
"/tasks/{user}/{task}/input": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"tags": [
"Tasks"
],
"summary": "Update AI task input",
"operationId": "update-ai-task-input",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Update task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.UpdateTaskInputRequest"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/tasks/{user}/{task}/logs": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Tasks"
],
"summary": "Get AI task logs",
"operationId": "get-ai-task-logs",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.TaskLogsResponse"
}
}
}
}
},
"/tasks/{user}/{task}/send": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"tags": [
"Tasks"
],
"summary": "Send input to AI task",
"operationId": "send-input-to-ai-task",
"parameters": [
{
"type": "string",
"description": "Username, user ID, or 'me' for the authenticated user",
"name": "user",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Task ID, or task name",
"name": "task",
"in": "path",
"required": true
},
{
"description": "Task input request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.TaskSendRequest"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/templates": {
"get": {
"security": [
@@ -8326,6 +8387,84 @@ const docTemplate = `{
}
}
},
"/users/{user}/preferences": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Get user preference settings",
"operationId": "get-user-preference-settings",
"parameters": [
{
"type": "string",
"description": "User ID, name, or me",
"name": "user",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.UserPreferenceSettings"
}
}
}
},
"put": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Update user preference settings",
"operationId": "update-user-preference-settings",
"parameters": [
{
"type": "string",
"description": "User ID, name, or me",
"name": "user",
"in": "path",
"required": true
},
{
"description": "New preference settings",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.UpdateUserPreferenceSettingsRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.UserPreferenceSettings"
}
}
}
}
},
"/users/{user}/profile": {
"put": {
"security": [
@@ -13237,6 +13376,9 @@ const docTemplate = `{
"codersdk.CreateTaskRequest": {
"type": "object",
"properties": {
"display_name": {
"type": "string"
},
"input": {
"type": "string"
},
@@ -17836,6 +17978,9 @@ const docTemplate = `{
"current_state": {
"$ref": "#/definitions/codersdk.TaskStateEntry"
},
"display_name": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
@@ -19025,6 +19170,14 @@ const docTemplate = `{
}
}
},
"codersdk.UpdateTaskInputRequest": {
"type": "object",
"properties": {
"input": {
"type": "string"
}
}
},
"codersdk.UpdateTemplateACL": {
"type": "object",
"properties": {
@@ -19179,6 +19332,14 @@ const docTemplate = `{
}
}
},
"codersdk.UpdateUserPreferenceSettingsRequest": {
"type": "object",
"properties": {
"task_notification_alert_dismissed": {
"type": "boolean"
}
}
},
"codersdk.UpdateUserProfileRequest": {
"type": "object",
"required": [
@@ -19564,6 +19725,14 @@ const docTemplate = `{
}
}
},
"codersdk.UserPreferenceSettings": {
"type": "object",
"properties": {
"task_notification_alert_dismissed": {
"type": "boolean"
}
}
},
"codersdk.UserQuietHoursScheduleConfig": {
"type": "object",
"properties": {

Some files were not shown because too many files have changed in this diff Show More