diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..74eda3b --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby + +require "rbconfig" + +root = IO.popen([ "git", "rev-parse", "--show-toplevel" ], &:read).strip +check = File.join(root, "script", "warp_engine_version_check.rb") + +exit 0 unless File.exist?(check) + +exec(RbConfig.ruby, check, "--staged") diff --git a/.woodpecker/deploy.yaml b/.woodpecker/deploy.yaml new file mode 100644 index 0000000..9e97551 --- /dev/null +++ b/.woodpecker/deploy.yaml @@ -0,0 +1,117 @@ +# Éles deploy: master push -> rubocop -> host teszt -> pull + függőségek + restart. +# +# A woodpecker-agent ugyanabban a stackben fut, mint az api és a frontend, ezért +# a deploy nem SSH-zik: a hoszt docker socketjét használja. A stack könyvtára a +# konténerben UGYANARRA az útvonalra van mountolva, mint a hoszton — különben a +# compose relatív bind mountjai (./data, ./apps) máshova oldódnának fel. +# +# Feltételek: +# - Woodpecker: a repónál engedélyezni kell a "trusted: volumes" jelölést +# (Repo settings -> Trusted, admin joggal), különben a mountok tiltottak. +# - Woodpecker secret: forge_token — Forgejo token a services/teletypegames +# olvasásához (ugyanaz, amit a warp_engine workflow használ). +# - A szerveren /srv/stacks/teletype-games egy master-en álló git checkout, +# benne az api és frontend konténerek futnak (container_name: api, frontend). + +when: + - event: push + branch: master + - event: manual + +services: + - name: mysql + image: mysql:8 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + MYSQL_DATABASE: softwares # a zeitwerk:check production modban bootol; a softwares_test-et a db:test:prepare csinalja + +steps: + rubocop: + image: ruby:3.3 + environment: + BUNDLE_PATH: .bundle-ci # a workspace-ben marad, a teszt step újrahasználja + commands: + - apt-get update && apt-get install -y --no-install-recommends default-libmysqlclient-dev + - cd apps/api + - bundle install --jobs 4 + - bundle exec rubocop + + test-host: + image: ruby:3.3 + environment: + RAILS_ENV: test + DB_HOST: mysql + DB_USER: root + DB_NAME: softwares + FILE_CONTAINER_PATH: /tmp/softwares + IMAGE_CONTAINER_PATH: /tmp/images + UPDATE_SECRET: ci-test + SECRET_KEY_BASE: ci-test + BUNDLE_PATH: .bundle-ci + commands: + - apt-get update && apt-get install -y --no-install-recommends default-libmysqlclient-dev default-mysql-client + - mkdir -p /tmp/softwares /tmp/images + - cd apps/api + - bundle install --jobs 4 + - | + echo "==> Varakozas a mysql-re" + for i in $(seq 1 60); do + if mysqladmin ping -h mysql --silent 2>/dev/null; then break; fi + sleep 2 + done + - bundle exec rails db:test:prepare + - bundle exec rspec + # Production modu eager load: a sema-betoltott teszt adatbazisra mutatunk, + # hogy a boot ne egy ures DB-n haljon el. + - RAILS_ENV=production DB_NAME=softwares_test bundle exec rails zeitwerk:check + + pull: + image: alpine/git + environment: + FORGE_TOKEN: + from_secret: forge_token + volumes: + - /srv/stacks/teletype-games:/srv/stacks/teletype-games + commands: + - git config --global --add safe.directory /srv/stacks/teletype-games + - cd /srv/stacks/teletype-games + # A szerver checkoutja detached HEAD-en is állhat: akkor a pull "sikeres", + # de a futó kód nem mozdul. Inkább bukjunk el itt. + - | + BRANCH="$(git symbolic-ref --short -q HEAD || true)" + if [ "$BRANCH" != "master" ]; then + echo "A checkout nem master-en all (HEAD: $${BRANCH:-detached}), a deploy megall." + exit 1 + fi + - TOKEN="$$(printf '%s' "$${FORGE_TOKEN}" | tr -d '[:space:]')" + - git fetch "https://ci:$${TOKEN}@git.teletypegames.org/services/teletypegames.git" master + - git merge --ff-only FETCH_HEAD + - | + HEAD_SHA="$(git rev-parse HEAD)" + echo "==> A szerver most itt all: $HEAD_SHA" + if [ "$HEAD_SHA" != "$CI_COMMIT_SHA" ]; then + echo "Megjegyzes: ez nem a pipeline commitja ($CI_COMMIT_SHA) — kozben ujabb push jott." + fi + when: + - event: [ push, manual ] + branch: master + + restart: + image: docker:cli + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + # Csak az api és a frontend indul újra. A stack többi szolgáltatását + # (traefik, gitea, woodpecker, mysql) nem bántjuk: a woodpecker-server + # restartja épp ezt a pipeline-t vágná el. + - docker exec api bundle install + # A migracio a restart elott fut: a motor migraciois utvonala a hoszt + # db:migrate-jaban van, tehat ez a warp_engine migraciot is elvegzi. + - docker exec api bundle exec rails db:migrate + - docker restart api + - docker exec frontend npm install + - docker restart frontend + - docker ps --filter name=api --filter name=frontend --format '{{.Names}} {{.Status}}' + when: + - event: [ push, manual ] + branch: master diff --git a/.woodpecker.yaml b/.woodpecker/warp_engine.yaml similarity index 68% rename from .woodpecker.yaml rename to .woodpecker/warp_engine.yaml index d2cc36c..65a61b1 100644 --- a/.woodpecker.yaml +++ b/.woodpecker/warp_engine.yaml @@ -1,7 +1,8 @@ -# Read-only split mirror: a libs/ruby/warp_engine alkönyvtárat kitükrözi a -# engines/warp_engine repóba (fejlesztés itt, a monorepóban történik; a tükör -# csak publikálásra való). Tag-elt release (warp_engine-v*) esetén a gem a -# Forgejo rubygems registry-be is felmegy. +# WarpEngine gem: verzió -> rubocop -> teszt -> tükrözés -> gem kiadás. +# +# A fejlesztés ebben a monorepóban történik; a engines/warp_engine repó csak +# publikálásra való read-only tükör, ezért CI-t oda nincs értelme tenni: a +# subtree split minden alkalommal felülírja. # # Szükséges Woodpecker secret: forge_token — Forgejo access token # repository:write (engines/warp_engine) és package:write joggal. @@ -29,16 +30,41 @@ services: MYSQL_DATABASE: warp_engine_test steps: - # A motor tesztje kapuzza a tobbit: ha bukik, se a tukrozes, se a gem - # kiadasa nem fut le. A mirror repoba nincs ertelme CI-t tenni, mert azt a - # subtree split minden alkalommal feluliria. + # Első kapu: a motor nem módosulhat verzióemelés nélkül. Ugyanaz a szkript + # fut a .githooks/pre-commit hookban is, csak --staged módban. + version-bumped: + image: ruby:3.3 + commands: + - ruby script/warp_engine_version_check.rb --range "$CI_PREV_COMMIT_SHA" "$CI_COMMIT_SHA" + when: + - event: [ push, manual ] + + # Tag esetén a verzió már nem emelkedhet: a tagnek a VERSION-t kell hirdetnie. + tag-matches-version: + image: ruby:3.3 + commands: + - ruby script/warp_engine_version_check.rb --tag "$CI_COMMIT_REF" + when: + - event: tag + + rubocop: + image: ruby:3.2 + environment: + BUNDLE_PATH: .bundle-ci # a workspace-ben marad, a következő step újrahasználja + commands: + - apt-get update && apt-get install -y --no-install-recommends default-libmysqlclient-dev + - cd libs/ruby/warp_engine + - bundle install --jobs 4 + - bundle exec rubocop + test-engine: image: ruby:3.2 environment: RAILS_ENV: test DB_HOST: mysql + BUNDLE_PATH: .bundle-ci commands: - - apt-get update && apt-get install -y --no-install-recommends default-libmysqlclient-dev + - apt-get update && apt-get install -y --no-install-recommends default-libmysqlclient-dev default-mysql-client - cd libs/ruby/warp_engine - bundle install --jobs 4 - | diff --git a/README.md b/README.md index 009a4a0..1fbc06d 100644 --- a/README.md +++ b/README.md @@ -106,11 +106,14 @@ JSON contract baselines for `/api/software` and `/api/builds` live in ### Backend (RuboCop) ```bash -docker compose run --rm --no-deps api bundle exec rubocop # check -docker compose run --rm --no-deps api bundle exec rubocop -A # autofix +docker compose run --rm --no-deps api bundle exec rubocop # host app +docker compose run --rm --no-deps api bundle exec rubocop -A # host app, autofix +docker exec -w /libs/ruby/warp_engine api \ + env BUNDLE_GEMFILE=/app/Gemfile bundle exec rubocop # engine ``` -Config: `apps/api/.rubocop.yml` (rubocop-rails-omakase preset) +Config: `apps/api/.rubocop.yml` and `libs/ruby/warp_engine/.rubocop.yml` +(both rubocop-rails-omakase). Both are green, and CI keeps them that way. ### Frontend (ESLint) @@ -120,3 +123,72 @@ docker compose run --rm --no-deps frontend npm run lint:fix # autofix ``` Config: `apps/frontend/eslint.config.js` (ESLint 9 flat config, Vue + TypeScript) + +## Pipelines + +Woodpecker reads every file in `.woodpecker/` as its own workflow, each with its +own trigger. + +### `.woodpecker/warp_engine.yaml` — the gem + +Runs when a push to master touches `libs/ruby/warp_engine/**`, on a manual run, +and on a `warp_engine-v*` tag: + +| Step | What it guards | +|---|---| +| `version-bumped` | the engine changed but `WarpEngine::VERSION` did not — fails first, before anything else runs | +| `tag-matches-version` | tag events only: `warp_engine-v0.9.1` must find `VERSION = "0.9.1"` | +| `rubocop` | `libs/ruby/warp_engine` against its own `.rubocop.yml` | +| `test-engine` | the engine suite (dummy app, `warp_engine_test` DB) | +| `split-mirror` | pushes the subtree split to `engines/warp_engine` (master pushes only) | +| `publish-gem` | `gem push` to the Forgejo registry (tags only) | + +Nothing is mirrored or published until the version check, RuboCop and the suite +have all passed. The mirror repository deliberately has no CI of its own: the +subtree split overwrites it on every run. + +### `.woodpecker/deploy.yaml` — the deploy + +Runs on every push to master (and manually): `rubocop` → `test-host` (host +suite + a production-mode `zeitwerk:check`) → `pull` → `restart`. + +The `pull` and `restart` steps reach the server through the host Docker socket +rather than SSH — the `woodpecker-agent` runs in the same stack as `api` and +`frontend`. Two things this depends on: + +- The stack directory is bind-mounted **at the same path** it has on the host + (`/srv/stacks/teletype-games`), so the compose file's relative bind mounts + (`./data`, `./apps`) still resolve where they did. +- Woodpecker only allows step volumes on a **trusted** repository: enable + *Trusted → Volumes* in the repo settings (admin only), or the deploy steps + are rejected. + +`pull` fails if the server checkout is not on `master` (a detached HEAD would +make the pull look successful while the running code never moves). `restart` +installs dependencies, runs `db:migrate` and only then restarts — and it +touches only `api` and `frontend`: restarting `woodpecker-server` would cut off +the very pipeline doing the deploy. + +`db:migrate` in the host app covers the engine too — WarpEngine appends its own +`db/migrate` to the host's migration paths. A failing migration stops the +deploy before the restart, so the old code keeps running. + +Secret used by both workflows: `forge_token` — a Forgejo token with +repository read/write and package:write. + +## Version bump hook + +The engine's version rule is enforced twice, by the same script: + +```bash +git config core.hooksPath .githooks # once per clone +``` + +`.githooks/pre-commit` runs `script/warp_engine_version_check.rb --staged`: a +commit that touches `libs/ruby/warp_engine/**` must also raise +`WarpEngine::VERSION` above the one in `HEAD`. The pipeline runs the same script +in `--range` mode over the pushed commits, so nothing slips through a +`--no-verify`. + +Escape hatch when a bump genuinely does not belong: +`SKIP_WARP_ENGINE_VERSION_CHECK=1 git commit ...` diff --git a/apps/api/Gemfile.lock b/apps/api/Gemfile.lock index b6ba1d1..fa30a8f 100644 --- a/apps/api/Gemfile.lock +++ b/apps/api/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: ../libs/ruby/warp_engine specs: - warp_engine (0.9.0) + warp_engine (0.9.1) apipie-rails blueprinter rails (>= 8.0) diff --git a/apps/api/app/admin/stores.rb b/apps/api/app/admin/stores.rb index 93ad910..ef8e85e 100644 --- a/apps/api/app/admin/stores.rb +++ b/apps/api/app/admin/stores.rb @@ -19,7 +19,6 @@ ActiveAdmin.register Store do end column :updated_at actions defaults: true do |store| - link_to store.active? ? "Hide" : "List", toggle_admin_store_path(store), method: :put diff --git a/apps/api/app/controllers/api/stores_controller.rb b/apps/api/app/controllers/api/stores_controller.rb index e1ff6a3..df7bafa 100644 --- a/apps/api/app/controllers/api/stores_controller.rb +++ b/apps/api/app/controllers/api/stores_controller.rb @@ -15,7 +15,6 @@ class Api::StoresController < ApiController returns code: 200, desc: "Array of stores" do property :name, String, desc: "Display name of the store" property :catalogUrl, String, desc: "Base URL of the WarpEngine catalog it serves" - end def index render json: StoreService.new.index diff --git a/apps/api/app/services/store_service.rb b/apps/api/app/services/store_service.rb index a7f04a1..864d06c 100644 --- a/apps/api/app/services/store_service.rb +++ b/apps/api/app/services/store_service.rb @@ -1,5 +1,4 @@ class StoreService - def index StoreSerializer.render_as_hash(Store.active.ordered) end diff --git a/apps/api/spec/controllers/stores_controller_spec.rb b/apps/api/spec/controllers/stores_controller_spec.rb index 424d3d7..4c4f3e6 100644 --- a/apps/api/spec/controllers/stores_controller_spec.rb +++ b/apps/api/spec/controllers/stores_controller_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Api::StoresController, type: :request do expect(response).to have_http_status(:ok) json = JSON.parse(response.body) - expect(json.map { |s| s["name"] }).to eq(["Apex Games", "Zed Games"]) + expect(json.map { |s| s["name"] }).to eq([ "Apex Games", "Zed Games" ]) end it "answers with the fields a client needs, camelCased" do diff --git a/apps/api/spec/models/event_spec.rb b/apps/api/spec/models/event_spec.rb index 1d7f1af..fc58419 100644 --- a/apps/api/spec/models/event_spec.rb +++ b/apps/api/spec/models/event_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Event, type: :model do future = create(:event, date: 1.week.from_now) create(:event, date: 1.week.ago) - expect(Event.upcoming).to eq([future]) + expect(Event.upcoming).to eq([ future ]) end end end diff --git a/apps/api/spec/models/store_spec.rb b/apps/api/spec/models/store_spec.rb index c27c1fa..3686117 100644 --- a/apps/api/spec/models/store_spec.rb +++ b/apps/api/spec/models/store_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Store, type: :model do later = create(:store, name: "Zed Games") first = create(:store, name: "Apex Games") - expect(Store.ordered).to eq([first, later]) + expect(Store.ordered).to eq([ first, later ]) end end diff --git a/libs/ruby/warp_engine/.rubocop.yml b/libs/ruby/warp_engine/.rubocop.yml new file mode 100644 index 0000000..44c65a9 --- /dev/null +++ b/libs/ruby/warp_engine/.rubocop.yml @@ -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/**/*" diff --git a/libs/ruby/warp_engine/Gemfile b/libs/ruby/warp_engine/Gemfile index 67d2226..9e49baf 100644 --- a/libs/ruby/warp_engine/Gemfile +++ b/libs/ruby/warp_engine/Gemfile @@ -10,4 +10,5 @@ group :development, :test do gem "shoulda-matchers", "~> 6.0" gem "webmock", "~> 3.0" gem "debug", platforms: %i[mri windows] + gem "rubocop-rails-omakase", require: false end diff --git a/libs/ruby/warp_engine/README.md b/libs/ruby/warp_engine/README.md index 78b1feb..e7ba595 100644 --- a/libs/ruby/warp_engine/README.md +++ b/libs/ruby/warp_engine/README.md @@ -761,6 +761,16 @@ bundle exec rspec ## Development 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 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. diff --git a/libs/ruby/warp_engine/app/controllers/concerns/warp_engine/subject_authentication.rb b/libs/ruby/warp_engine/app/controllers/concerns/warp_engine/subject_authentication.rb index 04d62c7..4555cff 100644 --- a/libs/ruby/warp_engine/app/controllers/concerns/warp_engine/subject_authentication.rb +++ b/libs/ruby/warp_engine/app/controllers/concerns/warp_engine/subject_authentication.rb @@ -1,5 +1,4 @@ module WarpEngine - module SubjectAuthentication extend ActiveSupport::Concern diff --git a/libs/ruby/warp_engine/app/controllers/concerns/warp_engine/update_authentication.rb b/libs/ruby/warp_engine/app/controllers/concerns/warp_engine/update_authentication.rb index 978732d..6b9187d 100644 --- a/libs/ruby/warp_engine/app/controllers/concerns/warp_engine/update_authentication.rb +++ b/libs/ruby/warp_engine/app/controllers/concerns/warp_engine/update_authentication.rb @@ -1,5 +1,4 @@ module WarpEngine - module UpdateAuthentication extend ActiveSupport::Concern diff --git a/libs/ruby/warp_engine/app/controllers/warp_engine/api/auth/devices_controller.rb b/libs/ruby/warp_engine/app/controllers/warp_engine/api/auth/devices_controller.rb index 1bb2d35..b89b4db 100644 --- a/libs/ruby/warp_engine/app/controllers/warp_engine/api/auth/devices_controller.rb +++ b/libs/ruby/warp_engine/app/controllers/warp_engine/api/auth/devices_controller.rb @@ -1,5 +1,4 @@ module WarpEngine - class Api::Auth::DevicesController < ApiController before_action :ensure_identity_configured diff --git a/libs/ruby/warp_engine/app/controllers/warp_engine/api/auth/tokens_controller.rb b/libs/ruby/warp_engine/app/controllers/warp_engine/api/auth/tokens_controller.rb index ccab0da..2d49349 100644 --- a/libs/ruby/warp_engine/app/controllers/warp_engine/api/auth/tokens_controller.rb +++ b/libs/ruby/warp_engine/app/controllers/warp_engine/api/auth/tokens_controller.rb @@ -1,5 +1,4 @@ module WarpEngine - class Api::Auth::TokensController < ApiController resource_description do short "Client tokens" diff --git a/libs/ruby/warp_engine/app/controllers/warp_engine/api/service_controller.rb b/libs/ruby/warp_engine/app/controllers/warp_engine/api/service_controller.rb index e8b5d9f..3bb25fb 100644 --- a/libs/ruby/warp_engine/app/controllers/warp_engine/api/service_controller.rb +++ b/libs/ruby/warp_engine/app/controllers/warp_engine/api/service_controller.rb @@ -1,5 +1,4 @@ module WarpEngine - class Api::ServiceController < ApiController resource_description do short "Service descriptor" diff --git a/libs/ruby/warp_engine/app/controllers/warp_engine/build/configs_controller.rb b/libs/ruby/warp_engine/app/controllers/warp_engine/build/configs_controller.rb index d7834eb..3dad161 100644 --- a/libs/ruby/warp_engine/app/controllers/warp_engine/build/configs_controller.rb +++ b/libs/ruby/warp_engine/app/controllers/warp_engine/build/configs_controller.rb @@ -1,6 +1,5 @@ module WarpEngine module Build - class ConfigsController < ApiController resource_description do short "CI pipeline configs" diff --git a/libs/ruby/warp_engine/app/models/warp_engine/device_grant.rb b/libs/ruby/warp_engine/app/models/warp_engine/device_grant.rb index d814fd3..46bcbbc 100644 --- a/libs/ruby/warp_engine/app/models/warp_engine/device_grant.rb +++ b/libs/ruby/warp_engine/app/models/warp_engine/device_grant.rb @@ -1,5 +1,4 @@ module WarpEngine - class DeviceGrant < ApplicationRecord self.table_name = "device_grants" @@ -58,7 +57,6 @@ module WarpEngine end def self.generate_user_code - 10.times do candidate = Array.new(USER_CODE_LENGTH) { USER_CODE_ALPHABET.chars.sample }.join return candidate unless exists?(user_code: candidate) diff --git a/libs/ruby/warp_engine/app/models/warp_engine/release.rb b/libs/ruby/warp_engine/app/models/warp_engine/release.rb index 327c7af..09c7470 100644 --- a/libs/ruby/warp_engine/app/models/warp_engine/release.rb +++ b/libs/ruby/warp_engine/app/models/warp_engine/release.rb @@ -16,7 +16,7 @@ module WarpEngine validate :c64_cannot_be_web_playable 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.empty? ? sorted.first : candidates.first end diff --git a/libs/ruby/warp_engine/app/services/concerns/warp_engine/platforms/builds/build_linux_arm64.rb b/libs/ruby/warp_engine/app/services/concerns/warp_engine/platforms/builds/build_linux_arm64.rb index 5508adf..d3c200d 100644 --- a/libs/ruby/warp_engine/app/services/concerns/warp_engine/platforms/builds/build_linux_arm64.rb +++ b/libs/ruby/warp_engine/app/services/concerns/warp_engine/platforms/builds/build_linux_arm64.rb @@ -1,7 +1,6 @@ module WarpEngine module Platforms module Builds - module BuildLinuxArm64 extend ActiveSupport::Concern diff --git a/libs/ruby/warp_engine/app/services/warp_engine/device_grant_service.rb b/libs/ruby/warp_engine/app/services/warp_engine/device_grant_service.rb index 29ec737..97ba4b6 100644 --- a/libs/ruby/warp_engine/app/services/warp_engine/device_grant_service.rb +++ b/libs/ruby/warp_engine/app/services/warp_engine/device_grant_service.rb @@ -1,5 +1,4 @@ module WarpEngine - class DeviceGrantService class NotConfigured < StandardError; end class UnknownCode < StandardError; end diff --git a/libs/ruby/warp_engine/app/services/warp_engine/download_service.rb b/libs/ruby/warp_engine/app/services/warp_engine/download_service.rb index 9870797..c106f9a 100644 --- a/libs/ruby/warp_engine/app/services/warp_engine/download_service.rb +++ b/libs/ruby/warp_engine/app/services/warp_engine/download_service.rb @@ -2,7 +2,6 @@ require "warp_engine/access_denied" module WarpEngine class DownloadService - Denied = WarpEngine::AccessDenied include AccessPolicyGuard diff --git a/libs/ruby/warp_engine/app/services/warp_engine/file_manager_service.rb b/libs/ruby/warp_engine/app/services/warp_engine/file_manager_service.rb index 25824bc..ecbc84a 100644 --- a/libs/ruby/warp_engine/app/services/warp_engine/file_manager_service.rb +++ b/libs/ruby/warp_engine/app/services/warp_engine/file_manager_service.rb @@ -1,6 +1,5 @@ module WarpEngine class FileManagerService - def base_path @base_path ||= Pathname.new(WarpEngine.config.file_container_path) end diff --git a/libs/ruby/warp_engine/app/services/warp_engine/file_service.rb b/libs/ruby/warp_engine/app/services/warp_engine/file_service.rb index 96a1dcf..8ff09a4 100644 --- a/libs/ruby/warp_engine/app/services/warp_engine/file_service.rb +++ b/libs/ruby/warp_engine/app/services/warp_engine/file_service.rb @@ -2,7 +2,6 @@ require "warp_engine/access_denied" module WarpEngine class FileService - include AccessPolicyGuard def show(input, subject: nil) diff --git a/libs/ruby/warp_engine/app/services/warp_engine/publish_service.rb b/libs/ruby/warp_engine/app/services/warp_engine/publish_service.rb index 90a6fec..57c7f81 100644 --- a/libs/ruby/warp_engine/app/services/warp_engine/publish_service.rb +++ b/libs/ruby/warp_engine/app/services/warp_engine/publish_service.rb @@ -1,6 +1,5 @@ module WarpEngine class PublishService - NOTIFICATION = "warp_engine.publish".freeze def publish(input) diff --git a/libs/ruby/warp_engine/lib/tasks/warp_engine_builds.rake b/libs/ruby/warp_engine/lib/tasks/warp_engine_builds.rake index d5be640..f751264 100644 --- a/libs/ruby/warp_engine/lib/tasks/warp_engine_builds.rake +++ b/libs/ruby/warp_engine/lib/tasks/warp_engine_builds.rake @@ -12,7 +12,7 @@ namespace :warp_engine do checklist = WarpEngine::AssetCoverageChecklist.new( coverage, only_missing: ENV["ONLY"] == "missing" ) - puts checklist.to_s + puts checklist if ENV["STRICT"] == "1" && coverage.totals[:missing].positive? abort "\nSTRICT: #{coverage.totals[:missing]} expected assets are missing." diff --git a/libs/ruby/warp_engine/lib/warp_engine.rb b/libs/ruby/warp_engine/lib/warp_engine.rb index 7d1694f..8778b3b 100644 --- a/libs/ruby/warp_engine/lib/warp_engine.rb +++ b/libs/ruby/warp_engine/lib/warp_engine.rb @@ -13,7 +13,6 @@ require "warp_engine/images" require "warp_engine/ci" module WarpEngine - def self.table_name_prefix "" end diff --git a/libs/ruby/warp_engine/lib/warp_engine/access.rb b/libs/ruby/warp_engine/lib/warp_engine/access.rb index 121d161..9faa2e6 100644 --- a/libs/ruby/warp_engine/lib/warp_engine/access.rb +++ b/libs/ruby/warp_engine/lib/warp_engine/access.rb @@ -1,7 +1,5 @@ module WarpEngine - module AccessPolicy - class Open def visible_software_scope(subject: nil) WarpEngine::Software.kept @@ -39,7 +37,6 @@ module WarpEngine end class Access - attr_reader :gated, :entitled, :price_cents, :currency, :purchase_url, :web_url def initialize(gated: false, entitled: true, price_cents: nil, currency: nil, diff --git a/libs/ruby/warp_engine/lib/warp_engine/ci.rb b/libs/ruby/warp_engine/lib/warp_engine/ci.rb index 06a6b32..2749fe6 100644 --- a/libs/ruby/warp_engine/lib/warp_engine/ci.rb +++ b/libs/ruby/warp_engine/lib/warp_engine/ci.rb @@ -1,5 +1,4 @@ module WarpEngine - module CI class Error < StandardError; end diff --git a/libs/ruby/warp_engine/lib/warp_engine/configuration.rb b/libs/ruby/warp_engine/lib/warp_engine/configuration.rb index f213c14..7573cce 100644 --- a/libs/ruby/warp_engine/lib/warp_engine/configuration.rb +++ b/libs/ruby/warp_engine/lib/warp_engine/configuration.rb @@ -1,6 +1,5 @@ module WarpEngine class Configuration - attr_accessor :site_url, :file_container_path, :update_secret, diff --git a/libs/ruby/warp_engine/lib/warp_engine/images.rb b/libs/ruby/warp_engine/lib/warp_engine/images.rb index e7d527f..58754b7 100644 --- a/libs/ruby/warp_engine/lib/warp_engine/images.rb +++ b/libs/ruby/warp_engine/lib/warp_engine/images.rb @@ -1,7 +1,5 @@ module WarpEngine - module Images - class HostModel URL_PREFIX = "/api/image".freeze diff --git a/libs/ruby/warp_engine/lib/warp_engine/storage.rb b/libs/ruby/warp_engine/lib/warp_engine/storage.rb index d7222eb..f536e49 100644 --- a/libs/ruby/warp_engine/lib/warp_engine/storage.rb +++ b/libs/ruby/warp_engine/lib/warp_engine/storage.rb @@ -1,5 +1,4 @@ module WarpEngine - module Storage Location = Struct.new(:kind, :path, :url, keyword_init: true) do def file? = kind == :file diff --git a/libs/ruby/warp_engine/lib/warp_engine/version.rb b/libs/ruby/warp_engine/lib/warp_engine/version.rb index 2829db1..ecd2034 100644 --- a/libs/ruby/warp_engine/lib/warp_engine/version.rb +++ b/libs/ruby/warp_engine/lib/warp_engine/version.rb @@ -1,5 +1,5 @@ module WarpEngine - VERSION = "0.9.0" + VERSION = "0.9.1" VERSION_HEADER = "WarpEngine-Version".freeze end diff --git a/libs/ruby/warp_engine/spec/ci/gitlab_client_spec.rb b/libs/ruby/warp_engine/spec/ci/gitlab_client_spec.rb index 0c1d3d2..f216a28 100644 --- a/libs/ruby/warp_engine/spec/ci/gitlab_client_spec.rb +++ b/libs/ruby/warp_engine/spec/ci/gitlab_client_spec.rb @@ -15,7 +15,7 @@ RSpec.describe WarpEngine::CI::Gitlab::Client do describe "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) expect(client.list_repos).to eq(repos) @@ -39,7 +39,7 @@ RSpec.describe WarpEngine::CI::Gitlab::Client do describe "secrets (variables)" do it "lists variables" do - vars = [{ "key" => "application_token" }] + vars = [ { "key" => "application_token" } ] stub_gl(:get, "/api/v4/projects/1/variables", body: vars) expect(client.list_secrets(1)).to eq(vars) @@ -68,7 +68,7 @@ RSpec.describe WarpEngine::CI::Gitlab::Client do describe "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) expect(client.list_pipelines(42)).to eq(pipelines) @@ -76,7 +76,7 @@ RSpec.describe WarpEngine::CI::Gitlab::Client do it "gets latest pipeline" do 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) end diff --git a/libs/ruby/warp_engine/spec/ci/woodpecker_client_spec.rb b/libs/ruby/warp_engine/spec/ci/woodpecker_client_spec.rb index 9210bfc..2d87289 100644 --- a/libs/ruby/warp_engine/spec/ci/woodpecker_client_spec.rb +++ b/libs/ruby/warp_engine/spec/ci/woodpecker_client_spec.rb @@ -15,7 +15,7 @@ RSpec.describe WarpEngine::CI::Woodpecker::Client do describe "repos" do it "lists repos" do - repos = [{ "id" => 1, "name" => "game1" }] + repos = [ { "id" => 1, "name" => "game1" } ] stub_wp(:get, "/api/repos", body: repos) expect(client.list_repos).to eq(repos) @@ -37,7 +37,7 @@ RSpec.describe WarpEngine::CI::Woodpecker::Client do describe "secrets" do it "lists secrets" do - secrets = [{ "name" => "application_token" }] + secrets = [ { "name" => "application_token" } ] stub_wp(:get, "/api/repos/1/secrets", body: secrets) expect(client.list_secrets(1)).to eq(secrets) @@ -66,7 +66,7 @@ RSpec.describe WarpEngine::CI::Woodpecker::Client do describe "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) expect(client.list_pipelines(42)).to eq(pipelines) diff --git a/libs/ruby/warp_engine/spec/models/platform_link_spec.rb b/libs/ruby/warp_engine/spec/models/platform_link_spec.rb index 6e57f8a..99af235 100644 --- a/libs/ruby/warp_engine/spec/models/platform_link_spec.rb +++ b/libs/ruby/warp_engine/spec/models/platform_link_spec.rb @@ -20,12 +20,13 @@ RSpec.describe WarpEngine::PlatformLink, type: :model do end end - describe "default scope" do - it "excludes soft-deleted records" do - active = create(:platform_link) - create(:platform_link, deleted_at: Time.current) + describe "scopes" do + it "keeps soft-deleted records out of .kept, not out of .all" do + active = create(:platform_link) + 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 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") result = WarpEngine::PlatformLink.for_platform("tic80") - expect(result).to eq([tic80_link]) + expect(result).to eq([ tic80_link ]) end it "returns empty array for platform without links" do diff --git a/libs/ruby/warp_engine/spec/models/software_spec.rb b/libs/ruby/warp_engine/spec/models/software_spec.rb index af2b310..8042303 100644 --- a/libs/ruby/warp_engine/spec/models/software_spec.rb +++ b/libs/ruby/warp_engine/spec/models/software_spec.rb @@ -18,7 +18,7 @@ RSpec.describe WarpEngine::Software, type: :model do active = create(:software) create(:software, deleted_at: Time.current) - expect(WarpEngine::Software.kept).to eq([active]) + expect(WarpEngine::Software.kept).to eq([ active ]) end it "includes soft-deleted records without scope" do diff --git a/libs/ruby/warp_engine/spec/services/builds_service_spec.rb b/libs/ruby/warp_engine/spec/services/builds_service_spec.rb index a19f529..e0763fb 100644 --- a/libs/ruby/warp_engine/spec/services/builds_service_spec.rb +++ b/libs/ruby/warp_engine/spec/services/builds_service_spec.rb @@ -11,7 +11,7 @@ RSpec.describe WarpEngine::BuildsService do end it "returns c64 with only cartridge" do - expect(result[:platforms]["c64"][:kinds]).to eq(["cartridge"]) + expect(result[:platforms]["c64"][:kinds]).to eq([ "cartridge" ]) end it "returns allKinds matching WarpEngine::ReleaseAsset::KINDS" do diff --git a/script/warp_engine_version_check.rb b/script/warp_engine_version_check.rb new file mode 100755 index 0000000..c58d6d3 --- /dev/null +++ b/script/warp_engine_version_check.rb @@ -0,0 +1,119 @@ +#!/usr/bin/env ruby + +require "rubygems" + +ENGINE_PREFIX = "libs/ruby/warp_engine/".freeze +VERSION_PATH = "#{ENGINE_PREFIX}lib/warp_engine/version.rb".freeze +TAG_PREFIX = "warp_engine-v".freeze +SKIP_ENV = "SKIP_WARP_ENGINE_VERSION_CHECK".freeze + +def die(message) + warn "\e[31mwarp_engine verzióellenőrzés\e[0m: #{message}" + exit 1 +end + +def ok(message) + puts "warp_engine verzióellenőrzés: #{message}" + exit 0 +end + +def git(*args) + output = IO.popen([ "git", *args ], err: File::NULL, &:read) + $?.success? ? output : nil +end + +def blank_rev?(rev) + rev.nil? || rev.empty? || rev.match?(/\A0+\z/) +end + +def version_in(spec) + source = git("show", spec) + return nil if source.nil? || source.empty? + + match = source.match(/VERSION\s*=\s*["']([^"']+)["']/) + return nil if match.nil? + + Gem::Version.new(match[1]) +rescue ArgumentError + nil +end + +def engine_files(*diff_args) + output = git("diff", *diff_args) or die("a git diff nem futott le (#{diff_args.join(' ')})") + output.lines.map(&:chomp).select { |path| path.start_with?(ENGINE_PREFIX) } +end + +def bumped!(from, to, changed, how) + die("a #{VERSION_PATH} nem olvasható #{how}") if to.nil? + + if from.nil? + ok("#{to} — korábbi verzió nem volt, elfogadva") + end + + if to > from + puts "warp_engine verzióellenőrzés: #{from} -> #{to}, rendben (#{changed.size} érintett fájl)" + return + end + + die(<<~MESSAGE) + a warp_engine módosult, de a verzió nem emelkedett (#{from} -> #{to}). + + Érintett fájlok: + #{changed.first(10).map { |path| " #{path}" }.join("\n")}#{changed.size > 10 ? "\n ... és még #{changed.size - 10}" : ''} + + Emeld a verziót itt: #{VERSION_PATH} + A gem a #{TAG_PREFIX} tagre kerül ki a registrybe. + + Kihagyás (csak indokolt esetben): #{SKIP_ENV}=1, vagy git commit --no-verify + MESSAGE +end + +def check_staged + changed = engine_files("--cached", "--name-only", "--diff-filter=ACMRD") + ok("a commit nem érinti a warp_engine-t") if changed.empty? + ok("első commit, elfogadva") if git("rev-parse", "--verify", "HEAD").nil? + + bumped!(version_in("HEAD:#{VERSION_PATH}"), version_in(":#{VERSION_PATH}"), changed, "az indexből") +end + +def check_range(from, to) + ok("nincs korábbi commit a push-ban, elfogadva") if blank_rev?(from) + to = "HEAD" if blank_rev?(to) + + changed = engine_files("--name-only", from, to) + ok("a push nem érinti a warp_engine-t") if changed.empty? + + bumped!(version_in("#{from}:#{VERSION_PATH}"), version_in("#{to}:#{VERSION_PATH}"), changed, "a #{to} commitban") +end + +def check_tag(ref) + tag = ref.to_s.sub(%r{\Arefs/tags/}, "") + die("a(z) #{tag} tag neve nem #{TAG_PREFIX} alakú") unless tag.start_with?(TAG_PREFIX) + + tagged = Gem::Version.new(tag.delete_prefix(TAG_PREFIX)) + current = version_in("HEAD:#{VERSION_PATH}") or die("a #{VERSION_PATH} nem olvasható a HEAD-ben") + + if tagged != current + die("a tag #{tagged}-t hirdet, a #{VERSION_PATH} viszont #{current}-t tartalmaz") + end + + ok("a tag és a VERSION egyezik (#{current})") +rescue ArgumentError + die("a(z) #{tag} tagből nem olvasható verzió") +end + +ok("kihagyva (#{SKIP_ENV})") if ENV[SKIP_ENV] == "1" + +case ARGV[0] +when "--staged" then check_staged +when "--range" then check_range(ARGV[1], ARGV[2]) +when "--tag" then check_tag(ARGV[1]) +else + warn <<~USAGE + Használat: + script/warp_engine_version_check.rb --staged + script/warp_engine_version_check.rb --range + script/warp_engine_version_check.rb --tag + USAGE + exit 2 +end