Prepare opencode-rails alpha8

This commit is contained in:
2026-07-19 22:21:41 -07:00
parent 61beb17f55
commit a9add2a7c1
9 changed files with 96 additions and 17 deletions

View File

@@ -21,4 +21,12 @@ jobs:
with: with:
ruby-version: "4.0" ruby-version: "4.0"
bundler-cache: true bundler-cache: true
- name: Verify tag matches gem version
env:
RELEASE_TAG: ${{ github.ref_name }}
run: >-
ruby -Ilib -ropencode/rails_version -e
'expected = ENV.fetch("RELEASE_TAG").delete_prefix("v");
abort "tag #{expected.inspect} does not match gem #{Opencode::RAILS_VERSION.inspect}"
unless Opencode::RAILS_VERSION == expected'
- uses: rubygems/release-gem@052cc82692552de3ef2b81fd670e41d13cba8092 # v1.4.0 - uses: rubygems/release-gem@052cc82692552de3ef2b81fd670e41d13cba8092 # v1.4.0

View File

@@ -1,5 +1,21 @@
# Changelog # Changelog
## 0.0.1.alpha8 - 2026-07-20
### Bumped
- Runtime dependency `opencode-ruby` pinned to `= 0.0.1.alpha8`, carrying the
hardened SSE framing parser while retaining the alpha7 subscribe-before-
prompt and at-most-once reconnect contract.
### Changed
- Test the supported runtime surface on Ruby 3.2, 3.3, 3.4, and 4.0.
- Pin every third-party CI and release action to an exact reviewed commit and
use Ruby 4.0 for release builds.
- Fail the trusted-publishing job before release when the pushed tag does not
match `Opencode::RAILS_VERSION`.
## 0.0.1.alpha7 - 2026-07-18 ## 0.0.1.alpha7 - 2026-07-18
### Fixed ### Fixed

View File

@@ -2,6 +2,6 @@ source "https://rubygems.org"
gem "opencode-ruby", gem "opencode-ruby",
git: "https://github.com/ajaynomics/opencode-ruby.git", git: "https://github.com/ajaynomics/opencode-ruby.git",
ref: "78b6f9c9e9c7d58b699af1c3c17764acd33de798" ref: "9277646a4bb2cf25a8384ffc140b154f49ea5766"
gemspec gemspec

View File

@@ -17,15 +17,25 @@ Production-grade [OpenCode](https://opencode.ai) integration for Rails apps. Lay
## Install ## Install
After both alpha8 gems are confirmed on RubyGems, pin the lockstep tuple:
```ruby ```ruby
# Gemfile # Gemfile
gem "opencode-ruby", "= 0.0.1.alpha7" # wire client + Reply state machine gem "opencode-ruby", "= 0.0.1.alpha8" # wire client + Reply state machine
gem "opencode-rails", "= 0.0.1.alpha8"
```
Until publication is verified, validate this candidate checkout against the
exact `opencode-ruby` source it was tested with:
```ruby
# Gemfile
gem "opencode-ruby",
git: "https://github.com/ajaynomics/opencode-ruby.git",
ref: "9277646a4bb2cf25a8384ffc140b154f49ea5766"
# v0.0.1.alpha7 is a GitHub source release, not a RubyGems release. Pin the
# peeled release commit so a moved tag cannot change the code Bundler installs.
gem "opencode-rails", gem "opencode-rails",
git: "https://github.com/ajaynomics/opencode-rails.git", path: "../opencode-rails"
ref: "2a391ccad1d098e4bb51eb19b6ca52adaa79e5cb"
``` ```
```bash ```bash
@@ -34,16 +44,16 @@ bundle install
Runtime deps: `activerecord`, `activestorage`, `activesupport` (>= 7.1). Depends on `opencode-ruby` for the underlying HTTP/SSE primitives. Runtime deps: `activerecord`, `activestorage`, `activesupport` (>= 7.1). Depends on `opencode-ruby` for the underlying HTTP/SSE primitives.
During the alpha series both gems are pinned in lockstep. Version 0.0.1.alpha7 During the alpha series both gems are pinned in lockstep. Version 0.0.1.alpha8
uses a subscribe-ready-before-prompt transport contract and reconnects an retains the subscribe-ready-before-prompt transport contract and reconnects an
accepted turn without posting its prompt again. accepted turn without posting its prompt again, while hardening SSE framing.
`opencode-rails` 0.0.1.alpha7 is available as a GitHub source release but has `opencode-rails` 0.0.1.alpha8 is a release candidate and is not yet confirmed
not been published to RubyGems. The `release.yml` workflow is prepared for published on RubyGems. The `release.yml` workflow is prepared for RubyGems
RubyGems trusted publishing, but the gem's trusted publisher still has to be trusted publishing, but the gem's trusted publisher must be registered and
registered for that workflow and its `release` environment. Until then, pushing verified for that workflow and its `release` environment. Until the registry
a `v*` tag does not make the gem installable from RubyGems; use the exact Git result is verified, pushing a `v*` tag does not guarantee publication. Trusted
commit above. Trusted publishing does not require a long-lived RubyGems API key. publishing does not require a long-lived RubyGems API key.
## Quickstart ## Quickstart

View File

@@ -11,5 +11,5 @@
# We can't reuse the same constant from a second gem, so we use a # We can't reuse the same constant from a second gem, so we use a
# distinct, non-namespaced constant. # distinct, non-namespaced constant.
module Opencode module Opencode
RAILS_VERSION = "0.0.1.alpha7" RAILS_VERSION = "0.0.1.alpha8"
end end

View File

@@ -37,7 +37,7 @@ Gem::Specification.new do |spec|
# this gem builds on. During alpha both gems evolve in lockstep — we pin # this gem builds on. During alpha both gems evolve in lockstep — we pin
# exactly (= not ~>) so that consumers always pick the version this gem # exactly (= not ~>) so that consumers always pick the version this gem
# was tested against. # was tested against.
spec.add_runtime_dependency "opencode-ruby", "= 0.0.1.alpha7" spec.add_runtime_dependency "opencode-ruby", "= 0.0.1.alpha8"
# Rails sub-libraries used at runtime. Depending on these individually # Rails sub-libraries used at runtime. Depending on these individually
# (instead of the `rails` umbrella) avoids forcing host apps to load # (instead of the `rails` umbrella) avoids forcing host apps to load

View File

@@ -5,6 +5,7 @@ require "ripper"
class ReadmeTest < Minitest::Test class ReadmeTest < Minitest::Test
README_PATH = File.expand_path("../README.md", __dir__) README_PATH = File.expand_path("../README.md", __dir__)
GEMFILE_PATH = File.expand_path("../Gemfile", __dir__)
def setup def setup
@readme = File.read(README_PATH) @readme = File.read(README_PATH)
@@ -36,4 +37,17 @@ class ReadmeTest < Minitest::Test
assert Ripper.sexp(source), "README Ruby fence #{index + 1} has invalid syntax" assert Ripper.sexp(source), "README Ruby fence #{index + 1} has invalid syntax"
end end
end end
def test_install_contract_tracks_candidate_versions_and_client_source
gemfile = File.read(GEMFILE_PATH)
client_ref = gemfile[/gem "opencode-ruby".*?ref:\s*"([0-9a-f]{40})"/m, 1]
refute_nil client_ref, "Gemfile must pin opencode-ruby to an exact commit"
assert_includes @readme, %(gem "opencode-ruby", "= #{Opencode::VERSION}")
assert_includes @readme, %(gem "opencode-rails", "= #{Opencode::RAILS_VERSION}")
assert_includes @readme, %(ref: "#{client_ref}")
assert_includes @readme,
"`opencode-rails` #{Opencode::RAILS_VERSION} is a release candidate"
assert_includes @readme, "pushing a `v*` tag does not guarantee publication"
end
end end

View File

@@ -33,6 +33,21 @@ class ReleaseWorkflowTest < Minitest::Test
assert_equal "4.0", setup_ruby.dig("with", "ruby-version") assert_equal "4.0", setup_ruby.dig("with", "ruby-version")
assert_equal 1, steps.count { |step| step["uses"] == RELEASE_GEM_ACTION } assert_equal 1, steps.count { |step| step["uses"] == RELEASE_GEM_ACTION }
assert steps.filter_map { |step| step["uses"] }.all? { |uses| uses.match?(/@[0-9a-f]{40}\z/) }
refute steps.any? { |step| step.fetch("run", "").match?(/\bgem\s+push\b/) } refute steps.any? { |step| step.fetch("run", "").match?(/\bgem\s+push\b/) }
end end
def test_release_tag_must_match_the_gem_version_before_publish
steps = push_job.fetch("steps")
preflight_index = steps.index { |step| step["name"] == "Verify tag matches gem version" }
publish_index = steps.index { |step| step["uses"] == RELEASE_GEM_ACTION }
refute_nil preflight_index
refute_nil publish_index
assert_operator preflight_index, :<, publish_index
preflight = steps.fetch(preflight_index)
assert_equal "${{ github.ref_name }}", preflight.dig("env", "RELEASE_TAG")
assert_includes preflight.fetch("run"), "unless Opencode::RAILS_VERSION == expected"
end
end end

16
test/version_test.rb Normal file
View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
require "test_helper"
class Opencode::VersionTest < Minitest::Test
GEMSPEC_PATH = File.expand_path("../opencode-rails.gemspec", __dir__)
def test_alpha_versions_and_runtime_dependency_stay_in_lockstep
specification = Gem::Specification.load(GEMSPEC_PATH)
dependency = specification.runtime_dependencies.find { |item| item.name == "opencode-ruby" }
refute_nil dependency
assert_equal Opencode::RAILS_VERSION, Opencode::VERSION
assert_equal "= #{Opencode::RAILS_VERSION}", dependency.requirement.to_s
end
end