The push prompted for credentials and then died with "404 page not
found". Both are the same fault: RubyGems normalizes the keys in
~/.gem/credentials — dots become __ and a trailing slash is appended —
so a key written as the host URL can never match the --host value.
Finding no key, gem push falls back to signing in against the RubyGems
sign_in endpoint, which Gitea does not implement; that is the 404.
A named key is stored verbatim and is matched by --key, so the lookup
succeeds. Verified against Gem::ConfigFile with both formats.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The publishing pipeline was already complete — subtree split to the
mirror, gem build and push on a warp_engine-v* tag — but it had never
been triggered, and after the org reorganization three of its targets
were stale: the mirror push URL and the rubygems registry namespace
(twice).
The gemspec's allowed_push_host has to match the --host that `gem push`
receives, so it now carries the full registry URL rather than the bare
forge host, plus source and documentation links.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The client was split into a reusable engine and a store definition, so
the page is now about our store: new repo and wiki links, the CLI path
the installer actually writes, and a closing section pointing at
warp-engine-batocera-store for anyone who wants a store of their own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The engine ships an ActiveJob based job (PipelineSyncJob), but a host's
application.rb does not necessarily require active_job/railtie - apps/api does
not. With eager loading off (development, test) nothing noticed; in production
WarpEngine::ApplicationJob blew up with "uninitialized constant
WarpEngine::ActiveJob", which is exactly what `rails zeitwerk:check` in
RAILS_ENV=production reported. An engine that ships jobs has to pull in the
framework it needs, so lib/warp_engine.rb requires the railtie.
Pre-existing on 0.1.0 as well; found while verifying that the 0.2.0 changes do
not break the portal. All three api-test steps are green now.
apps/api Gemfile.lock follows the 0.1.0 -> 0.2.0 path gem bump.
Two seams the hosts needed, both backward compatible.
Storage: artifacts are served through WarpEngine::Storage.adapter instead of
raw filesystem calls. The default :local adapter keeps the previous behaviour
byte for byte, including the path traversal guard. A host can now set
config.storage_adapter to any object answering file?/directory?/locate and
serve builds from an object store - FileService and /api/download both honour
a Location.redirect, so a signing adapter turns them into redirects.
DownloadService#create still returns an absolute path (nil when missing) for
existing callers; #locate is the new entry point that can also return a
redirect. Ingestion (upload, extraction, file manager) stays local for now.
Publish: PublishService emits ActiveSupport::Notifications
("warp_engine.publish") with platform/name/version/software/release, so hosts
can react to a new build without hanging callbacks on the models.
WarpEngine.instruments_publish? lets a host feature-detect and keep its
fallback for older engine versions.
The nav has too many items for the old spacing: smaller type, tighter
gaps, icons only on xl+ screens, and the hamburger now takes over below
lg (the desktop row did not fit between md and lg anymore).
The Hungarian engine strings drop the hyphenated loanword forms:
Engine-ek -> Motorok, Saját Engine-jeink -> Saját motorjaink.
The engines listing is a handful of curated pages, not a stream — no feed
needed. Removes the route, controller action, RssService#engines_feed and
the footer link. The WikiService#pages alias stays (blog/howtos feeds use
it).
The engine pages' repo metadata (new in the wiki pages API) drives the
Explore button and card title links, with the wiki page as fallback. The
never-deployed /engines/:slug detail page and its store/api plumbing are
gone, and the engines RSS feed links to the repos too.
Frontend: /engines index + /engines/:slug detail routes, nav menu item and
en/hu translations. Engine pages are few, so the index uses an emphasized
poster-style design (dark slate, emerald accents, numbered full-width cards
with content preview) instead of the blog/howtos layouts. Slugs are the last
wiki path segment, since engine pages live scattered in the wiki tree.
API: /api/rss/engines feed linking to the site's engine pages, and a
WikiService#pages alias for #index — RssService called the alias-less name,
so the blog and howtos feeds were raising NoMethodError.
release.UpdatedAt does not exist on the Release interface (or in the API
response) — the release dates rendered as invalid values and vue-tsc failed
the build.
examples/compose boots everything the engine's workflow assumes: mysql, a
minimal Rails host consuming the engine as a path gem, an SSH drop area
sharing the softwares volume with the app, and — behind the ci profile —
gitea plus woodpecker (agent attached to the stack network so pipeline
steps reach droparea/app by service name).
host_app doubles as a reference for a brand-new host: Gemfile, the two
initializers, apipie + engine mounts in routes.rb; on first boot the
entrypoint runs the install generator and db:prepare.
The README gains a detailed bring-up walkthrough: quickstart, publishing
a release by hand over scp + /update (verified end to end from a clean
slate), and the full gitea/woodpecker OAuth wiring.
The README is what the tools/warp_engine mirror shows: present the engine as
a standalone product installed from git or the gem registry, with the
monorepo workflow reduced to a short Development note.
A trailing newline pasted into the Woodpecker secret broke the push URL
("credential url cannot be parsed"); strip all whitespace from the token in
both the mirror and the gem-publish steps.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Path-filtered push events hide the workflow for unrelated pushes and manual
restarts have no changed-files list; add event: manual so the mirror can be
triggered from the Woodpecker UI.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The credentials printf line contains ': ' which YAML reads as a mapping;
use a literal block scalar for that command.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- .woodpecker.yaml: on master pushes touching libs/ruby/warp_engine, split the
subtree and force-push it to the read-only tools/warp_engine mirror; on
warp_engine-v* tags, build and push the gem to the Forgejo rubygems registry
- engine README documents the monorepo-first workflow and the mirror
Requires a `forge_token` Woodpecker secret (repository:write + package:write).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- root README: monorepo layout with libs/, the WarpEngine/host layering,
rebuild note for the root build context, make api-test and snapshot docs
- warp_engine README translated to English
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- host apipie matcher now globs the engine controllers, so /api/docs and
/api/swagger keep documenting the catalog endpoints
- rails g warp_engine:install: initializer template + a clean
create_warp_engine_tables migration (signed bigint PKs) for new hosts;
TTG never runs it
- engine append_migrations initializer: future catalog migrations in the
engine's db/migrate run via the host's rails db:migrate
- README documents the updater contract, config surface, host expectations
(admin JS picker, apipie matcher) and the soft-delete/resurrection behavior
- make api-test runs both suites plus a production zeitwerk:check
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- the 7 catalog admin files (softwares, releases, external_links,
platform_links, images, files page, downloads) move to the engine's
app/admin; the host ActiveAdmin instance loads them via
ActiveAdmin.application.load_paths (single admin, URLs unchanged)
- engine initializers: Zeitwerk ignore for app/admin (production eager load)
and load_paths + watchable_dirs registration, guarded by defined?(ActiveAdmin)
so the admin-less dummy app boots
- images admin reads image owners from WarpEngine.config.image_owners; the
host registers the Member owner in config/initializers/warp_engine.rb;
the interim ImageUsage registry is gone
- fix: SoftwareImage.distinct.pluck clashed with its order(:position)
default scope on MySQL (unscope(:order)) — introduced in phase 0, caught
by the first authenticated /admin/images smoke test
- files page: download links use the public /file/ URL instead of the raw
container path; the picker's stored path comes from config
Verified: both suites green, zeitwerk:check (production) clean, JSON
baselines intact, authenticated admin smoke test on every page incl.
the 3-level nested software form and Files picker mode.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- update, files and the 6 /api catalog controllers now live in the engine on
a new WarpEngine::ApiController base (same rescue/mime behavior as host)
- engine routes serve /update, /file/*path, /api/software*, /api/builds*,
/api/image/:id, /api/download at unchanged public paths via the root mount;
host routes keep only TTG endpoints (events, members, wiki, rss, swagger)
- /update secret comes from WarpEngine.config.update_secret and an
unconfigured secret now rejects every request (previously an empty
UPDATE_SECRET env accepted empty secrets)
- apipie-rails is an engine dependency (DSL in engine controllers); dummy app
configures apipie with validation off, mirroring the host
- engine request specs: catalog controller specs moved from host plus new
/update auth contract spec
Verified: engine suite 53 green, host suite 6 green, /api/software and
/api/builds byte-identical to baselines, /update 401/400 behavior intact,
admin and TTG endpoints OK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- all catalog services (update/software/highlighted/builds/file/file-manager/
download/image + SoftwareResponseBuilder), the SoftwareUpdater platform
services and their concerns, Blueprinter serializers (incl. TimestampFields)
and the 4 DTOs now live in the engine under WarpEngine::
- constantize dispatch strings use absolute names
(WarpEngine::SoftwareUpdater::<Platform>Service)
- container paths read from WarpEngine.config everywhere (FileService,
DownloadService, FileManagerService, ArchiveExtraction, ReleaseSerializer
path rewriting); FileManagerService base path is now lazy
- engine requires blueprinter itself; gemspec declares blueprinter + rubyzip
- engine test suite: spec/dummy app (mysql warp_engine_test, catalog-only
schema), rails_helper with engine-local factories; catalog model/service
specs and factories moved from the host
- host suite keeps TTG specs and loads catalog factories from the engine
Verified: engine suite 40 green, host suite 14 green, /api/software and
/api/builds byte-identical to baselines, admin OK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Software, Release, ReleaseAsset, ExternalLink, PlatformLink, Image,
SoftwareImage, Download now live in the engine under WarpEngine::, on top
of WarpEngine::ApplicationRecord; table names unchanged (empty prefix)
- each model runs an ActiveSupport load hook (:warp_engine_<model>) as a
host extension point
- WarpEngine::Image reads its upload path from WarpEngine.config
- host references fully qualified (services, serializers, admin, specs);
admin registrations renamed with as: so /admin URLs and route helpers are
byte-identical; factories pinned to the namespaced classes
- Member#image now class_name: "WarpEngine::Image"
Verified: full suite green, /api/software and /api/builds byte-identical to
the phase-0 baselines, admin routes unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- libs/ruby/warp_engine: gemspec, WarpEngine::Engine (isolate_namespace with
empty table_name_prefix), WarpEngine.configure surface (container paths,
update_secret, image_owners), empty engine routes
- host Gemfile: path gem; host routes: mount WarpEngine::Engine => "/" as
the last entry so host routes always win
- docker: api build context moved to repo root so libs/ is visible at
bundle-install time; ./libs:/libs runtime mount; root .dockerignore to keep
data/ and node_modules out of the build context
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- FileService/DownloadService: lazy container-path resolution instead of
class-load-time realpath (boot no longer requires /softwares to exist)
- downloadCount/totalDownloads: single grouped query passed as Blueprinter
option instead of per-release association counts (fixes N+1)
- admin Images: Member coupling replaced with ImageUsage owner registry
(precursor of the WarpEngine.config.image_owners hook)
- Image: UPLOAD_PATH constant replaced with call-time upload_path accessor
- proper test environment (config/environments/test.rb, softwares_test DB,
hosts.clear) — suite previously ran against the development DB
- spec fixes: case-insensitive uniqueness matchers (MySQL ai_ci collation),
DB-cascade has_many expectations, PlatformLink::SUPPORTED_PLATFORMS
- JSON baselines of /api/software and /api/builds for post-extraction diffing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Older-style tic80 pipelines (bombexpert, mranderson) do not upload a
docs zip; the unconditional extraction raised and aborted the whole
update, so the new binary assets never got registered. Extract and
register docs only when the zip/dir exists. Covered by new
Tic80Service specs (optional docs + binary slug pickup).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All software updater services now read an optional repo field from the
game metadata and upsert it as a "Repository" external link, keyed by
(software_id, label) so re-deploys update the existing link instead of
creating duplicates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>