Skip to content

Release v0.1.0 - #17

Merged
davidcreated merged 10 commits into
mainfrom
develop
Jul 24, 2026
Merged

Release v0.1.0#17
davidcreated merged 10 commits into
mainfrom
develop

Conversation

@davidcreated

@davidcreated davidcreated commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Promote develop to main for the v0.1.0 release.

Includes the full pipeline (detect, build, sign, upload across eight stacks), GoReleaser self-distribution, the rewritten README, and resilient brew/scoop upload. After this merges and HOMEBREW_TAP_TOKEN is set, tagging v0.1.0 publishes binaries, the GitHub release, and the Homebrew/Scoop taps.

Summary by CodeRabbit

  • New Features

    • Added automatic detection and guided build workflows for Go and web/Node projects, including common frameworks and monorepos.
    • Added anvil upload with dry-run previews and support for iOS, Android, and npm publishing.
    • Added automated release packaging for Linux, macOS, and Windows, including Homebrew and Scoop distribution.
  • Documentation

    • Updated installation, usage, supported-stack, release, and roadmap documentation.
    • Added changelog details for Go and web/Node support.

Add internal/detect with marker-file detectors for Flutter (vs pure Dart; app/module/plugin), React Native (bare, Expo managed, Expo prebuild), native Android (app vs library, KMP flag), and native iOS (Xcode/SPM/Podfile). A depth-limited prune-on-detect scanner with skip lists and a containment sweep attributes android/ios folders to their Flutter or RN parent and surfaces each monorepo project once. Add the Cobra-based anvil detect command (table and --json, with --path and --depth). 16 fixture-tree tests cover the edge cases (node_modules exclusion, monorepo, KMP, plugin example pruning). Also apply the no-emojis, no-dash-connectors, minimal-comments style across docs and record it in CLAUDE.md.
feat(detect): stack detection engine and anvil detect
* feat(build): guided build lifecycle and anvil build

Add the driver contract (internal/driver) and drivers for Flutter, React Native, native Android, native iOS, Swift (SPM), and Kotlin/JVM, with --flavor threaded into build and test steps. Add the runner (internal/pipeline) that streams combined output, captures exit codes, classifies results, collects artifacts, and fail-fasts. Add internal/tui with a Bubble Tea view and a plain non-TTY renderer, and the anvil build command (--path/--target/--flavor/--release/--dry-run/--plain). Refine detection so Gradle is not always Android and Package.swift is Swift, adding Swift and Kotlin stacks. Tests cover driver steps, the runner via a real subprocess, and the plain renderer.

* fix: remove unused dirExists helper (staticcheck U1000)
Add a Sign phase and the anvil sign command plus anvil build --sign. internal/sign generates a PKCS12 keystore with keytool, writes key.properties, wires Gradle signingConfigs, writes an iOS ExportOptions.plist, and gitignores the secrets. iOS sign steps live on the Flutter (flutter build ipa), React Native, and native iOS (xcodebuild archive + exportArchive) drivers; Android signs at build time via the wired Gradle config. Passwords come from prompts (huh) or environment, never the repo, and --dry-run makes no changes. Tests cover live keystore generation (keytool), Gradle wiring idempotence, gitignore, ExportOptions, and step argv.
Add internal/upload with an Uploader interface and three targets: iOS via xcrun altool (App Store Connect/TestFlight), Android via the Google Play Publisher API (insert edit, upload bundle, assign track, commit) using a service-account JWT, and npm via npm publish. Credentials resolve from flag, env, or a base64 env decoded to a 0600 temp file, and are refused if they live inside the repo. anvil upload is a dry run unless --yes. Add GoReleaser (.goreleaser.yaml) and a tag-triggered release workflow that build cross-platform binaries and publish a GitHub release plus Homebrew cask and Scoop manifests. Tests cover credential resolution, the in-repo refusal, and each uploader's Validate/Describe.
Add Go and web/Node as detectors and drivers, reusing the existing contract with no new dependencies. Go: go.mod detection (app vs library via a main-package scan); go mod download, go vet + gofmt -l (Analyze classified as failed on non-empty output), go test, go build. Web/Node: framework detection (Next, Nuxt, SvelteKit, Angular, Vite, CRA, Vue, Svelte, Astro, Remix, Gatsby); deps by lockfile (with Yarn Berry --immutable), lint, test (jest/vitest/script with CI=true), build via the package.json script. Monorepo roots (workspaces, pnpm-workspace.yaml, lerna, nx) are descended into rather than claimed, and a single-package Turbo repo is a leaf. Detector order appends Go then Web (Web last, most permissive).
Rewrite the README for the released tool (eight stacks, full detect/build/sign/upload pipeline, install via binary or go install, usage and flags). Make Homebrew and Scoop upload skip gracefully when HOMEBREW_TAP_TOKEN is absent, so the release ships binaries and the GitHub release without a token and auto-enables tap publishing once the secret is set.
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 07c0ed92-5d4b-494b-86bb-f913234c8864

📥 Commits

Reviewing files that changed from the base of the PR and between 99035c4 and cf7e675.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (24)
  • .github/workflows/release.yml
  • .goreleaser.yaml
  • CHANGELOG.md
  • README.md
  • cmd/upload.go
  • docs/ROADMAP.md
  • go.mod
  • internal/detect/detect.go
  • internal/detect/go.go
  • internal/detect/goweb_test.go
  • internal/detect/reactnative.go
  • internal/detect/web.go
  • internal/driver/go.go
  • internal/driver/goweb_test.go
  • internal/driver/reactnative.go
  • internal/driver/registry.go
  • internal/driver/web.go
  • internal/upload/android.go
  • internal/upload/creds.go
  • internal/upload/ios.go
  • internal/upload/npm.go
  • internal/upload/upload.go
  • internal/upload/upload_test.go
  • tasks/todo.md

📝 Walkthrough

Walkthrough

Changes

Go and web stack support

Layer / File(s) Summary
Stack detection
internal/detect/*
Adds Go module detection, web framework/tooling detection, workspace handling, and scan coverage.
Build drivers
internal/driver/*
Adds Go and web phase commands, classification, registry wiring, Yarn Berry installation handling, and tests.
Documentation and dependencies
CHANGELOG.md, docs/ROADMAP.md, tasks/todo.md, go.mod
Documents breadth progress and adds dependencies used by upload integrations.

Artifact upload command

Layer / File(s) Summary
Uploader contract and credentials
internal/upload/upload.go, internal/upload/creds.go
Defines platform upload interfaces and resolves credentials from paths, environment variables, or temporary files outside the repository.
Platform uploaders
internal/upload/{ios,android,npm}.go
Adds iOS, Android, and npm validation, descriptions, and execution workflows.
CLI orchestration and tests
cmd/upload.go, internal/upload/upload_test.go
Adds upload flags, platform selection, dry-run behavior, credential staging, cancellation, and tests.

Release automation

Layer / File(s) Summary
GoReleaser pipeline
.github/workflows/release.yml, .goreleaser.yaml
Builds multi-platform binaries and publishes archives, checksums, GitHub releases, Homebrew casks, and Scoop packages on version tags.
Project overview
README.md
Updates installation, usage, supported stacks, release badges, and project progress references.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as anvil upload
  participant Builder as buildUploader
  participant Uploader
  participant Store as iOS, Android, or npm target
  CLI->>Builder: choose platform and resolve credentials
  Builder->>Uploader: construct platform uploader
  CLI->>Uploader: describe dry run or execute with --yes
  Uploader->>Store: upload artifact or publish package
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the release-focused changeset and clearly states the version being released.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Comment @coderabbitai help to get the list of available commands.

@davidcreated
davidcreated merged commit c42587e into main Jul 24, 2026
10 of 11 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (2)
.goreleaser.yaml (1)

4-6: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Avoid mutating module metadata during release.

go mod tidy can rewrite go.mod and go.sum before GoReleaser builds the artifacts, so a release may be produced from dependency metadata that was not present in the tagged commit. GoReleaser runs these hooks before the build/release stages. (goreleaser.com)

Move this to pre-release CI validation, for example with go mod tidy -diff, and fail before creating the tag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.goreleaser.yaml around lines 4 - 6, Remove the mutating go mod tidy hook
from the GoReleaser before hooks so releases build from the tagged commit’s
unchanged module metadata. Add equivalent pre-release CI validation using go mod
tidy -diff, configured to fail before tag creation when go.mod or go.sum would
change.
.github/workflows/release.yml (1)

18-18: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Pin the release toolchain and action references.

stable, @v4, @v5, @v7, and ~> v2 can resolve to different code across reruns of the same tag. Pin the repository’s declared Go version, an exact GoReleaser version, and preferably full commit SHAs for the actions. GitHub documents full SHAs as the immutable action reference. (docs.github.com)

Also applies to: 21-23, 24-26

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 18, Pin the release toolchain in the
workflow: replace the floating Go version, GoReleaser version, and action tags
used by the release steps with the repository’s declared Go version, an exact
GoReleaser version, and preferably immutable full commit SHA references for each
action, including checkout and the actions on the referenced lines.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 18-20: Update the actions/checkout@v4 step to set
persist-credentials to false while preserving fetch-depth: 0, preventing the
checkout token from being stored in the repository configuration.
- Around line 21-23: Update the actions/setup-go@v5 step in the release job to
explicitly disable caching by setting its cache option to false, while
preserving the existing stable Go version configuration.

In @.goreleaser.yaml:
- Line 34: Update both tap manifest entries in the GoReleaser configuration to
use skip_upload: auto instead of the HOMEBREW_TAP_TOKEN-based template, so
prerelease tags are not uploaded while stable releases retain automatic
publishing.

In `@go.mod`:
- Line 64: Update the google.golang.org/grpc dependency entry in go.mod from
v1.82.0 to v1.82.1 or later, refresh the associated module metadata, and rerun
govulncheck to confirm the vulnerability is resolved.

In `@internal/detect/web.go`:
- Around line 82-85: Update hasWebTooling in internal/detect/web.go to ignore
npm’s default placeholder test script (“echo \"Error: no test specified\" &&
exit 1”) when determining web tooling, matching the existing web-driver
behavior. Add a bare-package test case in internal/detect/goweb_test.go covering
that script and assert it is not detected.

In `@internal/driver/reactnative.go`:
- Around line 97-108: Update isYarnBerry to parse package.json as structured
JSON and inspect only its packageManager field, rather than scanning raw
contents for yarn markers. Preserve .yarnrc.yml detection, return false for
unreadable or malformed manifests, and classify only Yarn versions newer than
Yarn 1 as Berry. Add coverage for Yarn 1, Yarn Berry, non-Yarn managers,
malformed manifests, and unrelated strings.

In `@internal/driver/web.go`:
- Around line 27-38: The Test case in internal/driver/web.go, around the
hasJest/hasVitest checks, must prioritize a valid scripts["test"] entry by
returning pmRun with CI=true before falling back to Jest or Vitest config
detection; update internal/driver/goweb_test.go around the existing test cases
to add a repository with a detected config and custom test script, asserting npm
run test and CI=true.

In `@internal/upload/creds.go`:
- Around line 27-36: The base64 credential path can create decoded secrets
inside the repository because the temporary directory is not validated. In the
relevant credential-decoding function, check dir with inside(repoRoot, dir)
before os.WriteFile; reject and remove the directory when it is repo-contained,
while preserving cleanup on all failures. Add a regression test covering TMPDIR
configured beneath the repository root.
- Around line 41-57: Update inside to canonicalize both root and path with
filepath.EvalSymlinks before computing their relative path, returning false if
resolution fails; preserve the existing boundary comparison. Add a regression
test covering an external path symlinked into repoRoot and verify it is rejected
by the credential-loading guard.

In `@internal/upload/npm.go`:
- Around line 17-26: Update NPM.Validate to verify that NPM_TOKEN or an .npmrc
contains a usable npm authentication setting rather than accepting any existing
file; inspect supported auth configuration or delegate validation to npm while
preserving successful validation for valid credentials. Add coverage for an
empty .npmrc and ensure it returns the authentication error.

In `@README.md`:
- Line 60: Update the Homebrew and Scoop distribution statement in README.md to
document HOMEBREW_TAP_TOKEN as a required prerequisite, noting that tap
artifacts may be skipped when it is absent.
- Around line 7-8: Update the README sentence describing the “one command”
workflow to match the documented CLI behavior: describe Anvil as providing the
full lifecycle without claiming a single command signs and uploads, unless the
Usage section is updated with the exact signing and upload command or flags.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Line 18: Pin the release toolchain in the workflow: replace the floating Go
version, GoReleaser version, and action tags used by the release steps with the
repository’s declared Go version, an exact GoReleaser version, and preferably
immutable full commit SHA references for each action, including checkout and the
actions on the referenced lines.

In @.goreleaser.yaml:
- Around line 4-6: Remove the mutating go mod tidy hook from the GoReleaser
before hooks so releases build from the tagged commit’s unchanged module
metadata. Add equivalent pre-release CI validation using go mod tidy -diff,
configured to fail before tag creation when go.mod or go.sum would change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 07c0ed92-5d4b-494b-86bb-f913234c8864

📥 Commits

Reviewing files that changed from the base of the PR and between 99035c4 and cf7e675.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (24)
  • .github/workflows/release.yml
  • .goreleaser.yaml
  • CHANGELOG.md
  • README.md
  • cmd/upload.go
  • docs/ROADMAP.md
  • go.mod
  • internal/detect/detect.go
  • internal/detect/go.go
  • internal/detect/goweb_test.go
  • internal/detect/reactnative.go
  • internal/detect/web.go
  • internal/driver/go.go
  • internal/driver/goweb_test.go
  • internal/driver/reactnative.go
  • internal/driver/registry.go
  • internal/driver/web.go
  • internal/upload/android.go
  • internal/upload/creds.go
  • internal/upload/ios.go
  • internal/upload/npm.go
  • internal/upload/upload.go
  • internal/upload/upload_test.go
  • tasks/todo.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 12

🧹 Nitpick comments (2)
.goreleaser.yaml (1)

4-6: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Avoid mutating module metadata during release.

go mod tidy can rewrite go.mod and go.sum before GoReleaser builds the artifacts, so a release may be produced from dependency metadata that was not present in the tagged commit. GoReleaser runs these hooks before the build/release stages. (goreleaser.com)

Move this to pre-release CI validation, for example with go mod tidy -diff, and fail before creating the tag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.goreleaser.yaml around lines 4 - 6, Remove the mutating go mod tidy hook
from the GoReleaser before hooks so releases build from the tagged commit’s
unchanged module metadata. Add equivalent pre-release CI validation using go mod
tidy -diff, configured to fail before tag creation when go.mod or go.sum would
change.
.github/workflows/release.yml (1)

18-18: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Pin the release toolchain and action references.

stable, @v4, @v5, @v7, and ~> v2 can resolve to different code across reruns of the same tag. Pin the repository’s declared Go version, an exact GoReleaser version, and preferably full commit SHAs for the actions. GitHub documents full SHAs as the immutable action reference. (docs.github.com)

Also applies to: 21-23, 24-26

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 18, Pin the release toolchain in the
workflow: replace the floating Go version, GoReleaser version, and action tags
used by the release steps with the repository’s declared Go version, an exact
GoReleaser version, and preferably immutable full commit SHA references for each
action, including checkout and the actions on the referenced lines.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Around line 18-20: Update the actions/checkout@v4 step to set
persist-credentials to false while preserving fetch-depth: 0, preventing the
checkout token from being stored in the repository configuration.
- Around line 21-23: Update the actions/setup-go@v5 step in the release job to
explicitly disable caching by setting its cache option to false, while
preserving the existing stable Go version configuration.

In @.goreleaser.yaml:
- Line 34: Update both tap manifest entries in the GoReleaser configuration to
use skip_upload: auto instead of the HOMEBREW_TAP_TOKEN-based template, so
prerelease tags are not uploaded while stable releases retain automatic
publishing.

In `@go.mod`:
- Line 64: Update the google.golang.org/grpc dependency entry in go.mod from
v1.82.0 to v1.82.1 or later, refresh the associated module metadata, and rerun
govulncheck to confirm the vulnerability is resolved.

In `@internal/detect/web.go`:
- Around line 82-85: Update hasWebTooling in internal/detect/web.go to ignore
npm’s default placeholder test script (“echo \"Error: no test specified\" &&
exit 1”) when determining web tooling, matching the existing web-driver
behavior. Add a bare-package test case in internal/detect/goweb_test.go covering
that script and assert it is not detected.

In `@internal/driver/reactnative.go`:
- Around line 97-108: Update isYarnBerry to parse package.json as structured
JSON and inspect only its packageManager field, rather than scanning raw
contents for yarn markers. Preserve .yarnrc.yml detection, return false for
unreadable or malformed manifests, and classify only Yarn versions newer than
Yarn 1 as Berry. Add coverage for Yarn 1, Yarn Berry, non-Yarn managers,
malformed manifests, and unrelated strings.

In `@internal/driver/web.go`:
- Around line 27-38: The Test case in internal/driver/web.go, around the
hasJest/hasVitest checks, must prioritize a valid scripts["test"] entry by
returning pmRun with CI=true before falling back to Jest or Vitest config
detection; update internal/driver/goweb_test.go around the existing test cases
to add a repository with a detected config and custom test script, asserting npm
run test and CI=true.

In `@internal/upload/creds.go`:
- Around line 27-36: The base64 credential path can create decoded secrets
inside the repository because the temporary directory is not validated. In the
relevant credential-decoding function, check dir with inside(repoRoot, dir)
before os.WriteFile; reject and remove the directory when it is repo-contained,
while preserving cleanup on all failures. Add a regression test covering TMPDIR
configured beneath the repository root.
- Around line 41-57: Update inside to canonicalize both root and path with
filepath.EvalSymlinks before computing their relative path, returning false if
resolution fails; preserve the existing boundary comparison. Add a regression
test covering an external path symlinked into repoRoot and verify it is rejected
by the credential-loading guard.

In `@internal/upload/npm.go`:
- Around line 17-26: Update NPM.Validate to verify that NPM_TOKEN or an .npmrc
contains a usable npm authentication setting rather than accepting any existing
file; inspect supported auth configuration or delegate validation to npm while
preserving successful validation for valid credentials. Add coverage for an
empty .npmrc and ensure it returns the authentication error.

In `@README.md`:
- Line 60: Update the Homebrew and Scoop distribution statement in README.md to
document HOMEBREW_TAP_TOKEN as a required prerequisite, noting that tap
artifacts may be skipped when it is absent.
- Around line 7-8: Update the README sentence describing the “one command”
workflow to match the documented CLI behavior: describe Anvil as providing the
full lifecycle without claiming a single command signs and uploads, unless the
Usage section is updated with the exact signing and upload command or flags.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Line 18: Pin the release toolchain in the workflow: replace the floating Go
version, GoReleaser version, and action tags used by the release steps with the
repository’s declared Go version, an exact GoReleaser version, and preferably
immutable full commit SHA references for each action, including checkout and the
actions on the referenced lines.

In @.goreleaser.yaml:
- Around line 4-6: Remove the mutating go mod tidy hook from the GoReleaser
before hooks so releases build from the tagged commit’s unchanged module
metadata. Add equivalent pre-release CI validation using go mod tidy -diff,
configured to fail before tag creation when go.mod or go.sum would change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 07c0ed92-5d4b-494b-86bb-f913234c8864

📥 Commits

Reviewing files that changed from the base of the PR and between 99035c4 and cf7e675.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (24)
  • .github/workflows/release.yml
  • .goreleaser.yaml
  • CHANGELOG.md
  • README.md
  • cmd/upload.go
  • docs/ROADMAP.md
  • go.mod
  • internal/detect/detect.go
  • internal/detect/go.go
  • internal/detect/goweb_test.go
  • internal/detect/reactnative.go
  • internal/detect/web.go
  • internal/driver/go.go
  • internal/driver/goweb_test.go
  • internal/driver/reactnative.go
  • internal/driver/registry.go
  • internal/driver/web.go
  • internal/upload/android.go
  • internal/upload/creds.go
  • internal/upload/ios.go
  • internal/upload/npm.go
  • internal/upload/upload.go
  • internal/upload/upload_test.go
  • tasks/todo.md
🛑 Comments failed to post (12)
.github/workflows/release.yml (2)

18-20: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

git ls-files .github/workflows/release.yml
wc -l .github/workflows/release.yml
cat -n .github/workflows/release.yml

Repository: openforge-oss/anvil

Length of output: 1284


Disable checkout credential persistence. actions/checkout writes its token into the repo config by default. This job already passes the tokens it needs to GoReleaser, so add persist-credentials: false to avoid leaving an extra credential in the workspace.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 18-20: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 18 - 20, Update the
actions/checkout@v4 step to set persist-credentials to false while preserving
fetch-depth: 0, preventing the checkout token from being stored in the
repository configuration.

Source: Linters/SAST tools


21-23: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== release workflow ==\n'
git ls-files .github/workflows/release.yml
echo
nl -ba .github/workflows/release.yml | sed -n '1,120p'

printf '\n== setup-go usage across repo ==\n'
rg -n "actions/setup-go@|cache:\s*false|cache:\s*true|go-version:" .github/workflows -S || true

printf '\n== repo root files relevant to Go deps ==\n'
git ls-files | rg '(^|/)(go\.mod|go\.sum)$|\.github/workflows/|(^|/)Makefile$' -n || true

Repository: openforge-oss/anvil

Length of output: 252


🌐 Web query:

actions/setup-go v5 cache default release workflow cache false documentation

💡 Result:

In the actions/setup-go action (including v5), the cache input controls the caching of Go modules and build outputs [1][2]. This feature is enabled by default [1][3]. When you set cache: false, the action disables the GitHub Actions cache for your Go modules (dependencies) and build outputs [4]. Consequently, no restore or save operations are performed for these items, and dependencies will be freshly downloaded during each workflow run [4]. It is important to note that setting cache: false does not disable all caching related to Go on the runner [4]. GitHub-hosted runners maintain a built-in "tool cache" that stores pre-installed versions of the Go runtime itself [5][4]. This toolchain caching is managed independently by GitHub Actions to optimize workflow speed and remains active regardless of the cache setting in the setup-go action [4]. Therefore, even with cache: false, the Go installation step remains fast because the binary is typically already available in the runner's tool cache [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== release workflow ==\n'
sed -n '1,120p' .github/workflows/release.yml | cat -n

printf '\n== setup-go usage across workflows ==\n'
rg -n "actions/setup-go@|cache:\s*false|cache:\s*true|go-version:" .github/workflows -S || true

Repository: openforge-oss/anvil

Length of output: 1920


🌐 Web query:

actions/setup-go cache default omitted v5 README cache input default false true

💡 Result:

In the actions/setup-go action, the cache input defaults to true [1][2]. This behavior was introduced in version 4 and persists in versions 5 and 6 [3][4][5]. Because caching is enabled by default, you do not need to explicitly set cache: true in your workflow configuration to benefit from it [1][3]; to disable caching, you must explicitly set cache: false [1][6].

Citations:


Disable setup-go caching in the release job. actions/setup-go@v5 enables cache restoration by default, and this workflow publishes release binaries. Add cache: false here unless the cache trust model is explicitly reviewed.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 21-21: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 21 - 23, Update the
actions/setup-go@v5 step in the release job to explicitly disable caching by
setting its cache option to false, while preserving the existing stable Go
version configuration.

Source: Linters/SAST tools

.goreleaser.yaml (1)

34-34: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant workflow and GoReleaser config around the referenced lines.
git ls-files '.github/workflows/release.yml' '.goreleaser.yaml'
printf '\n--- .github/workflows/release.yml ---\n'
cat -n .github/workflows/release.yml | sed -n '1,120p'
printf '\n--- .goreleaser.yaml ---\n'
cat -n .goreleaser.yaml | sed -n '1,120p'

Repository: openforge-oss/anvil

Length of output: 2953


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find any references to skip_upload and prerelease-related settings.
rg -n "skip_upload|prerelease|tap|scoop|brew" .goreleaser.yaml .github/workflows/release.yml

Repository: openforge-oss/anvil

Length of output: 736


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the GoReleaser config structure to inspect the exact fields and any templating.
ast-grep outline .goreleaser.yaml --lang yaml --view expanded

Repository: openforge-oss/anvil

Length of output: 188


🌐 Web query:

GitHub Actions tags filter glob pattern semantics plus signs character class workflow syntax tag patterns prerelease

💡 Result:

GitHub Actions uses glob-style pattern matching for its tags and tags-ignore workflow filters [1][2]. These patterns do not support full regular expression syntax, but they do support a specific set of wildcards and character classes [3]. Glob Pattern Semantics: - *: Matches zero or more characters, excluding the slash (/) character [3]. -: Matches zero or more of any character, including slashes [3]. -?: Matches any single character [1][3]. - []: Defines a character class that matches one alphanumeric character contained within the brackets [3]. Ranges within brackets are limited to [a-z], [A-Z], and [0-9] [3]. For example, [0-9] matches any digit, and [CB]at matches Cat or Bat [3]. -!: Used as a prefix to exclude specific patterns. When using the! character to exclude, you must also include at least one positive pattern for that same event to ensure the workflow is triggered [2][4]. Plus Signs (+): In GitHub Actions workflow filters, the plus sign (+) is explicitly listed as a special glob character [1][2]. If your tag name contains a literal plus sign and you need to match it, you must escape it using a backslash (+) [1][2]. Prerelease and Tag Pattern Strategy: GitHub Actions does not provide native "prerelease" or "semantic version" awareness in its filter syntax. You must define explicit glob patterns to match your desired tag structures [5]. For example, if you wish to target both standard releases and specific prerelease formats, you must list them as separate patterns [5]: on: push: tags: - v[0-9]+.[0-9]+.[0-9]+ - v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+ If complex filtering (such as true regex-based prerelease identification) is required that exceeds the capabilities of these glob patterns, the standard practice is to use a catch-all pattern (like '') and implement further logic using an 'if' condition within the workflow's jobs or steps (e.g., using 'contains(github.ref, 'release')') [5].

Citations:


🌐 Web query:

site:docs.github.com GitHub Actions tag filters glob patterns character classes plus literal

💡 Result:

GitHub Actions supports glob patterns for tag and branch filters within workflow triggers [1][2]. These filters use special characters—including *,, +,?, and!—to match multiple tag or branch names [1][3]. If a tag name contains any of these special characters and you intend to match the tag literally, you must escape each character by preceding it with a backslash () [1][2]. Regarding character classes (e.g., [a-z]), the official GitHub Actions documentation for workflow syntax filters does not list them as supported features for tag and branch glob matching [1][3]. The supported pattern syntax is defined by glob-style wildcards rather than full regex or POSIX-style character classes [1][2]. Key considerations for tag filters include: - You cannot use both tags and tags-ignore for the same event in a single workflow [1][3]. - If you use the ! character (to exclude a pattern), you must also define at least one pattern that does not use the ! character [2][3]. - If you only need to exclude tags, use the tags-ignore filter instead [2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import fnmatch

pattern = 'v[0-9]+.[0-9]+.[0-9]+*'
tags = [
    'v1.0.0',
    'v1.0.0-rc1',
    'v1.2.3',
    'v1.2.3-beta',
    'v10.20.30',
    'v1+0.0.0',
    'v1+.0+.0+',
]
for tag in tags:
    print(tag, fnmatch.fnmatchcase(tag, pattern))
PY

Repository: openforge-oss/anvil

Length of output: 264


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import fnmatch

pattern = 'v[0-9]+.[0-9]+.[0-9]+*'
tags = ['v1.0.0', 'v1.0.0-rc1', 'v10.20.30', 'v1+0.0.0', 'v1.0.0+meta']
print("pattern:", pattern)
for tag in tags:
    print(f"{tag:14} -> {fnmatch.fnmatchcase(tag, pattern)}")
PY

Repository: openforge-oss/anvil

Length of output: 309


🌐 Web query:

site:docs.github.com workflow syntax tags patterns character classes plus sign escape GitHub Actions

💡 Result:

In GitHub Actions, the handling of special characters, glob patterns, and escaping depends on whether you are using them in configuration filters (like branches or paths) or within expressions (${{ }}). Glob Patterns (Filters) When using filters such as branches, branches-ignore, tags, tags-ignore, paths, and paths-ignore, GitHub Actions uses glob pattern matching [1][2]. These patterns support special characters including *,, +,?, and! [1][2]. - Escaping: If a branch or path name literally contains any of these special characters, you must escape each one individually using a backslash () to ensure they are treated as literal characters rather than pattern operators [1][2][3]. Expression Syntax When working with expressions (inside ${{ }}), specific characters have reserved meanings, such as! for logical NOT [2][4]. - Reserved Characters: Because! is a reserved notation in YAML, if an expression starts with!, you must use the ${{ }} syntax or escape the expression by wrapping it in single quotes (''), double quotes (""), or parentheses [2][4]. - Literal Quotes: To use a literal single quote within a string inside an expression, you must escape it by adding an additional single quote (e.g., '') [4]. Shell-Specific Escaping When passing values—such as secrets or variables—to shell commands, the escaping requirements are dictated by the specific shell being used (e.g., Bash, PowerShell, or Cmd.exe) [5]. To avoid issues with special characters in secrets: - Always use environment variables to pass data to scripts [5]. - Quote these variables appropriately for the target shell (e.g., "$MY_VAR" in Bash, "$env:MY_VAR" in PowerShell, or "%MY_VAR%" in Cmd.exe) [5]. Character Classes and Regex GitHub Actions workflow syntax for filters relies on glob patterns rather than full regular expressions [4]. Consequently, traditional regex features like character classes (e.g., [a-z]) are generally not supported in these specific filter fields [4]. For complex logic requiring regex, you should use an if conditional with the contains or matches functions (or similar logic) within the expression syntax, where standard string evaluation rules apply [4][6].

Citations:


🌐 Web query:

site:docs.github.com "plus" "workflow syntax" tags glob patterns one or more

💡 Result:

In the context of GitHub Actions workflow syntax, the terms "plus" (+), glob patterns, and tags are used in the following ways: Glob Patterns and Tags GitHub Actions workflows use glob patterns to filter events, such as which branches or tags trigger a workflow execution [1][2]. When configuring filters under the on.push or on.pull_request events, the branches, branches-ignore, tags, and tags-ignore keywords accept glob patterns to match multiple names [1][3]. Supported glob characters include: * * (wildcard for single-segment matching) [1][4] * (recursive matching for multiple segments) [1][4] *? (matches a single character) [1] * + (matches one or more occurrences of the preceding character) [1] *! (used for negation/exclusion) [1] If a branch or tag name literally contains any of these special characters, you must escape each character by preceding it with a backslash () [1][3]. Plus Symbol (+) in Workflow Configuration The plus symbol (+) is specifically used in certain workflow contexts to denote an additive operation rather than a filter [5]. For example, when configuring custom settings for CodeQL analysis, if you want to run a combined set of queries or packs—instead of replacing the default set with your custom configuration—you can prefix the value in the workflow with the + symbol [5]. This signals that the additional items should be added to the default configuration rather than overriding it [5]. Additional Context These syntax rules are part of the YAML-based workflow configuration stored in the.github/workflows directory of a repository [6][7]. While glob patterns are fundamental to event filtering across many GitHub features (including Dependabot configuration) [4], the use of the + symbol is context-specific, primarily serving as an additive operator in advanced configuration files like those for code scanning [5].

Citations:


🌐 Web query:

site:docs.github.com GitHub Actions glob plus sign one or more pattern examples tags

💡 Result:

In GitHub Actions workflow filters, such as those used in branches, branches-ignore, tags, and tags-ignore, the plus sign (+) is a supported glob pattern character [1][2]. It is used to match one or more occurrences of the preceding character or group, depending on the underlying glob implementation (often matching the behavior of extended globs where + acts as a wildcard for "one or more" of the preceding pattern) [1][2]. Key points regarding the use of special characters in these filters: - Pattern Support: Characters like *, , `+`, `?`, and `!` are recognized as special pattern-matching characters [1][2]. - Literal Matches: If your branch or tag name contains a plus sign or any other special glob character that you wish to match literally, you must escape it using a backslash (\) [1][2]. For example, to match a tag named `v1+patch`, you would define the pattern as `v1\+patch`. - Combined Usage: You can combine these characters. For example, `release+*` would match tags starting with "release+" followed by one or more characters (assuming standard glob behavior for the `+` quantifier) [1][3]. When using these patterns to control workflow triggers: - You cannot use both the `branches` and `branches-ignore` keywords for the same event in a single workflow [2]. - To include and exclude patterns simultaneously, use only the `branches` (or `tags`) keyword and apply the `!` operator to negate specific patterns [3][2]. For more complex path-based filtering (e.g., in `paths` or `paths-ignore` or `hashFiles`), behavior may vary slightly as these often follow the `@actions/glob` implementation, which primarily utilizes `*` and for wildcards [1][4]. Always consult the specific section of the workflow syntax for the filter you are configuring [1][5].

Citations:


🌐 Web query:

site:docs.github.com/actions/reference/workflows-and-actions/workflow-syntax "+" tags filter "one or more" GitHub Actions

💡 Result:

You can filter GitHub Actions workflows to run on specific tags using the tags keyword within the push event trigger [1][2]. To restrict a workflow to run only for specific tags, add the tags filter to your workflow YAML file [3][4]: on: push: tags: - v1 - v2.* Key points regarding tag filtering: 1. Matching Patterns: You can use glob patterns (such as *,?, and!) to match multiple tag names [3][4]. If a tag name contains these special characters and you need a literal match, you must escape them with a backslash () [1][3]. 2. Exclusions: You can use tags-ignore to exclude specific tag patterns [1][2]. Note that you cannot use both the tags and tags-ignore keywords for the same event in a single workflow [3][4]. 3. Behavior: If you define tags or tags-ignore, the workflow will only run for events affecting tags and will not run for branch pushes [1][4]. If you define neither tags nor branches filters, the workflow will run for events affecting either branches or tags [1][3]. 4. Combined Filters: If you define both tags (or branches) and paths filters, the workflow will only run when both conditions are satisfied [1][2].

Citations:


🌐 Web query:

site:docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax "The + character" tags branches glob

💡 Result:

In the context of GitHub Actions workflow syntax, the + character is identified as a supported special character used within glob patterns for branches and tags [1]. These patterns allow for the matching of multiple branch or tag names [1]. If a branch or tag name literally contains a + character, it must be escaped using a backslash () to prevent it from being interpreted as a special glob character [1]. While documentation confirms its status as a special character for filtering, it does not explicitly define its specific matching behavior (such as "one or more") in the provided sources [1].

Citations:


🌐 Web query:

site:docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax "character class" "tags" "glob"

💡 Result:

In the context of glob patterns, character classes (also referred to as character sets or ranges) are a standard feature used to match any single character from a specified set or range [1]. A character class is defined by enclosing the characters in square brackets, such as [abc] to match 'a', 'b', or 'c', or [a-z] to match any lowercase letter. While the provided documentation specifically highlights wildcard operators such as *,, +,?, and!, glob patterns traditionally include support for character classes to provide more granular matching capabilities than simple wildcards [1]. These patterns are used in various environments, such as shell filename expansion and workflow configuration filters, to match multiple items based on defined criteria [1]. If a literal match for a character used in a pattern is required, it must be escaped with a backslash () [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import urllib.request
url = "https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax"
try:
    with urllib.request.urlopen(url, timeout=20) as r:
        text = r.read(5000).decode("utf-8", "replace")
    print(text[:2000])
except Exception as e:
    print("ERR", type(e).__name__, e)
PY

Repository: openforge-oss/anvil

Length of output: 304


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import urllib.request, re
url = "https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax"
try:
    with urllib.request.urlopen(url, timeout=20) as r:
        text = r.read().decode("utf-8", "replace")
    # Print nearby snippets for tags / glob / plus characters if present.
    for needle in ["tags", "glob", "plus", "character class", "workflow syntax"]:
        idx = text.lower().find(needle.lower())
        if idx != -1:
            start = max(0, idx - 300)
            end = min(len(text), idx + 1000)
            print(f"\n=== {needle} ===\n")
            print(re.sub(r"<[^>]+>", " ", text[start:end]))
except Exception as e:
    print("ERR", type(e).__name__, e)
PY

Repository: openforge-oss/anvil

Length of output: 304


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import urllib.request, re
url = "https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax"
try:
    with urllib.request.urlopen(url, timeout=20) as r:
        text = r.read().decode("utf-8", "replace")
    needles = ["tags:", "tags-ignore", "glob", "+", "[]", "workflow syntax"]
    for needle in needles:
        idx = text.lower().find(needle.lower())
        if idx != -1:
            start = max(0, idx - 400)
            end = min(len(text), idx + 1200)
            snippet = re.sub(r"<[^>]+>", " ", text[start:end])
            print(f"\n=== {needle} ===\n{snippet}\n")
except Exception as e:
    print("ERR", type(e).__name__, e)
PY

Repository: openforge-oss/anvil

Length of output: 304


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import urllib.request, ssl, re

url = "https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax"
ctx = ssl._create_unverified_context()
with urllib.request.urlopen(url, context=ctx, timeout=30) as r:
    html = r.read().decode("utf-8", "replace")

for needle in ["tags:", "branches:", "glob", "special characters", "+", "[0-9]"]:
    idx = html.lower().find(needle.lower())
    print(f"\n=== {needle} @ {idx} ===")
    if idx != -1:
        start = max(0, idx - 500)
        end = min(len(html), idx + 1500)
        snippet = re.sub(r"<[^>]+>", " ", html[start:end])
        print(snippet)
PY

Repository: openforge-oss/anvil

Length of output: 5109


🌐 Web query:

GoReleaser skip_upload auto prerelease homebrew casks scoop docs

💡 Result:

In GoReleaser, you can manage release behavior, pre-releases, and publishing to Homebrew or Scoop using specific configuration fields in your.goreleaser.yaml file. skip_upload The skip_upload option can be used in various sections (such as release, brew, or cask) to prevent artifacts from being uploaded to the source control management (SCM) or specific package managers [1][2][3]. In the release section, setting skip_upload: true prevents artifacts from being uploaded to the release, though the release itself (with the changelog) will still be created [1]. Be aware that disabling certain pipes or skipping uploads may affect dependent processes, such as auto-publishing to Homebrew taps [1][4]. Prereleases You can configure automatic pre-release detection by setting prerelease: auto in the release section of your configuration [5][6]. When enabled, GoReleaser automatically identifies tags with common pre-release suffixes (such as -alpha, -beta, -rc, -pre, or -nightly) and marks them as pre-releases on GitHub instead of standard releases [5][6]. The default behavior is false [6]. Homebrew Casks and Scoop GoReleaser supports automated publishing to Homebrew and Scoop by defining the homebrew_casks and scoops sections, respectively [7][8]. - Homebrew Casks: The homebrew_casks section is used to generate and publish Homebrew Casks into a specified tap repository [7][9]. This section has largely superseded the older brews configuration [9]. - Scoop: The scoops section allows you to generate and publish Scoop app manifests to a dedicated bucket repository [8]. Both configurations support using environment-specific tokens for authentication to private repositories by referencing environment variables (e.g., token: "{{.Env.YOUR_TOKEN_NAME }}") [10]. For further details, refer to the official GoReleaser documentation for Releases [1], Homebrew Casks [7], and Scoop [8].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Fetch the GitHub Actions workflow syntax page with TLS verification disabled and
# extract any lines mentioning tag filters or special pattern characters.
python3 - <<'PY'
import subprocess, re, sys

url = "https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax"
raw = subprocess.check_output(["curl", "-ksL", url], text=True)

for needle in ["tags", "branches", "glob", "special characters", "character", "+", "["]:
    idx = raw.lower().find(needle.lower())
    print(f"\n=== {needle} @ {idx} ===")
    if idx != -1:
        start = max(0, idx - 400)
        end = min(len(raw), idx + 1200)
        snippet = re.sub(r"<[^>]+>", " ", raw[start:end])
        print(snippet)
PY

Repository: openforge-oss/anvil

Length of output: 4465


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look up GoReleaser docs for skip_upload auto behavior.
python3 - <<'PY'
import subprocess, re

urls = [
    "https://goreleaser.com/customization/homebrew/",
    "https://goreleaser.com/customization/scoop/",
    "https://goreleaser.com/customization/",
]
for url in urls:
    try:
        raw = subprocess.check_output(["curl", "-ksL", url], text=True, timeout=30)
    except Exception as e:
        print(f"\n=== {url} ===\nERR {e}")
        continue
    print(f"\n=== {url} ===")
    for needle in ["skip_upload", "auto", "prerelease", "pre-release"]:
        idx = raw.lower().find(needle.lower())
        if idx != -1:
            start = max(0, idx - 300)
            end = min(len(raw), idx + 1200)
            snippet = re.sub(r"<[^>]+>", " ", raw[start:end])
            print(f"\n--- {needle} @ {idx} ---\n{snippet}")
PY

Repository: openforge-oss/anvil

Length of output: 6752


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Fetch the GitHub Actions workflow syntax page with TLS verification disabled and
# extract any lines mentioning tag filters or special pattern characters.
python3 - <<'PY'
import subprocess, re

url = "https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax"
raw = subprocess.check_output(["curl", "-ksL", url], text=True)

for needle in ["tags", "branches", "glob", "special characters", "character", "+", "["]:
    idx = raw.lower().find(needle.lower())
    print(f"\n=== {needle} @ {idx} ===")
    if idx != -1:
        start = max(0, idx - 400)
        end = min(len(raw), idx + 1200)
        snippet = re.sub(r"<[^>]+>", " ", raw[start:end])
        print(snippet)
PY

Repository: openforge-oss/anvil

Length of output: 4465


Skip prerelease uploads for tap manifests.
The tag filter still covers prerelease tags like v1.0.0-rc1, so skip_upload: false publishes prerelease Homebrew and Scoop manifests. Set both entries to skip_upload: auto.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.goreleaser.yaml at line 34, Update both tap manifest entries in the
GoReleaser configuration to use skip_upload: auto instead of the
HOMEBREW_TAP_TOKEN-based template, so prerelease tags are not uploaded while
stable releases retain automatic publishing.
go.mod (1)

64-64: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## go.mod excerpt\n'
nl -ba go.mod | sed -n '55,75p'

printf '\n## grpc references\n'
rg -n "google\.golang\.org/grpc" go.mod go.sum . -g '!**/vendor/**' || true

Repository: openforge-oss/anvil

Length of output: 216


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## go.mod excerpt\n'
sed -n '55,75p' go.mod | cat -n

printf '\n## grpc references\n'
grep -RIn --exclude-dir=.git --exclude-dir=vendor "google.golang.org/grpc" . || true

Repository: openforge-oss/anvil

Length of output: 1102


Upgrade google.golang.org/grpc to v1.82.1+
go.mod still pins v1.82.0, so this dependency remains in the vulnerable range covered by GHSA-hrxh-6v49-42gf. Update the module metadata and rerun govulncheck.

🧰 Tools
🪛 OSV Scanner (2.4.0)

[HIGH] 64-64: google.golang.org/grpc 1.82.0: gRPC-Go: xDS RBAC and HTTP/2 Vulnerabilities

(GHSA-hrxh-6v49-42gf)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` at line 64, Update the google.golang.org/grpc dependency entry in
go.mod from v1.82.0 to v1.82.1 or later, refresh the associated module metadata,
and rerun govulncheck to confirm the vulnerability is resolved.

Source: Linters/SAST tools

internal/detect/web.go (1)

82-85: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Ignore npm’s placeholder test script during detection.

A default npm init script such as echo "Error: no test specified" && exit 1 currently makes an otherwise bare package appear to be a web library.

  • internal/detect/web.go#L82-L85: exclude placeholder test scripts, as the web driver already does.
  • internal/detect/goweb_test.go#L51-L60: add a bare package case containing npm’s default test script and assert it is not detected.
📍 Affects 2 files
  • internal/detect/web.go#L82-L85 (this comment)
  • internal/detect/goweb_test.go#L51-L60
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/detect/web.go` around lines 82 - 85, Update hasWebTooling in
internal/detect/web.go to ignore npm’s default placeholder test script (“echo
\"Error: no test specified\" && exit 1”) when determining web tooling, matching
the existing web-driver behavior. Add a bare-package test case in
internal/detect/goweb_test.go covering that script and assert it is not
detected.
internal/driver/reactnative.go (1)

97-108: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Parse packageManager from package.json instead of scanning the raw file. strings.Contains can match unrelated yarn@ text and select the wrong install flag; read the manifest field directly and add coverage for Yarn 1, Yarn Berry, non-Yarn managers, malformed manifests, and unrelated strings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/driver/reactnative.go` around lines 97 - 108, Update isYarnBerry to
parse package.json as structured JSON and inspect only its packageManager field,
rather than scanning raw contents for yarn markers. Preserve .yarnrc.yml
detection, return false for unreadable or malformed manifests, and classify only
Yarn versions newer than Yarn 1 as Berry. Add coverage for Yarn 1, Yarn Berry,
non-Yarn managers, malformed manifests, and unrelated strings.
internal/driver/web.go (1)

27-38: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Honor the declared test script before config fallbacks.

A repository with vitest.config.* or Jest config and a custom scripts.test runs npx directly, bypassing its package manager and dropping script-defined flags/setup.

  • internal/driver/web.go#L27-L38: run a valid scripts.test via pmRun first; use Jest/Vitest config detection only as a fallback.
  • internal/driver/goweb_test.go#L29-L43: add a config-plus-custom-test-script case that expects npm run test (and CI=true).
📍 Affects 2 files
  • internal/driver/web.go#L27-L38 (this comment)
  • internal/driver/goweb_test.go#L29-L43
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/driver/web.go` around lines 27 - 38, The Test case in
internal/driver/web.go, around the hasJest/hasVitest checks, must prioritize a
valid scripts["test"] entry by returning pmRun with CI=true before falling back
to Jest or Vitest config detection; update internal/driver/goweb_test.go around
the existing test cases to add a repository with a detected config and custom
test script, asserting npm run test and CI=true.
internal/upload/creds.go (2)

27-36: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does Go's os.MkdirTemp with an empty directory use os.TempDir, and can os.TempDir use TMPDIR on Unix?

💡 Result:

Yes, when the dir argument provided to os.MkdirTemp is an empty string, it uses the directory returned by os.TempDir as the base location for the new temporary directory [1][2][3]. Regarding os.TempDir, it does indeed use the TMPDIR environment variable on Unix systems [1][3][4]. Specifically, on Unix-like systems, os.TempDir returns the value of $TMPDIR if it is set and non-empty; if $TMPDIR is empty or unset, it defaults to /tmp (or /data/local/tmp on Android) [5][4][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- internal/upload/creds.go ---\n'
cat -n internal/upload/creds.go

printf '\n--- search for repo-root / containment helpers ---\n'
rg -n "inside\(|EvalSymlinks|MkdirTemp|TempDir|TMPDIR|working tree|repoRoot|repo root" internal . -g '!**/vendor/**'

Repository: openforge-oss/anvil

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- internal/upload/creds.go ---'
cat -n internal/upload/creds.go

echo
echo '--- search for repo-root / containment helpers ---'
rg -n "inside\\(|EvalSymlinks|MkdirTemp|TempDir|TMPDIR|working tree|repoRoot|repo root" internal . -g '!**/vendor/**'

Repository: openforge-oss/anvil

Length of output: 7312


Block repo-local temp dirs for decoded credentials. This base64 path returns before the repo-boundary check, so a repo-scoped $TMPDIR can place the decoded secret inside the working tree. Reject dir with inside(repoRoot, dir) before WriteFile, clean up on failure, and add a regression test for TMPDIR under the repo root.

🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 33-33: Error return value of os.RemoveAll is not checked

(errcheck)


[error] 36-36: Error return value of os.RemoveAll is not checked

(errcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/upload/creds.go` around lines 27 - 36, The base64 credential path
can create decoded secrets inside the repository because the temporary directory
is not validated. In the relevant credential-decoding function, check dir with
inside(repoRoot, dir) before os.WriteFile; reject and remove the directory when
it is repo-contained, while preserving cleanup on all failures. Add a regression
test covering TMPDIR configured beneath the repository root.

41-57: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Go's filepath package, does Abs resolve symbolic links, and what does EvalSymlinks return?

💡 Result:

In Go's path/filepath package, filepath.Abs does not resolve symbolic links [1][2][3]. It operates by joining a relative path with the current working directory (if necessary) and then cleaning the resulting path using filepath.Clean [1][3]. Because filepath.Clean performs purely lexical path processing, it does not inspect the filesystem to account for symbolic links, which can result in incorrect paths if symbolic links are present [2][3][4]. In contrast, filepath.EvalSymlinks is specifically designed to resolve symbolic links [1][3]. It returns the path name after evaluating any symbolic links encountered [1][3]. If the provided path is relative, the result remains relative to the current directory unless one of the path components is an absolute symbolic link [1][3]. Similar to filepath.Abs, it also calls filepath.Clean on the final result [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- internal/upload/creds.go ---\n'
cat -n internal/upload/creds.go

printf '\n--- search for inside(repoRoot, path) and credential tests ---\n'
rg -n "inside\\(|EvalSymlinks|credential not found|refusing to read a credential|upload/creds" internal test . -g '!**/vendor/**' || true

Repository: openforge-oss/anvil

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- file list around internal/upload ---\n'
git ls-files internal/upload

Repository: openforge-oss/anvil

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- internal/upload/creds.go ---'
cat -n internal/upload/creds.go

printf '%s\n' ''
printf '%s\n' '--- search for inside(repoRoot, path), EvalSymlinks, and credential tests ---'
rg -n "inside\\(|EvalSymlinks|credential not found|refusing to read a credential|keep it outside the working tree|read a credential" internal . -g '!**/vendor/**' || true

printf '%s\n' ''
printf '%s\n' '--- file list around internal/upload ---'
git ls-files internal/upload

Repository: openforge-oss/anvil

Length of output: 3530


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- internal/upload/upload_test.go ---'
cat -n internal/upload/upload_test.go

Repository: openforge-oss/anvil

Length of output: 3311


Resolve symlinks before checking the repo boundary.

inside() only uses filepath.Abs, so a path outside repoRoot that points at a symlink into the working tree still passes the guard and can expose a repo secret. Canonicalize both paths with filepath.EvalSymlinks before comparing them, and add a regression test for the symlink case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/upload/creds.go` around lines 41 - 57, Update inside to canonicalize
both root and path with filepath.EvalSymlinks before computing their relative
path, returning false if resolution fails; preserve the existing boundary
comparison. Add a regression test covering an external path symlinked into
repoRoot and verify it is rejected by the credential-loading guard.
internal/upload/npm.go (1)

17-26: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate an actual npm authentication setting.

Any existing .npmrc, including an empty file, passes Validate() even though npm publish will fail authentication. Inspect for a supported auth configuration or delegate the check to npm, and add a test for an empty .npmrc.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/upload/npm.go` around lines 17 - 26, Update NPM.Validate to verify
that NPM_TOKEN or an .npmrc contains a usable npm authentication setting rather
than accepting any existing file; inspect supported auth configuration or
delegate validation to npm while preserving successful validation for valid
credentials. Add coverage for an empty .npmrc and ensure it returns the
authentication error.
README.md (2)

7-8: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the “one command” claim with the actual CLI flow.

This says one command also signs and uploads, but the Usage section exposes anvil sign, anvil build --sign, and anvil upload as separate operations. Reword this as the CLI providing the full lifecycle, or document the exact command/flags that perform signing and uploading.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 7 - 8, Update the README sentence describing the “one
command” workflow to match the documented CLI behavior: describe Anvil as
providing the full lifecycle without claiming a single command signs and
uploads, unless the Usage section is updated with the exact signing and upload
command or flags.

60-60: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the HOMEBREW_TAP_TOKEN prerequisite.

The release workflow and GoReleaser configuration skip Homebrew/Scoop uploads when HOMEBREW_TAP_TOKEN is absent. Name this variable here so users understand why tap artifacts may not appear in the next release.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 60, Update the Homebrew and Scoop distribution statement
in README.md to document HOMEBREW_TAP_TOKEN as a required prerequisite, noting
that tap artifacts may be skipped when it is absent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant