Compare commits
9 Commits
v0.0.1.alp
...
a9add2a7c1
| Author | SHA1 | Date | |
|---|---|---|---|
| a9add2a7c1 | |||
| 61beb17f55 | |||
| 8b78a558e1 | |||
| 4d975d99b3 | |||
|
|
b27d39edf4 | ||
| 75e4435d9d | |||
| 6148fc67da | |||
|
|
deeccb119e | ||
| fe953020d5 |
17
.github/workflows/release.yml
vendored
17
.github/workflows/release.yml
vendored
@@ -7,17 +7,26 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
push:
|
push:
|
||||||
|
if: ${{ github.server_url == 'https://github.com' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
id-token: write
|
id-token: write
|
||||||
environment: release
|
environment: release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: ruby/setup-ruby@v1
|
- uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1.319.0
|
||||||
with:
|
with:
|
||||||
ruby-version: ruby
|
ruby-version: "4.0"
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
- uses: rubygems/release-gem@v1
|
- 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
|
||||||
|
|||||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
ruby: ["3.2", "3.3", "3.4"]
|
ruby: ["3.2", "3.3", "3.4", "4.0"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- name: Set up Ruby ${{ matrix.ruby }}
|
- name: Set up Ruby ${{ matrix.ruby }}
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1.319.0
|
||||||
with:
|
with:
|
||||||
ruby-version: ${{ matrix.ruby }}
|
ruby-version: ${{ matrix.ruby }}
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|||||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -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
|
||||||
|
|||||||
2
Gemfile
2
Gemfile
@@ -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
|
||||||
|
|||||||
73
README.md
73
README.md
@@ -17,10 +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" # wire client + Reply state machine
|
gem "opencode-ruby", "= 0.0.1.alpha8" # wire client + Reply state machine
|
||||||
gem "opencode-rails" # AR-coupled session/turn/artifact stack
|
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"
|
||||||
|
|
||||||
|
gem "opencode-rails",
|
||||||
|
path: "../opencode-rails"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -29,13 +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.
|
||||||
|
|
||||||
Releases use RubyGems trusted publishing. Register `release.yml` as the gem's
|
`opencode-rails` 0.0.1.alpha8 is a release candidate and is not yet confirmed
|
||||||
trusted publisher (using the `release` environment); after that, a `v*` tag
|
published on RubyGems. The `release.yml` workflow is prepared for RubyGems
|
||||||
publishes without a long-lived RubyGems API key.
|
trusted publishing, but the gem's trusted publisher must be registered and
|
||||||
|
verified for that workflow and its `release` environment. Until the registry
|
||||||
|
result is verified, pushing a `v*` tag does not guarantee publication. Trusted
|
||||||
|
publishing does not require a long-lived RubyGems API key.
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
@@ -49,13 +67,29 @@ Opencode::ErrorReporter.adapter = ->(error, **opts) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# app/services/noop_reply_observer.rb
|
||||||
|
#
|
||||||
|
# Turn requires an observer factory even when the app does not need live
|
||||||
|
# partial rendering. For a streaming UI, replace this with an observer that
|
||||||
|
# persists/broadcasts selected ReplyObserver callbacks (and throttle writes).
|
||||||
|
class NoopReplyObserver
|
||||||
|
include Opencode::ReplyObserver
|
||||||
|
|
||||||
|
def watch(reply)
|
||||||
|
reply.add_observer(self)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# app/jobs/generate_response_job.rb
|
# app/jobs/generate_response_job.rb
|
||||||
class GenerateResponseJob < ApplicationJob
|
class GenerateResponseJob < ApplicationJob
|
||||||
def perform(assistant_message)
|
def perform(assistant_message)
|
||||||
conversation = assistant_message.conversation
|
conversation = assistant_message.conversation
|
||||||
user_message = conversation.messages.where(role: :user).last
|
user_message = conversation.messages.where(role: :user).last
|
||||||
client = Opencode::Client.new(base_url: ENV["OPENCODE_URL"])
|
client = Opencode::Client.new(base_url: ENV.fetch("OPENCODE_URL"))
|
||||||
|
|
||||||
session = Opencode::Session.new(
|
session = Opencode::Session.new(
|
||||||
conversation,
|
conversation,
|
||||||
@@ -68,9 +102,15 @@ class GenerateResponseJob < ApplicationJob
|
|||||||
subject: conversation,
|
subject: conversation,
|
||||||
query_text: user_message.content,
|
query_text: user_message.content,
|
||||||
client: client,
|
client: client,
|
||||||
session: session,
|
session_for: session,
|
||||||
|
observer_factory: ->(_message) { NoopReplyObserver.new },
|
||||||
|
system_context: ->(record) { "You are assisting with #{record.title}." },
|
||||||
|
agent_name: ->(_record) { ENV.fetch("OPENCODE_AGENT", "build") },
|
||||||
|
tracer: ->(name, **payload) {
|
||||||
|
ActiveSupport::Notifications.instrument("assistant.#{name}", payload)
|
||||||
|
},
|
||||||
on_turn_finished: ->(result) {
|
on_turn_finished: ->(result) {
|
||||||
# result.status #=> :completed | :error | :cancelled
|
# result.status #=> :completed | :cancelled | :error | :failed
|
||||||
# result.message #=> the AR row (reloaded)
|
# result.message #=> the AR row (reloaded)
|
||||||
# result.duration_ms
|
# result.duration_ms
|
||||||
}
|
}
|
||||||
@@ -87,7 +127,16 @@ class GenerateResponseJob < ApplicationJob
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
The host's record (here `conversation`) must respond to `#title`, `#opencode_session_id`, `#opencode_session_id=`, `#with_lock(&block)`, `#update!`, `#reload`, `#id`. The host's message record (here `assistant_message`) must respond to `#error!(content)`, `#update_columns(...)`, `#with_lock(&block)`, `#reload`, `#pending?`.
|
The host record (here `conversation`) must respond to `#title`,
|
||||||
|
`#opencode_session_id`, `#opencode_session_id=`, `#with_lock(&block)`,
|
||||||
|
`#update!`, `#reload`, and `#id`. The assistant message must respond to `#id`,
|
||||||
|
`#reload`, `#cancelled?`, `#finalize!(**attrs)`, `#update!(**attrs)`, and
|
||||||
|
`#error!(content)`. A non-no-op observer may impose additional record methods
|
||||||
|
for its own live snapshots.
|
||||||
|
|
||||||
|
`Opencode::Turn` is an internal, alpha-stage composition seam. Its keyword
|
||||||
|
constructor is intentionally explicit and may change before 1.0, so keep this
|
||||||
|
wiring in one host service/job and keep the gem source pinned exactly.
|
||||||
|
|
||||||
## What you get
|
## What you get
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
53
test/readme_test.rb
Normal file
53
test/readme_test.rb
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "test_helper"
|
||||||
|
require "ripper"
|
||||||
|
|
||||||
|
class ReadmeTest < Minitest::Test
|
||||||
|
README_PATH = File.expand_path("../README.md", __dir__)
|
||||||
|
GEMFILE_PATH = File.expand_path("../Gemfile", __dir__)
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@readme = File.read(README_PATH)
|
||||||
|
@quickstart = @readme[/^## Quickstart\n(?<body>.*?)(?=^## )/m, :body]
|
||||||
|
refute_nil @quickstart, "README must retain a Quickstart section"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_quickstart_turn_call_documents_every_required_keyword
|
||||||
|
turn_call = @quickstart[/Opencode::Turn\.new\((?<args>.*?)^\s*\)\.call/m, :args]
|
||||||
|
refute_nil turn_call, "Quickstart must contain an Opencode::Turn.new(...).call example"
|
||||||
|
|
||||||
|
documented = turn_call.scan(/^\s*([a-z_]+):/).flatten.map(&:to_sym)
|
||||||
|
parameters = Opencode::Turn.instance_method(:initialize).parameters
|
||||||
|
required = parameters.filter_map { |kind, name| name if kind == :keyreq }
|
||||||
|
accepted = parameters.filter_map { |kind, name| name if %i[keyreq key].include?(kind) }
|
||||||
|
|
||||||
|
assert_empty required - documented,
|
||||||
|
"Quickstart is missing required Turn keywords: #{(required - documented).join(", ")}"
|
||||||
|
assert_empty documented - accepted,
|
||||||
|
"Quickstart uses unsupported Turn keywords: #{(documented - accepted).join(", ")}"
|
||||||
|
refute_includes documented, :session
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_readme_ruby_fences_parse
|
||||||
|
ruby_fences = @readme.scan(/```ruby\n(.*?)```/m).flatten
|
||||||
|
refute_empty ruby_fences
|
||||||
|
|
||||||
|
ruby_fences.each_with_index do |source, index|
|
||||||
|
assert Ripper.sexp(source), "README Ruby fence #{index + 1} has invalid syntax"
|
||||||
|
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
|
||||||
53
test/release_workflow_test.rb
Normal file
53
test/release_workflow_test.rb
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "minitest/autorun"
|
||||||
|
require "yaml"
|
||||||
|
|
||||||
|
class ReleaseWorkflowTest < Minitest::Test
|
||||||
|
ROOT = File.expand_path("..", __dir__)
|
||||||
|
WORKFLOW_PATH = File.join(ROOT, ".github", "workflows", "release.yml")
|
||||||
|
SETUP_RUBY_ACTION = "ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600"
|
||||||
|
RELEASE_GEM_ACTION = "rubygems/release-gem@052cc82692552de3ef2b81fd670e41d13cba8092"
|
||||||
|
|
||||||
|
def workflow
|
||||||
|
@workflow ||= YAML.safe_load(File.read(WORKFLOW_PATH), aliases: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def push_job
|
||||||
|
workflow.fetch("jobs").fetch("push")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_release_job_is_inert_on_non_github_runners
|
||||||
|
assert_equal "${{ github.server_url == 'https://github.com' }}", push_job.fetch("if")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_release_job_keeps_the_trusted_publisher_boundary
|
||||||
|
assert_equal "release", push_job.fetch("environment")
|
||||||
|
assert_equal(
|
||||||
|
{ "contents" => "write", "id-token" => "write" },
|
||||||
|
push_job.fetch("permissions")
|
||||||
|
)
|
||||||
|
|
||||||
|
steps = push_job.fetch("steps")
|
||||||
|
setup_ruby = steps.find { |step| step["uses"] == SETUP_RUBY_ACTION }
|
||||||
|
|
||||||
|
assert_equal "4.0", setup_ruby.dig("with", "ruby-version")
|
||||||
|
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/) }
|
||||||
|
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
|
||||||
16
test/version_test.rb
Normal file
16
test/version_test.rb
Normal 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
|
||||||
67
test/workflow_contract_test.rb
Normal file
67
test/workflow_contract_test.rb
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "minitest/autorun"
|
||||||
|
require "yaml"
|
||||||
|
|
||||||
|
class WorkflowContractTest < Minitest::Test
|
||||||
|
ROOT = File.expand_path("..", __dir__)
|
||||||
|
WORKFLOW_DIRECTORY = File.join(ROOT, ".github", "workflows")
|
||||||
|
TEST_WORKFLOW_PATH = File.join(WORKFLOW_DIRECTORY, "test.yml")
|
||||||
|
ACTION_PINS = {
|
||||||
|
"actions/checkout" => "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0",
|
||||||
|
"ruby/setup-ruby" => "003a5c4d8d6321bd302e38f6f0ec593f77f06600",
|
||||||
|
"rubygems/release-gem" => "052cc82692552de3ef2b81fd670e41d13cba8092"
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def test_matrix_covers_every_supported_ruby
|
||||||
|
workflow = YAML.safe_load(File.read(TEST_WORKFLOW_PATH), aliases: false)
|
||||||
|
versions = workflow.dig("jobs", "test", "strategy", "matrix", "ruby")
|
||||||
|
|
||||||
|
assert_equal %w[3.2 3.3 3.4 4.0], versions
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_every_third_party_action_uses_its_reviewed_commit
|
||||||
|
action_uses = Dir[File.join(WORKFLOW_DIRECTORY, "*.{yml,yaml}")].sort.flat_map do |path|
|
||||||
|
workflow = YAML.safe_load(File.read(path), aliases: false)
|
||||||
|
|
||||||
|
workflow_uses(workflow)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 5, action_uses.length
|
||||||
|
action_uses.each do |action_use|
|
||||||
|
action, separator, revision = action_use.rpartition("@")
|
||||||
|
|
||||||
|
assert_equal "@", separator
|
||||||
|
assert_equal ACTION_PINS.fetch(action), revision
|
||||||
|
assert_match(/\A[0-9a-f]{40}\z/, revision)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_action_discovery_only_reads_workflow_action_locations
|
||||||
|
workflow = YAML.safe_load(<<~YAML, aliases: false)
|
||||||
|
jobs:
|
||||||
|
reusable:
|
||||||
|
uses: "owner/workflow@revision"
|
||||||
|
with:
|
||||||
|
uses: ordinary-job-input
|
||||||
|
test:
|
||||||
|
steps:
|
||||||
|
- uses: "owner/action@revision"
|
||||||
|
with:
|
||||||
|
uses: ordinary-step-input
|
||||||
|
YAML
|
||||||
|
|
||||||
|
assert_equal %w[owner/workflow@revision owner/action@revision], workflow_uses(workflow)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def workflow_uses(node)
|
||||||
|
node.fetch("jobs").values.flat_map do |job|
|
||||||
|
action_uses = job.key?("uses") ? [job.fetch("uses")] : []
|
||||||
|
step_uses = job.fetch("steps", []).filter_map { |step| step["uses"] }
|
||||||
|
|
||||||
|
action_uses.concat(step_uses)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user