advanced pipeline

This commit is contained in:
2026-08-25 23:50:11 +02:00
parent c8dc153e80
commit 3f5caf947f
30 changed files with 44 additions and 42 deletions
+13
View File
@@ -0,0 +1,13 @@
inherit_gem:
rubocop-rails-omakase: rubocop.yml
AllCops:
NewCops: enable
TargetRubyVersion: 3.2
Exclude:
- "bin/**/*"
- "db/**/*"
- "config/**/*"
- "examples/**/*"
- "spec/dummy/**/*"
- "vendor/**/*"
+1
View File
@@ -10,4 +10,5 @@ group :development, :test do
gem "shoulda-matchers", "~> 6.0" gem "shoulda-matchers", "~> 6.0"
gem "webmock", "~> 3.0" gem "webmock", "~> 3.0"
gem "debug", platforms: %i[mri windows] gem "debug", platforms: %i[mri windows]
gem "rubocop-rails-omakase", require: false
end end
+11 -1
View File
@@ -761,6 +761,16 @@ bundle exec rspec
## Development ## Development
This repository is a **read-only split mirror** — development happens in the This repository is a **read-only split mirror** — development happens in the
[`tools/teletypegames`](https://git.teletypegames.org/tools/teletypegames) [`services/teletypegames`](https://git.teletypegames.org/services/teletypegames)
monorepo under `libs/ruby/warp_engine`, and CI republishes the mirror on every monorepo under `libs/ruby/warp_engine`, and CI republishes the mirror on every
change. Please do not open pull requests against the mirror. change. Please do not open pull requests against the mirror.
Two rules the monorepo's pipeline enforces before anything is mirrored or
published:
- **Every commit that touches the engine raises `WarpEngine::VERSION`.** A
pre-commit hook checks it locally, the pipeline's first step checks it again
over the pushed commits, and a `warp_engine-v*` tag must name the version the
tree actually holds.
- **RuboCop is green** (`.rubocop.yml`, rubocop-rails-omakase), and the suite
passes — in that order, before the gem is built.
@@ -1,5 +1,4 @@
module WarpEngine module WarpEngine
module SubjectAuthentication module SubjectAuthentication
extend ActiveSupport::Concern extend ActiveSupport::Concern
@@ -1,5 +1,4 @@
module WarpEngine module WarpEngine
module UpdateAuthentication module UpdateAuthentication
extend ActiveSupport::Concern extend ActiveSupport::Concern
@@ -1,5 +1,4 @@
module WarpEngine module WarpEngine
class Api::Auth::DevicesController < ApiController class Api::Auth::DevicesController < ApiController
before_action :ensure_identity_configured before_action :ensure_identity_configured
@@ -1,5 +1,4 @@
module WarpEngine module WarpEngine
class Api::Auth::TokensController < ApiController class Api::Auth::TokensController < ApiController
resource_description do resource_description do
short "Client tokens" short "Client tokens"
@@ -1,5 +1,4 @@
module WarpEngine module WarpEngine
class Api::ServiceController < ApiController class Api::ServiceController < ApiController
resource_description do resource_description do
short "Service descriptor" short "Service descriptor"
@@ -1,6 +1,5 @@
module WarpEngine module WarpEngine
module Build module Build
class ConfigsController < ApiController class ConfigsController < ApiController
resource_description do resource_description do
short "CI pipeline configs" short "CI pipeline configs"
-2
View File
@@ -1,5 +1,4 @@
module WarpEngine module WarpEngine
class DeviceGrant < ApplicationRecord class DeviceGrant < ApplicationRecord
self.table_name = "device_grants" self.table_name = "device_grants"
@@ -58,7 +57,6 @@ module WarpEngine
end end
def self.generate_user_code def self.generate_user_code
10.times do 10.times do
candidate = Array.new(USER_CODE_LENGTH) { USER_CODE_ALPHABET.chars.sample }.join candidate = Array.new(USER_CODE_LENGTH) { USER_CODE_ALPHABET.chars.sample }.join
return candidate unless exists?(user_code: candidate) return candidate unless exists?(user_code: candidate)
+1 -1
View File
@@ -16,7 +16,7 @@ module WarpEngine
validate :c64_cannot_be_web_playable validate :c64_cannot_be_web_playable
def self.latest_non_dev(releases) def self.latest_non_dev(releases)
sorted = releases.sort_by { |r| [r.created_at || Time.at(0), r.id] }.reverse sorted = releases.sort_by { |r| [ r.created_at || Time.at(0), r.id ] }.reverse
candidates = sorted.reject { |r| r.version.to_s.start_with?("dev-") } candidates = sorted.reject { |r| r.version.to_s.start_with?("dev-") }
candidates.empty? ? sorted.first : candidates.first candidates.empty? ? sorted.first : candidates.first
end end
@@ -1,7 +1,6 @@
module WarpEngine module WarpEngine
module Platforms module Platforms
module Builds module Builds
module BuildLinuxArm64 module BuildLinuxArm64
extend ActiveSupport::Concern extend ActiveSupport::Concern
@@ -1,5 +1,4 @@
module WarpEngine module WarpEngine
class DeviceGrantService class DeviceGrantService
class NotConfigured < StandardError; end class NotConfigured < StandardError; end
class UnknownCode < StandardError; end class UnknownCode < StandardError; end
@@ -2,7 +2,6 @@ require "warp_engine/access_denied"
module WarpEngine module WarpEngine
class DownloadService class DownloadService
Denied = WarpEngine::AccessDenied Denied = WarpEngine::AccessDenied
include AccessPolicyGuard include AccessPolicyGuard
@@ -1,6 +1,5 @@
module WarpEngine module WarpEngine
class FileManagerService class FileManagerService
def base_path def base_path
@base_path ||= Pathname.new(WarpEngine.config.file_container_path) @base_path ||= Pathname.new(WarpEngine.config.file_container_path)
end end
-1
View File
@@ -2,7 +2,6 @@ require "warp_engine/access_denied"
module WarpEngine module WarpEngine
class FileService class FileService
include AccessPolicyGuard include AccessPolicyGuard
def show(input, subject: nil) def show(input, subject: nil)
@@ -1,6 +1,5 @@
module WarpEngine module WarpEngine
class PublishService class PublishService
NOTIFICATION = "warp_engine.publish".freeze NOTIFICATION = "warp_engine.publish".freeze
def publish(input) def publish(input)
+1 -1
View File
@@ -12,7 +12,7 @@ namespace :warp_engine do
checklist = WarpEngine::AssetCoverageChecklist.new( checklist = WarpEngine::AssetCoverageChecklist.new(
coverage, only_missing: ENV["ONLY"] == "missing" coverage, only_missing: ENV["ONLY"] == "missing"
) )
puts checklist.to_s puts checklist
if ENV["STRICT"] == "1" && coverage.totals[:missing].positive? if ENV["STRICT"] == "1" && coverage.totals[:missing].positive?
abort "\nSTRICT: #{coverage.totals[:missing]} expected assets are missing." abort "\nSTRICT: #{coverage.totals[:missing]} expected assets are missing."
-1
View File
@@ -13,7 +13,6 @@ require "warp_engine/images"
require "warp_engine/ci" require "warp_engine/ci"
module WarpEngine module WarpEngine
def self.table_name_prefix def self.table_name_prefix
"" ""
end end
-3
View File
@@ -1,7 +1,5 @@
module WarpEngine module WarpEngine
module AccessPolicy module AccessPolicy
class Open class Open
def visible_software_scope(subject: nil) def visible_software_scope(subject: nil)
WarpEngine::Software.kept WarpEngine::Software.kept
@@ -39,7 +37,6 @@ module WarpEngine
end end
class Access class Access
attr_reader :gated, :entitled, :price_cents, :currency, :purchase_url, :web_url attr_reader :gated, :entitled, :price_cents, :currency, :purchase_url, :web_url
def initialize(gated: false, entitled: true, price_cents: nil, currency: nil, def initialize(gated: false, entitled: true, price_cents: nil, currency: nil,
-1
View File
@@ -1,5 +1,4 @@
module WarpEngine module WarpEngine
module CI module CI
class Error < StandardError; end class Error < StandardError; end
-1
View File
@@ -1,6 +1,5 @@
module WarpEngine module WarpEngine
class Configuration class Configuration
attr_accessor :site_url, attr_accessor :site_url,
:file_container_path, :file_container_path,
:update_secret, :update_secret,
-2
View File
@@ -1,7 +1,5 @@
module WarpEngine module WarpEngine
module Images module Images
class HostModel class HostModel
URL_PREFIX = "/api/image".freeze URL_PREFIX = "/api/image".freeze
-1
View File
@@ -1,5 +1,4 @@
module WarpEngine module WarpEngine
module Storage module Storage
Location = Struct.new(:kind, :path, :url, keyword_init: true) do Location = Struct.new(:kind, :path, :url, keyword_init: true) do
def file? = kind == :file def file? = kind == :file
+1 -1
View File
@@ -1,5 +1,5 @@
module WarpEngine module WarpEngine
VERSION = "0.9.0" VERSION = "0.9.1"
VERSION_HEADER = "WarpEngine-Version".freeze VERSION_HEADER = "WarpEngine-Version".freeze
end end
+4 -4
View File
@@ -15,7 +15,7 @@ RSpec.describe WarpEngine::CI::Gitlab::Client do
describe "repos" do describe "repos" do
it "lists repos" do it "lists repos" do
repos = [{ "id" => 1, "path" => "game1" }] repos = [ { "id" => 1, "path" => "game1" } ]
stub_gl(:get, "/api/v4/projects?membership=true&per_page=100&simple=true", body: repos) stub_gl(:get, "/api/v4/projects?membership=true&per_page=100&simple=true", body: repos)
expect(client.list_repos).to eq(repos) expect(client.list_repos).to eq(repos)
@@ -39,7 +39,7 @@ RSpec.describe WarpEngine::CI::Gitlab::Client do
describe "secrets (variables)" do describe "secrets (variables)" do
it "lists variables" do it "lists variables" do
vars = [{ "key" => "application_token" }] vars = [ { "key" => "application_token" } ]
stub_gl(:get, "/api/v4/projects/1/variables", body: vars) stub_gl(:get, "/api/v4/projects/1/variables", body: vars)
expect(client.list_secrets(1)).to eq(vars) expect(client.list_secrets(1)).to eq(vars)
@@ -68,7 +68,7 @@ RSpec.describe WarpEngine::CI::Gitlab::Client do
describe "pipelines" do describe "pipelines" do
it "lists pipelines" do it "lists pipelines" do
pipelines = [{ "id" => 1, "status" => "success" }] pipelines = [ { "id" => 1, "status" => "success" } ]
stub_gl(:get, "/api/v4/projects/42/pipelines?page=1&per_page=25", body: pipelines) stub_gl(:get, "/api/v4/projects/42/pipelines?page=1&per_page=25", body: pipelines)
expect(client.list_pipelines(42)).to eq(pipelines) expect(client.list_pipelines(42)).to eq(pipelines)
@@ -76,7 +76,7 @@ RSpec.describe WarpEngine::CI::Gitlab::Client do
it "gets latest pipeline" do it "gets latest pipeline" do
pipeline = { "id" => 5, "status" => "running" } pipeline = { "id" => 5, "status" => "running" }
stub_gl(:get, "/api/v4/projects/42/pipelines?per_page=1&sort=desc", body: [pipeline]) stub_gl(:get, "/api/v4/projects/42/pipelines?per_page=1&sort=desc", body: [ pipeline ])
expect(client.latest_pipeline(42)).to eq(pipeline) expect(client.latest_pipeline(42)).to eq(pipeline)
end end
+3 -3
View File
@@ -15,7 +15,7 @@ RSpec.describe WarpEngine::CI::Woodpecker::Client do
describe "repos" do describe "repos" do
it "lists repos" do it "lists repos" do
repos = [{ "id" => 1, "name" => "game1" }] repos = [ { "id" => 1, "name" => "game1" } ]
stub_wp(:get, "/api/repos", body: repos) stub_wp(:get, "/api/repos", body: repos)
expect(client.list_repos).to eq(repos) expect(client.list_repos).to eq(repos)
@@ -37,7 +37,7 @@ RSpec.describe WarpEngine::CI::Woodpecker::Client do
describe "secrets" do describe "secrets" do
it "lists secrets" do it "lists secrets" do
secrets = [{ "name" => "application_token" }] secrets = [ { "name" => "application_token" } ]
stub_wp(:get, "/api/repos/1/secrets", body: secrets) stub_wp(:get, "/api/repos/1/secrets", body: secrets)
expect(client.list_secrets(1)).to eq(secrets) expect(client.list_secrets(1)).to eq(secrets)
@@ -66,7 +66,7 @@ RSpec.describe WarpEngine::CI::Woodpecker::Client do
describe "pipelines" do describe "pipelines" do
it "lists pipelines" do it "lists pipelines" do
pipelines = [{ "number" => 1, "status" => "success" }] pipelines = [ { "number" => 1, "status" => "success" } ]
stub_wp(:get, "/api/repos/42/pipelines?page=1&perPage=25", body: pipelines) stub_wp(:get, "/api/repos/42/pipelines?page=1&perPage=25", body: pipelines)
expect(client.list_pipelines(42)).to eq(pipelines) expect(client.list_pipelines(42)).to eq(pipelines)
+7 -6
View File
@@ -20,12 +20,13 @@ RSpec.describe WarpEngine::PlatformLink, type: :model do
end end
end end
describe "default scope" do describe "scopes" do
it "excludes soft-deleted records" do it "keeps soft-deleted records out of .kept, not out of .all" do
active = create(:platform_link) active = create(:platform_link)
create(:platform_link, deleted_at: Time.current) deleted = create(:platform_link, deleted_at: Time.current)
expect(WarpEngine::PlatformLink.all).to eq([active]) expect(WarpEngine::PlatformLink.kept).to eq([ active ])
expect(WarpEngine::PlatformLink.all).to contain_exactly(active, deleted)
end end
it "does not order: ordering is asked for, not inherited" do it "does not order: ordering is asked for, not inherited" do
@@ -52,7 +53,7 @@ RSpec.describe WarpEngine::PlatformLink, type: :model do
create(:platform_link, platform: "love") create(:platform_link, platform: "love")
result = WarpEngine::PlatformLink.for_platform("tic80") result = WarpEngine::PlatformLink.for_platform("tic80")
expect(result).to eq([tic80_link]) expect(result).to eq([ tic80_link ])
end end
it "returns empty array for platform without links" do it "returns empty array for platform without links" do
+1 -1
View File
@@ -18,7 +18,7 @@ RSpec.describe WarpEngine::Software, type: :model do
active = create(:software) active = create(:software)
create(:software, deleted_at: Time.current) create(:software, deleted_at: Time.current)
expect(WarpEngine::Software.kept).to eq([active]) expect(WarpEngine::Software.kept).to eq([ active ])
end end
it "includes soft-deleted records without scope" do it "includes soft-deleted records without scope" do
+1 -1
View File
@@ -11,7 +11,7 @@ RSpec.describe WarpEngine::BuildsService do
end end
it "returns c64 with only cartridge" do it "returns c64 with only cartridge" do
expect(result[:platforms]["c64"][:kinds]).to eq(["cartridge"]) expect(result[:platforms]["c64"][:kinds]).to eq([ "cartridge" ])
end end
it "returns allKinds matching WarpEngine::ReleaseAsset::KINDS" do it "returns allKinds matching WarpEngine::ReleaseAsset::KINDS" do