sigit-code 1.5.2__tar.gz → 1.5.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. {sigit_code-1.5.2 → sigit_code-1.5.4}/.agents/AGENTS.md +56 -3
  2. {sigit_code-1.5.2 → sigit_code-1.5.4}/.github/workflows/ci.yml +1 -1
  3. sigit_code-1.5.4/.github/workflows/release-aur.yml +99 -0
  4. {sigit_code-1.5.2 → sigit_code-1.5.4}/.github/workflows/release-github.yml +111 -10
  5. sigit_code-1.5.4/.github/workflows/release-nuget.yml +315 -0
  6. sigit_code-1.5.4/.github/workflows/release-scoop.yml +140 -0
  7. sigit_code-1.5.4/.github/workflows/release-winget.yml +71 -0
  8. {sigit_code-1.5.2 → sigit_code-1.5.4}/.gitignore +1 -0
  9. {sigit_code-1.5.2 → sigit_code-1.5.4}/AGENTS.md +56 -3
  10. {sigit_code-1.5.2 → sigit_code-1.5.4}/CHANGELOG.md +54 -1
  11. {sigit_code-1.5.2 → sigit_code-1.5.4}/CLAUDE.md +56 -3
  12. {sigit_code-1.5.2 → sigit_code-1.5.4}/Cargo.lock +734 -674
  13. {sigit_code-1.5.2 → sigit_code-1.5.4}/Cargo.toml +14 -1
  14. {sigit_code-1.5.2 → sigit_code-1.5.4}/LICENSE +1 -1
  15. {sigit_code-1.5.2 → sigit_code-1.5.4}/PKG-INFO +2 -2
  16. {sigit_code-1.5.2 → sigit_code-1.5.4}/README.md +6 -0
  17. {sigit_code-1.5.2 → sigit_code-1.5.4}/npm/README.md.tmpl +1 -1
  18. {sigit_code-1.5.2 → sigit_code-1.5.4}/npm/sigit/README.md +1 -1
  19. sigit_code-1.5.4/nuget/.gitignore +5 -0
  20. sigit_code-1.5.4/nuget/sigit/Program.cs +114 -0
  21. sigit_code-1.5.4/nuget/sigit/README.md +65 -0
  22. sigit_code-1.5.4/nuget/sigit/SiGit.Code.csproj +41 -0
  23. sigit_code-1.5.4/packaging/aur/PKGBUILD.in +31 -0
  24. sigit_code-1.5.4/packaging/nfpm.yaml +39 -0
  25. {sigit_code-1.5.2 → sigit_code-1.5.4}/pypi/README.md +1 -1
  26. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/backend.rs +6 -0
  27. sigit_code-1.5.4/src/browser_auth.rs +521 -0
  28. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/chat.rs +24 -9
  29. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/main.rs +170 -18
  30. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/provider.rs +2 -1
  31. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/tools.rs +2 -3
  32. {sigit_code-1.5.2 → sigit_code-1.5.4}/tests/acp_permissions.rs +6 -0
  33. {sigit_code-1.5.2 → sigit_code-1.5.4}/.agents/skills/agent-client-protocol/SKILL.md +0 -0
  34. {sigit_code-1.5.2 → sigit_code-1.5.4}/.agents/skills/ai-assisted-coding/SKILL.md +0 -0
  35. {sigit_code-1.5.2 → sigit_code-1.5.4}/.agents/skills/branding/SKILL.md +0 -0
  36. {sigit_code-1.5.2 → sigit_code-1.5.4}/.agents/skills/run-sigit/SKILL.md +0 -0
  37. {sigit_code-1.5.2 → sigit_code-1.5.4}/.agents/skills/run-sigit/driver.mjs +0 -0
  38. {sigit_code-1.5.2 → sigit_code-1.5.4}/.agents/skills/run-sigit/tui-smoke.sh +0 -0
  39. {sigit_code-1.5.2 → sigit_code-1.5.4}/.agents/skills/sigit-code-release/SKILL.md +0 -0
  40. {sigit_code-1.5.2 → sigit_code-1.5.4}/.agents/skills/tool-calling/SKILL.md +0 -0
  41. {sigit_code-1.5.2 → sigit_code-1.5.4}/.claude/skills/agent-client-protocol/SKILL.md +0 -0
  42. {sigit_code-1.5.2 → sigit_code-1.5.4}/.claude/skills/ai-assisted-coding/SKILL.md +0 -0
  43. {sigit_code-1.5.2 → sigit_code-1.5.4}/.claude/skills/branding/SKILL.md +0 -0
  44. {sigit_code-1.5.2 → sigit_code-1.5.4}/.claude/skills/run-sigit/SKILL.md +0 -0
  45. {sigit_code-1.5.2 → sigit_code-1.5.4}/.claude/skills/run-sigit/driver.mjs +0 -0
  46. {sigit_code-1.5.2 → sigit_code-1.5.4}/.claude/skills/run-sigit/tui-smoke.sh +0 -0
  47. {sigit_code-1.5.2 → sigit_code-1.5.4}/.claude/skills/sigit-code-release/SKILL.md +0 -0
  48. {sigit_code-1.5.2 → sigit_code-1.5.4}/.claude/skills/tool-calling/SKILL.md +0 -0
  49. {sigit_code-1.5.2 → sigit_code-1.5.4}/.github/workflows/release-crates.yml +0 -0
  50. {sigit_code-1.5.2 → sigit_code-1.5.4}/.github/workflows/release-homebrew.yml +0 -0
  51. {sigit_code-1.5.2 → sigit_code-1.5.4}/.github/workflows/release-mcp-registry.yml +0 -0
  52. {sigit_code-1.5.2 → sigit_code-1.5.4}/.github/workflows/release-npm.yml +0 -0
  53. {sigit_code-1.5.2 → sigit_code-1.5.4}/.github/workflows/release-pypi.yml +0 -0
  54. {sigit_code-1.5.2 → sigit_code-1.5.4}/.nvmrc +0 -0
  55. {sigit_code-1.5.2 → sigit_code-1.5.4}/docs/hooks.md +0 -0
  56. {sigit_code-1.5.2 → sigit_code-1.5.4}/docs/mcp.md +0 -0
  57. {sigit_code-1.5.2 → sigit_code-1.5.4}/examples/settings-with-hooks.toml +0 -0
  58. {sigit_code-1.5.2 → sigit_code-1.5.4}/examples/skills/README.md +0 -0
  59. {sigit_code-1.5.2 → sigit_code-1.5.4}/examples/skills/commit-message/SKILL.md +0 -0
  60. {sigit_code-1.5.2 → sigit_code-1.5.4}/npm/package-main.json.tmpl +0 -0
  61. {sigit_code-1.5.2 → sigit_code-1.5.4}/npm/package.json.tmpl +0 -0
  62. {sigit_code-1.5.2 → sigit_code-1.5.4}/npm/scripts/render-main-package.cjs +0 -0
  63. {sigit_code-1.5.2 → sigit_code-1.5.4}/npm/scripts/render-platform-package.cjs +0 -0
  64. {sigit_code-1.5.2 → sigit_code-1.5.4}/npm/sigit/.gitignore +0 -0
  65. {sigit_code-1.5.2 → sigit_code-1.5.4}/npm/sigit/package.json +0 -0
  66. {sigit_code-1.5.2 → sigit_code-1.5.4}/npm/sigit/src/index.ts +0 -0
  67. {sigit_code-1.5.2 → sigit_code-1.5.4}/npm/sigit/tsconfig.json +0 -0
  68. {sigit_code-1.5.2 → sigit_code-1.5.4}/pypi/pyproject.toml +0 -0
  69. {sigit_code-1.5.2 → sigit_code-1.5.4}/pyproject.toml +0 -0
  70. {sigit_code-1.5.2 → sigit_code-1.5.4}/rust-toolchain.toml +0 -0
  71. {sigit_code-1.5.2 → sigit_code-1.5.4}/server.json +0 -0
  72. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/account.rs +0 -0
  73. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/commands.rs +0 -0
  74. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/credentials.rs +0 -0
  75. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/frontmatter.rs +0 -0
  76. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/headless.rs +0 -0
  77. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/hooks.rs +0 -0
  78. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/instructions.rs +0 -0
  79. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/mcp.rs +0 -0
  80. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/models.rs +0 -0
  81. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/permissions.rs +0 -0
  82. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/session_store.rs +0 -0
  83. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/settings.rs +0 -0
  84. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/setup.rs +0 -0
  85. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/skills.rs +0 -0
  86. {sigit_code-1.5.2 → sigit_code-1.5.4}/src/subagents.rs +0 -0
  87. {sigit_code-1.5.2 → sigit_code-1.5.4}/tests/headless_mode.rs +0 -0
@@ -211,6 +211,18 @@ feeds results back. Neither the loop nor ACP/TUI surfaces depend on a concrete b
211
211
  those read env vars once at init.
212
212
  - **`src/account.rs`** — siGit Code Cloud auth (`/login`, `/logout`, `/whoami`); authenticates
213
213
  against the account API and stores a session token. Performs no console I/O.
214
+ - **`src/browser_auth.rs`** — browser sign-in, the path the editor's "Sign in to siGit Code"
215
+ button takes. sigit is a public OAuth client (client id `sigit-code-cli`, no secret, PKCE
216
+ S256) against the authorization server at `$SIGIT_API_URL/oauth`. Two ways the code gets
217
+ back: a loopback listener on `127.0.0.1:0` (the registration lists `http://127.0.0.1/callback`
218
+ with no port, and RFC 8252 §7.3 matching ignores the port, so one registration covers every
219
+ run), or the out-of-band URN, where the server renders the code and the user pastes it. The
220
+ resulting token goes into `credentials` like any other, and is served by `/api/v1/user` rather
221
+ than the deprecated `/api/v1/me` — OAuth tokens are rejected there. The scopes requested are
222
+ `user:read code:agent`; `code:agent` is what reaches `chat/completions` and the official MCP
223
+ server. Surfaces: ACP `authenticate` (loopback only — nowhere to show a code), a bare `/login`
224
+ in either chat surface, and `sigit login` (which adds the paste fallback, plus `--paste` to
225
+ force it and `--password` for the old email/password prompt).
214
226
  - **`src/credentials.rs`** — local session-token store (TOML, `0600` on Unix).
215
227
  - **`src/models.rs`** — model-picker types shared across platforms.
216
228
 
@@ -249,6 +261,47 @@ mutating tools; the escape hatch for clients without permission-request support)
249
261
 
250
262
  ## Releasing
251
263
 
252
- Version lives in `Cargo.toml`. The binary is published to five registries via separate workflows
253
- (`release-crates`, `release-github`, `release-homebrew`, `release-npm`, `release-pypi`); the
254
- `npm/` and `pypi/` dirs hold the wrapper-package templates. Update `CHANGELOG.md` for releases.
264
+ Version lives in `Cargo.toml`. Update `CHANGELOG.md` for releases.
265
+
266
+ Publishing splits into two groups. **Language registries** each get their own tag-triggered
267
+ workflow: `release-crates`, `release-npm`, `release-nuget`, `release-pypi`. The `npm/`, `nuget/`,
268
+ and `pypi/` dirs hold their wrapper-package templates.
269
+
270
+ **OS package managers** all publish somewhere outside this repo and all need checksums from the
271
+ GitHub release, so `release-github` builds the binaries, attaches the assets, and then dispatches
272
+ `release-homebrew`, `release-scoop`, `release-winget`, and `release-aur`. A dispatch failure in one
273
+ is logged as a warning rather than failing the others, so an unconfigured channel does not block a
274
+ release. Their inputs live in `packaging/`.
275
+
276
+ Every release asset now carries a `.sha256` sidecar, not just the macOS Homebrew tarball. Scoop,
277
+ winget, and the AUR PKGBUILD each need one, and they consume the raw binaries rather than the
278
+ tarball. `release-github` also builds a `.deb` and `.rpm` per Linux target with nfpm
279
+ (`packaging/nfpm.yaml`), packaging the already-built binary rather than re-invoking cargo.
280
+
281
+ Three of these need credentials or a one-time manual step before they work:
282
+
283
+ - Scoop needs a `getsigit/scoop-bucket` repo and a `SCOOP_BUCKET_TOKEN` secret, mirroring the
284
+ Homebrew tap setup.
285
+ - winget needs a `WINGET_TOKEN` (PAT with `public_repo`) so `wingetcreate` can fork
286
+ `microsoft/winget-pkgs`. The workflow only handles *updates*; the first submission has to be made
287
+ by hand with `wingetcreate new`, since a package must exist before it can be updated.
288
+ - The AUR needs `AUR_USERNAME`, `AUR_EMAIL`, and `AUR_SSH_PRIVATE_KEY`. It publishes `sigit-bin`
289
+ (a prebuilt binary) so Arch users are not compiling the on-device inference stack to install a
290
+ CLI.
291
+
292
+ `[profile.release]` sets `strip = "symbols"` because binary size is a distribution constraint, not
293
+ just a nicety. See the NuGet note below.
294
+
295
+ The NuGet package (`SiGit.Code`, installed with `dotnet tool install --global SiGit.Code`) is the
296
+ odd one out among the language registries: npm and PyPI publish one artifact per platform, but a
297
+ .NET tool is a single package, so `nuget/sigit/` bundles all six binaries under
298
+ `native/<os>-<arch>/` and a small managed shim (`Program.cs`) execs the right one. That shim leaves
299
+ stdin/stdout/stderr unredirected on purpose, since siGit Code chooses TUI or ACP mode by testing
300
+ whether stdin is a TTY. It also sets `RollForward=Major`: it targets `net8.0`, and without that a
301
+ machine carrying only the .NET 10 runtime installs a tool that refuses to start.
302
+
303
+ Bundling every target means the package is large, so `release-nuget.yml` fails the pack job if the
304
+ `.nupkg` crosses nuget.org's 250 MB limit. At v1.5.1 it lands around 160 MB. If a future release
305
+ trips that check, the fix is not to drop targets but to split into RID-specific tool packages
306
+ (.NET 10's `DotnetToolRidPackage`), which ship one binary per platform the way npm already does.
307
+ That also needs a fallback package for pre-.NET-10 SDKs.
@@ -78,7 +78,7 @@ jobs:
78
78
  key: clippy-${{ matrix.target }}
79
79
 
80
80
  - name: Clippy
81
- run: cargo clippy --tests --target ${{ matrix.target }} -- -D warnings
81
+ run: cargo clippy --locked --tests --target ${{ matrix.target }} -- -D warnings
82
82
 
83
83
  test:
84
84
  name: Test — ${{ matrix.name }}
@@ -0,0 +1,99 @@
1
+ name: AUR Release
2
+
3
+ # Dispatched by release-github.yml once the release and its checksums exist.
4
+ #
5
+ # Publishes the `sigit-bin` package: a prebuilt binary rather than a source
6
+ # build, so Arch users are not compiling the whole dependency tree (and the
7
+ # on-device inference stack in particular) to install a CLI.
8
+ on:
9
+ workflow_dispatch:
10
+ inputs:
11
+ tag:
12
+ description: "Release tag (e.g. v1.5.2)"
13
+ required: true
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ env:
19
+ REPO: getsigit/sigit
20
+
21
+ jobs:
22
+ publish-aur-package:
23
+ name: Publish sigit-bin to the AUR
24
+ runs-on: ubuntu-latest
25
+
26
+ steps:
27
+ - name: Checkout
28
+ uses: actions/checkout@v6
29
+ with:
30
+ ref: ${{ github.event.inputs.tag }}
31
+
32
+ - name: Resolve tag and version
33
+ id: release
34
+ shell: bash
35
+ run: |
36
+ TAG="${{ github.event.inputs.tag }}"
37
+ VERSION="${TAG#v}"
38
+
39
+ echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
40
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
41
+
42
+ - name: Read SHA256 checksums from release
43
+ id: sha
44
+ env:
45
+ GH_TOKEN: ${{ github.token }}
46
+ shell: bash
47
+ run: |
48
+ mkdir -p artifacts
49
+
50
+ gh release download "${{ steps.release.outputs.tag }}" \
51
+ --repo "${{ env.REPO }}" \
52
+ --pattern "sigit-linux-*.sha256" \
53
+ --dir artifacts/
54
+
55
+ X86_64_SHA=$(cat artifacts/sigit-linux-amd64.sha256)
56
+ AARCH64_SHA=$(cat artifacts/sigit-linux-arm64.sha256)
57
+
58
+ echo "x86_64=${X86_64_SHA}" >> "$GITHUB_OUTPUT"
59
+ echo "aarch64=${AARCH64_SHA}" >> "$GITHUB_OUTPUT"
60
+
61
+ echo "x86_64 SHA256: ${X86_64_SHA}"
62
+ echo "aarch64 SHA256: ${AARCH64_SHA}"
63
+
64
+ - name: Render PKGBUILD
65
+ shell: bash
66
+ run: |
67
+ mkdir -p aur-build
68
+
69
+ sed \
70
+ -e "s/@VERSION@/${{ steps.release.outputs.version }}/g" \
71
+ -e "s/@SHA256_X86_64@/${{ steps.sha.outputs.x86_64 }}/g" \
72
+ -e "s/@SHA256_AARCH64@/${{ steps.sha.outputs.aarch64 }}/g" \
73
+ packaging/aur/PKGBUILD.in > aur-build/PKGBUILD
74
+
75
+ if grep -q '@[A-Z0-9_]*@' aur-build/PKGBUILD; then
76
+ echo "::error::PKGBUILD still contains unsubstituted placeholders" >&2
77
+ grep -n '@[A-Z0-9_]*@' aur-build/PKGBUILD >&2
78
+ exit 1
79
+ fi
80
+
81
+ # Catches an unbalanced quote or paren before it reaches the AUR.
82
+ bash -n aur-build/PKGBUILD
83
+
84
+ echo "Rendered PKGBUILD:"
85
+ cat aur-build/PKGBUILD
86
+
87
+ # The action runs makepkg in an Arch container to generate .SRCINFO and
88
+ # pushes over SSH. Generating .SRCINFO by hand is possible but drifts
89
+ # from the PKGBUILD the moment a field is added.
90
+ - name: Publish to the AUR
91
+ uses: KSXGitHub/github-actions-deploy-aur@v3
92
+ with:
93
+ pkgname: sigit-bin
94
+ pkgbuild: aur-build/PKGBUILD
95
+ commit_username: ${{ secrets.AUR_USERNAME }}
96
+ commit_email: ${{ secrets.AUR_EMAIL }}
97
+ ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
98
+ commit_message: "Update sigit-bin to ${{ steps.release.outputs.version }}"
99
+ updpkgsums: false
@@ -119,6 +119,87 @@ jobs:
119
119
  echo "Archive: ${ARCHIVE_NAME}"
120
120
  echo "SHA256: $(cat "${ARCHIVE_NAME}.sha256")"
121
121
 
122
+ # Debian and RPM packages are built from the binary staged above rather
123
+ # than by re-invoking cargo, so each Linux target compiles once and gets
124
+ # packaged twice.
125
+ - name: Build Linux packages
126
+ if: contains(matrix.target, 'linux')
127
+ shell: bash
128
+ env:
129
+ NFPM_VERSION: "2.43.0"
130
+ run: |
131
+ case "$(uname -m)" in
132
+ x86_64) nfpm_arch="x86_64"; export PKG_ARCH="amd64" ;;
133
+ aarch64) nfpm_arch="arm64"; export PKG_ARCH="arm64" ;;
134
+ *) echo "Unsupported Linux build host $(uname -m)" >&2; exit 1 ;;
135
+ esac
136
+
137
+ curl -sSfL \
138
+ "https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_Linux_${nfpm_arch}.tar.gz" \
139
+ | tar -xz -C /tmp nfpm
140
+
141
+ export PKG_VERSION="${RELEASE_VERSION}"
142
+ export PKG_BINARY="./release/${PROJECT_NAME}-${{ matrix.name }}"
143
+
144
+ # An empty version is worse than a hard failure here: nfpm accepts it
145
+ # and silently emits a "0.0.0~rc0" package that would be attached to
146
+ # the release. Same for a missing binary, which only surfaces as an
147
+ # opaque glob error. Fail loudly instead.
148
+ if [ -z "${PKG_VERSION}" ]; then
149
+ echo "RELEASE_VERSION is empty; refusing to build packages" >&2
150
+ exit 1
151
+ fi
152
+ if [ ! -f "${PKG_BINARY}" ]; then
153
+ echo "Staged binary ${PKG_BINARY} not found" >&2
154
+ exit 1
155
+ fi
156
+
157
+ # nfpm expands ${VAR} in most fields but NOT in contents.src, so the
158
+ # config is rendered up front and nfpm only ever sees literal values.
159
+ # envsubst comes from gettext-base, preinstalled on the Ubuntu runner
160
+ # images. The explicit variable list keeps it from clobbering any
161
+ # other shell-looking text added to the config later.
162
+ if ! command -v envsubst >/dev/null 2>&1; then
163
+ sudo apt-get update && sudo apt-get install -y gettext-base
164
+ fi
165
+
166
+ envsubst '${PKG_ARCH} ${PKG_VERSION} ${PKG_BINARY}' \
167
+ < packaging/nfpm.yaml > /tmp/nfpm.rendered.yaml
168
+
169
+ /tmp/nfpm package --config /tmp/nfpm.rendered.yaml --packager deb --target ./release/
170
+ /tmp/nfpm package --config /tmp/nfpm.rendered.yaml --packager rpm --target ./release/
171
+
172
+ # Guard against a packager silently producing nothing.
173
+ for ext in deb rpm; do
174
+ if ! compgen -G "./release/*.${ext}" >/dev/null; then
175
+ echo "No .${ext} produced" >&2
176
+ exit 1
177
+ fi
178
+ done
179
+
180
+ ls -la ./release/
181
+
182
+ # Homebrew has always had a checksum because the tap needs one. Scoop,
183
+ # winget, and the AUR PKGBUILD each need one too, and they consume the
184
+ # raw binaries rather than the macOS tarball, so every asset gets a
185
+ # sidecar. Windows runners use bash from Git for Windows, which has
186
+ # sha256sum; macOS only has shasum.
187
+ - name: Checksum release assets
188
+ shell: bash
189
+ run: |
190
+ cd release
191
+ for asset in *; do
192
+ case "${asset}" in *.sha256) continue ;; esac
193
+
194
+ if command -v sha256sum >/dev/null 2>&1; then
195
+ sha256sum "${asset}" | awk '{print $1}' > "${asset}.sha256"
196
+ else
197
+ shasum -a 256 "${asset}" | awk '{print $1}' > "${asset}.sha256"
198
+ fi
199
+
200
+ echo "${asset}: $(cat "${asset}.sha256")"
201
+ done
202
+
122
203
  - name: Upload binary artifact
123
204
  uses: actions/upload-artifact@v4
124
205
  with:
@@ -171,17 +252,37 @@ jobs:
171
252
  tag_name: ${{ steps.tag.outputs.tag }}
172
253
  files: release/*
173
254
 
174
- - name: Trigger Homebrew release
255
+ # These channels all publish somewhere outside this repo (a tap, a Scoop
256
+ # bucket, microsoft/winget-pkgs, the AUR) and every one of them reads
257
+ # checksums off the release created above, so they hang off this job
258
+ # rather than firing on the tag directly.
259
+ - name: Trigger OS package manager releases
175
260
  uses: actions/github-script@v7
176
261
  with:
177
262
  script: |
178
- await github.rest.actions.createWorkflowDispatch({
179
- owner: context.repo.owner,
180
- repo: context.repo.repo,
181
- workflow_id: 'release-homebrew.yml',
182
- ref: 'main',
183
- inputs: {
184
- tag: '${{ steps.tag.outputs.tag }}'
263
+ const tag = '${{ steps.tag.outputs.tag }}'
264
+ const workflows = [
265
+ 'release-homebrew.yml',
266
+ 'release-scoop.yml',
267
+ 'release-winget.yml',
268
+ // New AUR account registration is temporarily closed
269
+ //'release-aur.yml',
270
+ ]
271
+
272
+ for (const workflow_id of workflows) {
273
+ try {
274
+ await github.rest.actions.createWorkflowDispatch({
275
+ owner: context.repo.owner,
276
+ repo: context.repo.repo,
277
+ workflow_id,
278
+ ref: 'main',
279
+ inputs: { tag },
280
+ })
281
+ console.log(`Dispatched ${workflow_id} for tag ${tag}`)
282
+ } catch (error) {
283
+ // One packaging channel being unconfigured (a missing secret,
284
+ // a bucket repo that does not exist yet) should not take the
285
+ // rest of the fan-out down with it.
286
+ core.warning(`Failed to dispatch ${workflow_id}: ${error.message}`)
185
287
  }
186
- })
187
- console.log('Dispatched release-homebrew.yml for tag ${{ steps.tag.outputs.tag }}')
288
+ }
@@ -0,0 +1,315 @@
1
+ name: NuGet Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+ workflow_dispatch:
8
+ inputs:
9
+ tag:
10
+ description: "Release tag (e.g. v1.5.2)"
11
+ required: true
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ env:
17
+ CARGO_TERM_COLOR: always
18
+ DOTNET_VERSION: 8.0.x
19
+ NUGET_PACKAGE_ID: SiGit.Code
20
+
21
+ jobs:
22
+ build-native-binaries:
23
+ name: Build native binary (${{ matrix.build.NAME }})
24
+ runs-on: ${{ matrix.build.OS }}
25
+ strategy:
26
+ fail-fast: false
27
+ matrix:
28
+ build:
29
+ - {
30
+ NAME: linux-x64,
31
+ OS: ubuntu-latest,
32
+ TARGET: x86_64-unknown-linux-gnu,
33
+ }
34
+ - {
35
+ NAME: linux-arm64,
36
+ OS: ubuntu-24.04-arm,
37
+ TARGET: aarch64-unknown-linux-gnu,
38
+ }
39
+ - {
40
+ NAME: windows-x64,
41
+ OS: windows-2022,
42
+ TARGET: x86_64-pc-windows-msvc,
43
+ }
44
+ - {
45
+ NAME: windows-arm64,
46
+ OS: windows-2022,
47
+ TARGET: aarch64-pc-windows-msvc,
48
+ }
49
+ - { NAME: darwin-x64, OS: macos-26, TARGET: x86_64-apple-darwin }
50
+ - { NAME: darwin-arm64, OS: macos-26, TARGET: aarch64-apple-darwin }
51
+ steps:
52
+ - name: Checkout
53
+ uses: actions/checkout@v6
54
+ with:
55
+ ref: ${{ github.event.inputs.tag || github.ref }}
56
+
57
+ - name: Read Rust toolchain
58
+ shell: bash
59
+ run: |
60
+ rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
61
+ if [ -z "$rust_toolchain" ]; then
62
+ echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2
63
+ exit 1
64
+ fi
65
+ echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV"
66
+
67
+ - name: Install Rust toolchain
68
+ uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
69
+ with:
70
+ toolchain: ${{ env.RUST_TOOLCHAIN }}
71
+
72
+ - name: Install Rust target
73
+ shell: bash
74
+ run: |
75
+ rustup target add ${{ matrix.build.TARGET }} --toolchain ${{ env.RUST_TOOLCHAIN }}
76
+ rustup target list --installed
77
+
78
+ - name: Setup Rust cache
79
+ uses: Swatinem/rust-cache@v2
80
+ with:
81
+ key: nuget-${{ matrix.build.TARGET }}
82
+
83
+ - name: Build binary
84
+ shell: bash
85
+ run: cargo build --locked --release --target ${{ matrix.build.TARGET }}
86
+
87
+ - name: Prepare artifact
88
+ shell: bash
89
+ run: |
90
+ executable_name="sigit"
91
+ if [[ "${{ matrix.build.OS }}" == "windows-2022" ]]; then
92
+ executable_name="sigit.exe"
93
+ fi
94
+
95
+ mkdir -p "nuget-artifacts/native/${{ matrix.build.NAME }}"
96
+ cp "target/${{ matrix.build.TARGET }}/release/${executable_name}" \
97
+ "nuget-artifacts/native/${{ matrix.build.NAME }}/${executable_name}"
98
+
99
+ - name: Upload native artifact
100
+ uses: actions/upload-artifact@v4
101
+ with:
102
+ name: nuget-native-${{ matrix.build.NAME }}
103
+ path: nuget-artifacts/native
104
+
105
+ pack-nuget:
106
+ name: Pack NuGet .NET tool
107
+ runs-on: ubuntu-latest
108
+ needs: build-native-binaries
109
+ steps:
110
+ - name: Checkout
111
+ uses: actions/checkout@v6
112
+ with:
113
+ ref: ${{ github.event.inputs.tag || github.ref }}
114
+
115
+ - name: Set the release version
116
+ shell: bash
117
+ run: |
118
+ if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
119
+ release_version="${GITHUB_REF_NAME#v}"
120
+ else
121
+ release_version="${{ github.event.inputs.tag }}"
122
+ release_version="${release_version#v}"
123
+ fi
124
+ echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
125
+
126
+ - name: Download native artifacts
127
+ uses: actions/download-artifact@v4
128
+ with:
129
+ pattern: nuget-native-*
130
+ path: nuget/sigit/native
131
+ merge-multiple: true
132
+
133
+ - name: Setup .NET SDK
134
+ uses: actions/setup-dotnet@v4
135
+ with:
136
+ dotnet-version: ${{ env.DOTNET_VERSION }}
137
+
138
+ - name: Pack .NET tool
139
+ shell: bash
140
+ run: |
141
+ dotnet pack nuget/sigit/SiGit.Code.csproj \
142
+ --configuration Release \
143
+ --output nuget/dist \
144
+ -p:PackageVersion=${RELEASE_VERSION}
145
+
146
+ # All six binaries ship in one package, so this sits closer to nuget.org's
147
+ # 250 MB ceiling than the single-platform npm and PyPI artifacts do. Fail
148
+ # here with a clear message rather than at `dotnet nuget push`, where the
149
+ # error is just "the package file exceeds the size limit".
150
+ - name: Check package size against the NuGet limit
151
+ shell: bash
152
+ run: |
153
+ package="$(ls nuget/dist/*.nupkg | head -n 1)"
154
+ size_bytes="$(wc -c < "${package}")"
155
+ limit_bytes=$((250 * 1024 * 1024))
156
+
157
+ echo "Package: ${package}"
158
+ echo "Size: $((size_bytes / 1024 / 1024)) MiB"
159
+
160
+ if [ "${size_bytes}" -gt "${limit_bytes}" ]; then
161
+ echo "::error::${package} is over nuget.org's 250 MB package limit. Split the tool into RID-specific packages (.NET 10 DotnetToolRidPackage) instead of bundling every target." >&2
162
+ exit 1
163
+ fi
164
+
165
+ - name: Upload package artifact
166
+ uses: actions/upload-artifact@v4
167
+ with:
168
+ name: nuget-package
169
+ path: nuget/dist/*.nupkg
170
+
171
+ smoke-test:
172
+ name: Smoke test (${{ matrix.os }})
173
+ runs-on: ${{ matrix.os }}
174
+ needs: pack-nuget
175
+ strategy:
176
+ fail-fast: false
177
+ matrix:
178
+ os:
179
+ - ubuntu-latest
180
+ - windows-2022
181
+ - macos-26
182
+ steps:
183
+ - name: Checkout
184
+ uses: actions/checkout@v6
185
+ with:
186
+ ref: ${{ github.event.inputs.tag || github.ref }}
187
+
188
+ - name: Set the release version
189
+ shell: bash
190
+ run: |
191
+ if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
192
+ release_version="${GITHUB_REF_NAME#v}"
193
+ else
194
+ release_version="${{ github.event.inputs.tag }}"
195
+ release_version="${release_version#v}"
196
+ fi
197
+ echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
198
+
199
+ - name: Download package artifact
200
+ uses: actions/download-artifact@v4
201
+ with:
202
+ name: nuget-package
203
+ path: nuget/dist
204
+
205
+ - name: Setup .NET SDK
206
+ uses: actions/setup-dotnet@v4
207
+ with:
208
+ dotnet-version: ${{ env.DOTNET_VERSION }}
209
+
210
+ - name: Install tool from local package
211
+ shell: pwsh
212
+ run: |
213
+ $packageSource = (Resolve-Path "nuget/dist").Path
214
+ @"
215
+ <?xml version="1.0" encoding="utf-8"?>
216
+ <configuration>
217
+ <packageSources>
218
+ <clear />
219
+ <add key="local" value="$packageSource" />
220
+ </packageSources>
221
+ </configuration>
222
+ "@ | Set-Content -Path "nuget/NuGet.Config"
223
+
224
+ dotnet tool install `
225
+ --tool-path ".tool" `
226
+ --configfile "nuget/NuGet.Config" `
227
+ --version "$env:RELEASE_VERSION" `
228
+ "$env:NUGET_PACKAGE_ID"
229
+
230
+ - name: Verify bundled native binary
231
+ shell: pwsh
232
+ run: |
233
+ $runtimeIdentifier = if ($IsWindows) {
234
+ if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { "windows-arm64" } else { "windows-x64" }
235
+ } elseif ($IsMacOS) {
236
+ if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { "darwin-arm64" } else { "darwin-x64" }
237
+ } else {
238
+ if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { "linux-arm64" } else { "linux-x64" }
239
+ }
240
+
241
+ $binaryName = if ($IsWindows) { "sigit.exe" } else { "sigit" }
242
+ $nativeBinary = Get-ChildItem ".tool/.store" -Recurse -File | Where-Object {
243
+ $_.FullName -like "*native*${runtimeIdentifier}*${binaryName}"
244
+ } | Select-Object -First 1
245
+
246
+ if (-not $nativeBinary) {
247
+ Write-Error "The sigit native binary for ${runtimeIdentifier} was not bundled in the installed .NET tool package."
248
+ }
249
+
250
+ Write-Host "Found bundled native binary at $($nativeBinary.FullName)"
251
+
252
+ publish-nuget:
253
+ name: Publish to NuGet
254
+ runs-on: ubuntu-latest
255
+ needs: smoke-test
256
+ # Trusted Publishing exchanges a GitHub OIDC token for a short-lived NuGet
257
+ # API key, so no long-lived NUGET_API_KEY secret is stored. Requires the
258
+ # `sigit-nuget` trusted publisher policy on nuget.org (owner getsigit, repo
259
+ # sigit, workflow release-nuget.yml).
260
+ permissions:
261
+ contents: read
262
+ id-token: write
263
+ steps:
264
+ - name: Set the release version
265
+ shell: bash
266
+ run: |
267
+ if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
268
+ release_version="${GITHUB_REF_NAME#v}"
269
+ else
270
+ release_version="${{ github.event.inputs.tag }}"
271
+ release_version="${release_version#v}"
272
+ fi
273
+ echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV"
274
+
275
+ - name: Download package artifact
276
+ uses: actions/download-artifact@v4
277
+ with:
278
+ name: nuget-package
279
+ path: nuget/dist
280
+
281
+ - name: Setup .NET SDK
282
+ uses: actions/setup-dotnet@v4
283
+ with:
284
+ dotnet-version: ${{ env.DOTNET_VERSION }}
285
+
286
+ - name: Check whether release already exists on NuGet
287
+ id: nuget-check
288
+ shell: bash
289
+ run: |
290
+ package_index_url="https://api.nuget.org/v3-flatcontainer/sigit.code/index.json"
291
+ if curl -fsS "${package_index_url}" | grep -F "\"${RELEASE_VERSION}\"" >/dev/null; then
292
+ echo "${NUGET_PACKAGE_ID} ${RELEASE_VERSION} already exists on NuGet, skipping publish"
293
+ echo "exists=true" >> "$GITHUB_OUTPUT"
294
+ else
295
+ echo "exists=false" >> "$GITHUB_OUTPUT"
296
+ fi
297
+
298
+ # Exchange the workflow's OIDC token for a short-lived NuGet API key. The
299
+ # key is written to the environment for the subsequent `dotnet nuget push`
300
+ # via the NUGET_API_KEY variable this action sets.
301
+ - name: NuGet trusted-publishing login
302
+ if: steps.nuget-check.outputs.exists != 'true'
303
+ uses: NuGet/login@v1
304
+ id: nuget-login
305
+ with:
306
+ user: setoelkahfi
307
+
308
+ - name: Publish package
309
+ if: steps.nuget-check.outputs.exists != 'true'
310
+ shell: bash
311
+ run: |
312
+ dotnet nuget push nuget/dist/*.nupkg \
313
+ --api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}" \
314
+ --source https://api.nuget.org/v3/index.json \
315
+ --skip-duplicate