Release v0.1.0 - #17
Conversation
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.
ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (24)
📝 WalkthroughWalkthroughChangesGo and web stack support
Artifact upload command
Release automation
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (2)
.goreleaser.yaml (1)
4-6: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAvoid mutating module metadata during release.
go mod tidycan rewritego.modandgo.sumbefore 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 winPin the release toolchain and action references.
stable,@v4,@v5,@v7, and~> v2can 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
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (24)
.github/workflows/release.yml.goreleaser.yamlCHANGELOG.mdREADME.mdcmd/upload.godocs/ROADMAP.mdgo.modinternal/detect/detect.gointernal/detect/go.gointernal/detect/goweb_test.gointernal/detect/reactnative.gointernal/detect/web.gointernal/driver/go.gointernal/driver/goweb_test.gointernal/driver/reactnative.gointernal/driver/registry.gointernal/driver/web.gointernal/upload/android.gointernal/upload/creds.gointernal/upload/ios.gointernal/upload/npm.gointernal/upload/upload.gointernal/upload/upload_test.gotasks/todo.md
There was a problem hiding this comment.
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 winAvoid mutating module metadata during release.
go mod tidycan rewritego.modandgo.sumbefore 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 winPin the release toolchain and action references.
stable,@v4,@v5,@v7, and~> v2can 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
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (24)
.github/workflows/release.yml.goreleaser.yamlCHANGELOG.mdREADME.mdcmd/upload.godocs/ROADMAP.mdgo.modinternal/detect/detect.gointernal/detect/go.gointernal/detect/goweb_test.gointernal/detect/reactnative.gointernal/detect/web.gointernal/driver/go.gointernal/driver/goweb_test.gointernal/driver/reactnative.gointernal/driver/registry.gointernal/driver/web.gointernal/upload/android.gointernal/upload/creds.gointernal/upload/ios.gointernal/upload/npm.gointernal/upload/upload.gointernal/upload/upload_test.gotasks/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.ymlRepository: openforge-oss/anvil
Length of output: 1284
Disable checkout credential persistence.
actions/checkoutwrites its token into the repo config by default. This job already passes the tokens it needs to GoReleaser, so addpersist-credentials: falseto 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 || trueRepository: 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
cacheinput controls the caching of Go modules and build outputs [1][2]. This feature is enabled by default [1][3]. When you setcache: 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 settingcache: falsedoes 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 thecachesetting in thesetup-goaction [4]. Therefore, even withcache: false, the Go installation step remains fast because the binary is typically already available in the runner's tool cache [4].Citations:
- 1: https://github.com/actions/setup-go
- 2: https://github.com/actions/setup-go/tree/v5.6.0
- 3: https://github.com/actions/setup-go/blob/d35c59ab/README.md
- 4: actions/setup-go#549
- 5: https://docs.github.com/actions/automating-builds-and-tests/building-and-testing-go
🏁 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 || trueRepository: 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:
- 1: https://github.com/actions/setup-go
- 2: https://github.com/actions/setup-go/blob/main/action.yml
- 3: https://github.blog/changelog/2023-03-24-github-actions-the-setup-go-action-now-enables-caching-by-default/
- 4: https://github.com/actions/setup-go/tree/v5.6.0
- 5: actions/setup-go@c51a720
- 6: https://github.com/marketplace/actions/setup-go-environment
Disable
setup-gocaching in the release job.actions/setup-go@v5enables cache restoration by default, and this workflow publishes release binaries. Addcache: falsehere 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.ymlRepository: 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 expandedRepository: 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
tagsandtags-ignoreworkflow 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:
- 1: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
- 2: https://docs.github.com/en/enterprise-cloud@latest/actions/reference/workflows-and-actions/workflow-syntax
- 3: https://github.com/github/docs/blob/main/content/actions/reference/workflows-and-actions/workflow-syntax.md
- 4: https://stackoverflow.com/questions/76612101/match-tags-in-github-action
- 5: https://stackoverflow.com/questions/77284754/only-run-workflow-if-semantic-version-is-used
🌐 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
tagsandtags-ignorefor 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 thetags-ignorefilter instead [2][3].Citations:
- 1: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
- 2: http://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow
- 3: https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow
🏁 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)) PYRepository: 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)}") PYRepository: 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:
- 1: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
- 2: https://docs.github.com/en/enterprise-cloud@latest/actions/reference/workflows-and-actions/workflow-syntax
- 3: https://docs.github.com/en/enterprise-server@3.19/actions/reference/workflows-and-actions/workflow-syntax
- 4: https://docs.github.com/actions/reference/evaluate-expressions-in-workflows-and-actions
- 5: https://docs.github.com/actions/security-guides/using-secrets-in-github-actions
- 6: https://docs.github.com/en/actions/concepts/workflows-and-actions/expressions
🌐 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.pushoron.pull_requestevents, thebranches,branches-ignore,tags, andtags-ignorekeywords 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:
- 1: https://docs.github.com/fr/actions/reference/workflows-and-actions/workflow-syntax
- 2: https://docs.github.com/fr/enterprise-server@3.15/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow
- 3: https://docs.github.com/fr/enterprise-server@3.13/actions/reference/workflow-syntax-for-github-actions
- 4: https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference
- 5: https://docs.github.com/en/code-security/reference/code-scanning/workflow-configuration-options
- 6: https://docs.github.com/fr/actions/concepts/workflows-and-actions/workflows
- 7: https://docs.github.com/fr/actions/tutorials/create-an-example-workflow
🌐 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, andtags-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 `*` andfor wildcards [1][4]. Always consult the specific section of the workflow syntax for the filter you are configuring [1][5].Citations:
- 1: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
- 2: https://docs.github.com/actions/using-workflows/triggering-a-workflow
- 3: https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow
- 4: https://docs.github.com/actions/reference/evaluate-expressions-in-workflows-and-actions
- 5: https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions
🌐 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:
- 1: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
- 2: https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow
- 3: https://docs.github.com/en/enterprise-cloud@latest/actions/reference/workflows-and-actions/workflow-syntax
- 4: https://docs.github.com/en/enterprise-server@3.18/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow
🌐 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) PYRepository: 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) PYRepository: 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) PYRepository: 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) PYRepository: 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:
- 1: https://goreleaser.com/customization/publish/scm/
- 2: https://github.com/goreleaser/goreleaser/blob/826438b86584ba27c055186bfb544c20646bf667/pkg/config/config.go
- 3: https://github.com/goreleaser/goreleaser/blob/main/pkg/config/config.go
- 4: goreleaser/goreleaser#4743
- 5: flungo/terraform-provider-stalwart#6
- 6: panyam/slyds@4f1e060
- 7: https://goreleaser.com/customization/publish/homebrew_casks/
- 8: https://goreleaser.com/customization/publish/scoop/
- 9: goreleaser/goreleaser#5780
- 10: goreleaser/goreleaser#1650
🏁 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) PYRepository: 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}") PYRepository: 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) PYRepository: openforge-oss/anvil
Length of output: 4465
Skip prerelease uploads for tap manifests.
The tag filter still covers prerelease tags likev1.0.0-rc1, soskip_upload: falsepublishes prerelease Homebrew and Scoop manifests. Set both entries toskip_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/**' || trueRepository: 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" . || trueRepository: openforge-oss/anvil
Length of output: 1102
Upgrade
google.golang.org/grpcto v1.82.1+
go.modstill pinsv1.82.0, so this dependency remains in the vulnerable range covered by GHSA-hrxh-6v49-42gf. Update the module metadata and rerungovulncheck.🧰 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 initscript such asecho "Error: no test specified" && exit 1currently 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
packageManagerfrompackage.jsoninstead of scanning the raw file.strings.Containscan match unrelatedyarn@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 customscripts.testrunsnpxdirectly, bypassing its package manager and dropping script-defined flags/setup.
internal/driver/web.go#L27-L38: run a validscripts.testviapmRunfirst; 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 expectsnpm run test(andCI=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:
- 1: https://pkg.go.dev/os
- 2: https://go.googlesource.com/go/+/refs/heads/master/src/os/tempfile.go
- 3: https://pkg.go.dev/os@go1.26.4
- 4: https://go.googlesource.com/go/+/refs/heads/master/src/os/file.go
- 5: https://github.com/golang/go/blob/go1.26.4/src/os/file_unix.go
- 6: https://github.com/golang/go/blob/go1.24.0/src/os/file_unix.go
🏁 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
$TMPDIRcan place the decoded secret inside the working tree. Rejectdirwithinside(repoRoot, dir)beforeWriteFile, clean up on failure, and add a regression test forTMPDIRunder the repo root.🧰 Tools
🪛 golangci-lint (2.12.2)
[error] 33-33: Error return value of
os.RemoveAllis not checked(errcheck)
[error] 36-36: Error return value of
os.RemoveAllis 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:
- 1: https://pkg.go.dev/path/filepath
- 2: https://go.dev/src/path/filepath/path.go?m=text
- 3: https://github.com/golang/go/blob/go1.26.5/src/path/filepath/path.go
- 4: golang/go#16255
🏁 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/**' || trueRepository: 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/uploadRepository: 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/uploadRepository: 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.goRepository: openforge-oss/anvil
Length of output: 3311
Resolve symlinks before checking the repo boundary.
inside()only usesfilepath.Abs, so a path outsiderepoRootthat points at a symlink into the working tree still passes the guard and can expose a repo secret. Canonicalize both paths withfilepath.EvalSymlinksbefore 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, passesValidate()even thoughnpm publishwill 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, andanvil uploadas 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_TOKENprerequisite.The release workflow and GoReleaser configuration skip Homebrew/Scoop uploads when
HOMEBREW_TAP_TOKENis 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.
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_TOKENis set, taggingv0.1.0publishes binaries, the GitHub release, and the Homebrew/Scoop taps.Summary by CodeRabbit
New Features
anvil uploadwith dry-run previews and support for iOS, Android, and npm publishing.Documentation