Skip to content

Introduce --upgrade CLI argument - #63

Merged
christianhelle merged 7 commits into
mainfrom
update
Jul 7, 2026
Merged

Introduce --upgrade CLI argument#63
christianhelle merged 7 commits into
mainfrom
update

Conversation

@christianhelle

@christianhelle christianhelle commented Jul 7, 2026

Copy link
Copy Markdown
Owner

This pull request adds a self-upgrade feature to the openapi2zig CLI tool, allowing users to update to the latest version with a simple --upgrade flag. The implementation includes argument parsing, user-facing documentation, integration in the main entrypoint, and a new upgrade.zig module that handles all platform-specific upgrade logic.

Upgrade Feature Implementation

  • Added support for a --upgrade flag in CLI argument parsing, making it possible to trigger an upgrade from the command line.
  • Updated the CLI usage and help text to document the new --upgrade option and provide example usage. [1] [2]
  • Added a test to ensure the --upgrade flag is parsed correctly.

Integration and Upgrade Logic

  • Integrated the upgrade logic into the main program flow in src/main.zig, so that when the --upgrade flag is present, the upgrade process is run instead of normal code generation. [1] [2]
  • Added a new upgrade.zig module that:
    • Detects the current platform and architecture.
    • Fetches the latest release version from GitHub.
    • Downloads the appropriate binary archive for the user's platform.
    • Extracts and installs the new binary, handling platform-specific file operations.
    • Provides user feedback throughout the process and includes tests for key upgrade logic.

Summary by CodeRabbit

  • New Features

    • Added an --upgrade option to update the app directly from the CLI, including refreshed usage/help text.
    • The app now checks for the latest release, downloads the matching build for your platform, and installs it automatically.
  • Bug Fixes

    • Improved the startup flow so upgrade runs independently from the normal code-generation path.
    • Enhanced error handling and status messaging for upgrade failures.
  • Tests

    • Added unit tests for upgrade parsing and version/platform handling.
    • Updated smoke tests to skip a failing case when generation errors occur.

@christianhelle christianhelle self-assigned this Jul 7, 2026
@christianhelle christianhelle added the enhancement New feature or request label Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e2f1214-ea18-4776-83a9-5d1f9f7ed43c

📥 Commits

Reviewing files that changed from the base of the PR and between 4e7a4d3 and e83e0dd.

📒 Files selected for processing (1)
  • test/smoke-tests.ps1
✅ Files skipped from review due to trivial changes (1)
  • test/smoke-tests.ps1

📝 Walkthrough

Walkthrough

Adds a --upgrade CLI path that runs a new self-update workflow instead of code generation. The update flow checks the latest GitHub release, downloads the matching archive for the current platform, extracts it, and replaces the running binary.

Changes

Self-upgrade feature

Layer / File(s) Summary
CLI flag and usage text
src/cli.zig
ParsedArgs gains an upgrade field; parse() short-circuits on --upgrade; usage/help text documents the new option; a test verifies the flag is parsed.
Main entry point wiring
src/main.zig
Imports the upgrade module and adds a conditional branch that runs upgrade.run when the upgrade flag is set, printing errors and returning early on both success and failure.
Platform detection helpers
src/upgrade.zig
Introduces a Platform enum and helpers mapping the build target to platform tags, archive string fragments, and Windows/Linux detection, with unit tests.
Version check and archive download
src/upgrade.zig
Resolves temp directories, fetches the latest GitHub release tag via curl/PowerShell with JSON parsing, downloads the release archive over HTTP, and strips leading v from version tags (tested).
Extraction and binary installation
src/upgrade.zig
Extracts archives with tar, replaces the running binary (including Windows rename/cleanup via PowerShell), and coordinates the full flow in the exported run function.
Smoke test denylist update
test/smoke-tests.ps1
Adds openapi/v3.1/openai.yaml to the denylist for all wrapper modes after an InvalidEscapeSequence generation error.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Cli
  participant Main
  participant Upgrade
  participant GitHubReleases

  User->>Cli: run with --upgrade
  Cli->>Main: parsed_args.upgrade = true
  Main->>Upgrade: run(allocator, io, environ_map)
  Upgrade->>GitHubReleases: fetch latest release tag
  GitHubReleases-->>Upgrade: tag_name
  Upgrade->>Upgrade: download and extract archive
  Upgrade->>Upgrade: replaceBinary
  Upgrade-->>Main: success or error.UpgradeFailed
  Main-->>User: print status / return
Loading

Possibly related PRs

Suggested labels: documentation

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change by introducing the new --upgrade CLI flag.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
src/main.zig (1)

13-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate error output on upgrade failure.

upgrade.run already prints a specific failure reason internally before returning the error (e.g. " Failed to check for updates: {}\n" per src/upgrade.zig), and this branch prints a second, generic "Upgrade failed: {}\n" for the same error. Users will see two lines describing the same failure. Consider either dropping the wrapper print here (letting upgrade.run own user-facing messaging) or removing the internal prints in upgrade.run and centralizing the message here.

♻️ Example: let upgrade.run own the messaging
     if (parsed_args.upgrade) {
-        upgrade.run(allocator, io, init.environ_map) catch |err| {
-            std.debug.print("Upgrade failed: {}\n", .{err});
-            return err;
-        };
+        try upgrade.run(allocator, io, init.environ_map);
         return;
     }
🤖 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 `@src/main.zig` around lines 13 - 19, The upgrade failure path in main should
not emit duplicate user-facing errors because upgrade.run already prints the
specific reason internally. Update the parsed_args.upgrade branch in main.zig to
either stop printing the generic "Upgrade failed" message and just return the
error, or move all user-facing failure logging out of upgrade.run and centralize
it here. Keep the behavior consistent by choosing one owner for the message
across main and upgrade.run.
src/upgrade.zig (3)

282-285: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

This test duplicates the trim logic instead of exercising production code.

The assertions re-implement the v-prefix stripping inline, so they pass regardless of the actual code in run. Extract the prefix-stripping into a small helper (e.g. stripVPrefix) and test that, so the test protects the real behavior.

🤖 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 `@src/upgrade.zig` around lines 282 - 285, The test in upgrade.zig is
duplicating the inline v-prefix trimming logic instead of verifying the actual
behavior used by run. Extract the prefix removal into a small helper such as
stripVPrefix and update the version comparison test to call that helper
directly, so the assertions exercise the production path rather than
reimplementing it inside the test.

88-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Error paths discard useful diagnostics.

Every failure here collapses to a bare error.UpgradeFailed, so users get no indication whether curl/powershell is missing, the network call failed, or the JSON was malformed. On non-zero exit you already have result.stderr available but discard it. Consider logging result.stderr / the offending condition before returning.

As per coding guidelines: "Use Zig error sets and catch |err| pattern for error handling with meaningful context messages".

🤖 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 `@src/upgrade.zig` around lines 88 - 104, The upgrade flow in upgrade logic
returns bare UpgradeFailed for every failure, which hides whether the command
failed, stderr was emitted, or the JSON response was invalid. Update the error
handling around the command result and JSON parsing to log meaningful context
before returning, especially in the result.term check and the non-Windows
parsing path; use the existing result.stderr and the offending condition, and
prefer Zig’s catch |err| pattern with descriptive messages in the upgrade
function.

Source: Coding guidelines


184-201: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

std.process.run result buffers are leaked here (and inconsistent allocator).

copyFile and the cleanup call discard the RunResult with _ =, leaking the stdout/stderr allocations (contrast with fetchLatestVersion, which frees them). Line 184 also uses std.heap.page_allocator instead of the passed allocator, which is inconsistent. Impact is small in a short-lived upgrade process, but worth tidying for consistency.

🤖 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 `@src/upgrade.zig` around lines 184 - 201, The cleanup call and copyFile both
discard the std.process.run result, which leaks the captured stdout/stderr
buffers, and the cleanup path also uses std.heap.page_allocator instead of the
allocator passed into the upgrade flow. Update the upgrade logic around
std.process.run and the copyFile helper so the RunResult is always captured and
its buffers are freed like in fetchLatestVersion, and make the cleanup
invocation use the same allocator consistently.
🤖 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 `@src/upgrade.zig`:
- Around line 218-226: The upgrade check in the version comparison is
equality-only, so any non-matching build is treated as needing an upgrade even
when the current build is newer. Update the logic around the current/latest
comparison in upgrade.zig to use a proper semver ordering instead of
std.mem.eql, and only continue with the download/replace flow when
latest_version is strictly newer than version_info.VERSION. Keep the existing
latest_version trimming behavior, but route the decision through the version
comparison in the upgrade path.
- Around line 242-249: The upgrade flow is extracting the release archive to the
wrong directory, so `replaceBinary` later looks in a different path than where
`extractArchive` placed `openapi2zig`. Update the `upgrade` logic around
`extractArchive` and `new_binary` so the archive is extracted into `tmp_sub` or,
alternatively, compute `new_binary` from the actual extraction root
(`tmp_dir_path`) to match the flat release layout and keep the binary lookup
consistent.
- Around line 137-146: The download flow in the release-asset handling path
trusts the archive after reader.allocRemaining and dest_dir.writeFile, so add
artifact authentication before returning the archive name. Verify the downloaded
body against a published SHA-256 checksum or signature in the same upgrade path
that writes the asset, and only proceed when the integrity check passes. Use the
existing download/extract flow in src/upgrade.zig to locate the verification
step so the binary is replaced only after validation.
- Around line 128-131: Increase the response-head buffer used in receiveHead
within the upgrade flow so it can hold larger redirect headers, including
GitHub’s signed Location value; replace the current 1 KiB redirect_buf in the
upgrade-related logic with a larger size such as 8 KiB to avoid truncation and
failed downloads.

---

Nitpick comments:
In `@src/main.zig`:
- Around line 13-19: The upgrade failure path in main should not emit duplicate
user-facing errors because upgrade.run already prints the specific reason
internally. Update the parsed_args.upgrade branch in main.zig to either stop
printing the generic "Upgrade failed" message and just return the error, or move
all user-facing failure logging out of upgrade.run and centralize it here. Keep
the behavior consistent by choosing one owner for the message across main and
upgrade.run.

In `@src/upgrade.zig`:
- Around line 282-285: The test in upgrade.zig is duplicating the inline
v-prefix trimming logic instead of verifying the actual behavior used by run.
Extract the prefix removal into a small helper such as stripVPrefix and update
the version comparison test to call that helper directly, so the assertions
exercise the production path rather than reimplementing it inside the test.
- Around line 88-104: The upgrade flow in upgrade logic returns bare
UpgradeFailed for every failure, which hides whether the command failed, stderr
was emitted, or the JSON response was invalid. Update the error handling around
the command result and JSON parsing to log meaningful context before returning,
especially in the result.term check and the non-Windows parsing path; use the
existing result.stderr and the offending condition, and prefer Zig’s catch |err|
pattern with descriptive messages in the upgrade function.
- Around line 184-201: The cleanup call and copyFile both discard the
std.process.run result, which leaks the captured stdout/stderr buffers, and the
cleanup path also uses std.heap.page_allocator instead of the allocator passed
into the upgrade flow. Update the upgrade logic around std.process.run and the
copyFile helper so the RunResult is always captured and its buffers are freed
like in fetchLatestVersion, and make the cleanup invocation use the same
allocator consistently.
🪄 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

Run ID: e4a8adde-2b34-48e4-adaf-48aeea264a54

📥 Commits

Reviewing files that changed from the base of the PR and between 72c604e and 4e7a4d3.

📒 Files selected for processing (3)
  • src/cli.zig
  • src/main.zig
  • src/upgrade.zig

Comment thread src/upgrade.zig
Comment on lines +128 to +131
var redirect_buf: [1024]u8 = undefined;
var response = try req.receiveHead(&redirect_buf);

if (response.head.status != .ok) return error.UpgradeFailed;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Zig std.http.Client request receiveHead redirect buffer size Location header

💡 Result:

In Zig's std.http.Client, the buffering of HTTP headers is essential for handling responses and automatically following redirects, as headers must be parsed to identify fields like Location [1]. Regarding buffer management and size: 1. Header Buffering: When performing a request, std.http.Client requires a buffer to store the incoming HTTP headers. If the entire HTTP header does not fit within the allocated buffer, the request will fail [2][3]. 2. Redirect Buffers: When using the high-level Client.fetch API, you may need to provide a redirect_buffer to handle header data across automatic redirects [4]. 3. Size Limits: - For manual request management (Client.request), the header strategy defaults to dynamic allocation with a maximum limit of 16 KB [1]. - Alternatively, you can provide a static buffer using the.header_strategy =.{.static = &header_buf } option [5]. - If providing a custom buffer, it must be large enough to hold the complete header block (the end of which is marked by the \r\n\r\n pattern) [1]. A commonly used size for these buffers is 8 KB (e.g., u8) [5][4]. Key Considerations: - Location Header: The client parses the Location header from the buffered response to determine the destination for redirects [1]. - Redirect Logic: Automatic redirect handling is enabled by default (usually up to 3 redirects) [1][5]. If a redirect fails or reaches the maximum count, the client may return an error such as TooManyHttpRedirects [6]. - Implementation Note: HTTP headers are parsed in two stages to accommodate the necessity of accessing header information after the initial receipt, which is critical for correctly processing redirects [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files of interest =="
git ls-files | rg '(^|/)(build\.zig|zig\.mod|src/upgrade\.zig|.*\.zig)$' | sed -n '1,120p'

echo
echo "== src/upgrade.zig around the cited lines =="
nl -ba src/upgrade.zig | sed -n '100,170p'

echo
echo "== Zig version references =="
rg -n 'zig [0-9]|0\.[0-9]+\.[0-9]+|std\.http\.Client|receiveHead|redirect_buf|header_strategy' -S . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**'

Repository: christianhelle/openapi2zig

Length of output: 3778


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/upgrade.zig full relevant region =="
nl -ba src/upgrade.zig | sed -n '1,240p'

echo
echo "== search for upgrade/download flow =="
rg -n 'UpgradeFailed|receiveHead|redirect_buf|Location|releases/download|github' src/upgrade.zig

Repository: christianhelle/openapi2zig

Length of output: 247


🌐 Web query:

Zig std.http.Client.Request.receiveHead redirect buffer status .ok 302 follow redirects

💡 Result:

In Zig's std.http.Client, the receiveHead function handles the initial response headers, and the library supports automatic HTTP redirects [1][2]. When using std.http.Client.Request.receiveHead, you must provide a redirect_buffer (a []u8 slice) [3][4]. This buffer is used to store and process header information during the redirection process [1]. Key details regarding redirects and buffer usage: - Automatic Handling: The std.http.Client implementation includes support for automatically following HTTP redirects [1][2]. - Redirect Buffer: The redirect_buffer is necessary for the client to store header data during these redirects [1]. If you do not provide one, or if it is too small, the operation may fail. It is recommended to provide an appropriately sized buffer (e.g., 8 KB or 16 KB) [1][2]. - Status Codes: The client handles standard HTTP status codes, including redirect codes like 301, 302, 303, 307, and 308 [5]. - fetch API: For simpler use cases, you can use std.http.Client.fetch, which abstracts away much of the manual request and redirect handling, and accepts a redirect_buffer parameter directly [6][2]. - Potential Issues: If you are manually managing requests (i.e., not using fetch), ensure that the connection state is properly maintained. If a request hangs after receiving headers or performing operations, it may be due to un-flushed data or improper connection state management [4]. If you are encountering specific issues with 302 redirects, ensure your redirect_buffer is allocated and large enough to hold the headers for the redirected request [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

path = Path("src/upgrade.zig")
lines = path.read_text().splitlines()
for start, end in [(1, 240)]:
    for i in range(start, min(end, len(lines)) + 1):
        print(f"{i:4d} {lines[i-1]}")
PY

echo
echo "== build/version references =="
python3 - <<'PY'
from pathlib import Path
for name in ["build.zig", "zig.mod", "build.zig.zon"]:
    p = Path(name)
    if p.exists():
        print(f"\n## {name}")
        text = p.read_text().splitlines()
        for i, line in enumerate(text, 1):
            if "zig" in line.lower() or "version" in line.lower() or "http" in line.lower():
                print(f"{i:4d} {line}")
PY

echo
echo "== std.http.Client usage in repo =="
rg -n 'receiveHead|redirect_buffer|header_strategy|std\.http\.Client|fetch\(' src build.zig

Repository: christianhelle/openapi2zig

Length of output: 15560


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

path = Path("src/upgrade.zig")
lines = path.read_text().splitlines()
for i, line in enumerate(lines, 1):
    if 90 <= i <= 170 or 1 <= i <= 80:
        print(f"{i:4d} {line}")
PY

echo
echo "== build/version references =="
python3 - <<'PY'
from pathlib import Path
for name in ["build.zig", "zig.mod", "build.zig.zon"]:
    p = Path(name)
    if p.exists():
        print(f"\n## {name}")
        text = p.read_text().splitlines()
        for i, line in enumerate(text, 1):
            if "zig" in line.lower() or "version" in line.lower() or "http" in line.lower():
                print(f"{i:4d} {line}")
PY

echo
echo "== std.http.Client usage in repo =="
rg -n 'receiveHead|redirect_buffer|header_strategy|std\.http\.Client|fetch\(' src build.zig

Repository: christianhelle/openapi2zig

Length of output: 12071


Increase the redirect/header buffer here
receiveHead needs enough room for the full response head, including GitHub’s signed 302 Location; 1 KiB can overflow and abort release downloads. 8 KiB is a safer floor.

🤖 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 `@src/upgrade.zig` around lines 128 - 131, Increase the response-head buffer
used in receiveHead within the upgrade flow so it can hold larger redirect
headers, including GitHub’s signed Location value; replace the current 1 KiB
redirect_buf in the upgrade-related logic with a larger size such as 8 KiB to
avoid truncation and failed downloads.

Comment thread src/upgrade.zig
Comment on lines +137 to +146
const body = try reader.allocRemaining(allocator, .limited(100 * 1024 * 1024));
defer allocator.free(body);

try dest_dir.writeFile(io, .{ .sub_path = archive, .data = body });

if (content_length) |_| {
std.debug.print(" Downloaded... ({s})\n", .{archive});
}

return allocator.dupe(u8, archive);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file list =="
git ls-files src/upgrade.zig

echo
echo "== outline =="
ast-grep outline src/upgrade.zig --view expanded || true

echo
echo "== relevant lines =="
sed -n '1,260p' src/upgrade.zig | cat -n

echo
echo "== searches for verification / checksum / signature / TLS / install logic =="
rg -n "checksum|sha256|signature|verify|verified|tls|certificate|http|download|extract|writeFile|self-update|upgrade|archive" src/upgrade.zig

Repository: christianhelle/openapi2zig

Length of output: 13591


🌐 Web query:

Zig std.http Client TLS certificate validation default behavior official documentation

💡 Result:

In Zig, the std.http.Client performs TLS certificate validation by default when making HTTPS requests [1][2]. Key aspects of its default behavior include: Root Certificate Scanning: Zig performs lazy scanning of system root certificates upon the first HTTPS request [1][3]. The client uses a ca_bundle (of type std.crypto.Certificate.Bundle) to manage these certificates [4][5]. If the server provides a certificate that cannot be verified against the system's root CA chain, the client will return an error, such as error.TlsCertificateNotVerified [1]. TLS Enforcement: TLS is enabled by default in std.http.Client [4][2]. It can be globally disabled by setting std.options.http_disable_tls = true in your project's root file [4][6]. When disabled, any attempt to make an HTTPS request will result in an error.TlsInitializationFailed [6]. Customization: As of the current development state of Zig, direct, first-class support for customizing certificate validation (such as disabling validation or providing custom trust anchors beyond the standard bundle) is limited or requires interacting with lower-level crypto APIs [7]. The implementation relies on std.crypto.tls.Client, which initiates the handshake and enforces verification [1][8]. Because these mechanisms are part of the standard library's internal implementation, behavior may evolve in future versions. Developers are encouraged to check the generated documentation for the specific version of the Zig compiler being used [5][9].

Citations:


Add checksum or signature verification for the release asset. src/upgrade.zig:137-146 trusts the downloaded archive before extracting and replacing the binary. TLS is already handled by std.http.Client; the missing step is authenticating the artifact itself with a published SHA-256 or signature.

🤖 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 `@src/upgrade.zig` around lines 137 - 146, The download flow in the
release-asset handling path trusts the archive after reader.allocRemaining and
dest_dir.writeFile, so add artifact authentication before returning the archive
name. Verify the downloaded body against a published SHA-256 checksum or
signature in the same upgrade path that writes the asset, and only proceed when
the integrity check passes. Use the existing download/extract flow in
src/upgrade.zig to locate the verification step so the binary is replaced only
after validation.

Comment thread src/upgrade.zig
Comment on lines +218 to +226
const current = version_info.VERSION;
const latest_trimmed = if (std.mem.startsWith(u8, latest_version, "v")) latest_version[1..] else latest_version;

std.debug.print(" Latest version: {s}\n", .{latest_version});

if (std.mem.eql(u8, current, latest_trimmed)) {
std.debug.print(" Already up to date.\n", .{});
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Version check is equality-only, not "is newer".

std.mem.eql(current, latest_trimmed) treats any difference as "upgrade available". If the running build is newer than the latest release (e.g. a local/dev build) or the tag formatting differs, this will proceed to download and replace with an older binary. Consider a semver comparison so only strictly-newer releases trigger the replace.

🤖 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 `@src/upgrade.zig` around lines 218 - 226, The upgrade check in the version
comparison is equality-only, so any non-matching build is treated as needing an
upgrade even when the current build is newer. Update the logic around the
current/latest comparison in upgrade.zig to use a proper semver ordering instead
of std.mem.eql, and only continue with the download/replace flow when
latest_version is strictly newer than version_info.VERSION. Keep the existing
latest_version trimming behavior, but route the decision through the version
comparison in the upgrade path.

Comment thread src/upgrade.zig
Comment on lines +242 to +249
const archive_path = try std.fs.path.join(allocator, &.{ tmp_dir_path, sub_dir_name, archive_name });
defer allocator.free(archive_path);

std.debug.print(" Extracting...\n", .{});
try extractArchive(allocator, io, archive_path, tmp_dir_path, platform);

const binary_name = if (isWindows(platform)) "openapi2zig.exe" else "openapi2zig";
const new_binary = try std.fs.path.join(allocator, &.{ tmp_dir_path, sub_dir_name, binary_name });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh release view --repo christianhelle/openapi2zig --json assets --jq '.assets[].name'

Repository: christianhelle/openapi2zig

Length of output: 292


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/upgrade.zig relevant sections =="
nl -ba src/upgrade.zig | sed -n '1,340p' | sed -n '180,320p'

Repository: christianhelle/openapi2zig

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmpdir="$(mktemp -d)"
asset="openapi2zig-linux-x86_64.tar.gz"

echo "Downloading $asset to $tmpdir"
gh release download --repo christianhelle/openapi2zig "$asset" -D "$tmpdir"

echo "Archive contents:"
python3 - <<'PY' "$tmpdir/$asset"
import sys, tarfile, os
path = sys.argv[1]
with tarfile.open(path, "r:gz") as tf:
    names = tf.getnames()
    for n in names:
        print(n)
PY

Repository: christianhelle/openapi2zig

Length of output: 249


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate extractArchive =="
rg -n "fn extractArchive|extractArchive\\(" src/upgrade.zig

echo
echo "== relevant section of src/upgrade.zig =="
sed -n '200,320p' src/upgrade.zig

echo
echo "== archive naming / tmp dir uses =="
rg -n "tmp_dir_path|sub_dir_name|tmp_sub|archive_name|new_binary|replaceBinary" src/upgrade.zig

Repository: christianhelle/openapi2zig

Length of output: 5417


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== release workflow files =="
fd -a -t f '.github/workflows|build|release|package' .

echo
echo "== search for archive/root directory names =="
rg -n "openapi2zig-upgrade|tar.gz|zip|archive|release asset|pkg|package" .github src . -g '!**/zig-cache/**' -g '!**/zig-out/**'

echo
echo "== inspect any workflow mentioning releases =="
for f in $(fd -a -t f '.github/workflows' .); do
  echo "--- $f ---"
  sed -n '1,260p' "$f"
done

Repository: christianhelle/openapi2zig

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== install.sh relevant lines =="
sed -n '80,260p' install.sh

echo
echo "== .github/workflows/release.yml relevant lines =="
sed -n '55,95p' .github/workflows/release.yml

echo
echo "== src/upgrade.zig extract + replace flow =="
sed -n '145,260p' src/upgrade.zig

Repository: christianhelle/openapi2zig

Length of output: 10665


Extract and look up the binary in the same directory. The release archives are flat (release/* is packaged at the archive root), so extracting to tmp_dir_path places openapi2zig there, but replaceBinary looks under tmp/openapi2zig-upgrade/. Extract into tmp_sub or resolve new_binary from tmp_dir_path.

🤖 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 `@src/upgrade.zig` around lines 242 - 249, The upgrade flow is extracting the
release archive to the wrong directory, so `replaceBinary` later looks in a
different path than where `extractArchive` placed `openapi2zig`. Update the
`upgrade` logic around `extractArchive` and `new_binary` so the archive is
extracted into `tmp_sub` or, alternatively, compute `new_binary` from the actual
extraction root (`tmp_dir_path`) to match the flat release layout and keep the
binary lookup consistent.

@christianhelle
christianhelle merged commit f9500de into main Jul 7, 2026
17 checks passed
@christianhelle
christianhelle deleted the update branch July 7, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant