diff --git a/apps/api/app/controllers/api/builds_controller.rb b/apps/api/app/controllers/api/builds_controller.rb new file mode 100644 index 0000000..f81ddb2 --- /dev/null +++ b/apps/api/app/controllers/api/builds_controller.rb @@ -0,0 +1,5 @@ +class Api::BuildsController < ApiController + def index + render json: BuildsService.new.index + end +end diff --git a/apps/api/app/controllers/api/software_builds_controller.rb b/apps/api/app/controllers/api/software_builds_controller.rb new file mode 100644 index 0000000..77a696a --- /dev/null +++ b/apps/api/app/controllers/api/software_builds_controller.rb @@ -0,0 +1,5 @@ +class Api::SoftwareBuildsController < ApiController + def show + render json: BuildsService.new.show(params[:name]) + end +end diff --git a/apps/api/app/services/builds_service.rb b/apps/api/app/services/builds_service.rb new file mode 100644 index 0000000..e558afc --- /dev/null +++ b/apps/api/app/services/builds_service.rb @@ -0,0 +1,37 @@ +class BuildsService + def index + platforms = UpdateService::SUPPORTED_PLATFORMS.each_with_object({}) do |platform, hash| + service_class = "SoftwareUpdater::#{platform.camelize}Service".constantize + hash[platform] = { + label: service_class.label, + kinds: service_class.expected_kinds + } + end + + all_kinds = ReleaseAsset::KINDS + + { platforms: platforms, allKinds: all_kinds } + end + + def show(name) + software = Software.find_by!(name: name) + service_class = "SoftwareUpdater::#{software.platform.camelize}Service".constantize + expected = service_class.expected_kinds + + releases = software.releases.includes(:release_assets).order(updated_at: :desc) + + release_data = releases.each_with_object({}) do |release, hash| + actual = release.release_assets.map(&:kind) + hash[release.version] = { + actual: actual, + missing: expected - actual + } + end + + { + platform: software.platform, + expected: expected, + releases: release_data + } + end +end diff --git a/apps/api/app/services/concerns/software_updater/binary_attachment.rb b/apps/api/app/services/concerns/software_updater/binary_attachment.rb deleted file mode 100644 index 0e302bb..0000000 --- a/apps/api/app/services/concerns/software_updater/binary_attachment.rb +++ /dev/null @@ -1,23 +0,0 @@ -module SoftwareUpdater - module BinaryAttachment - # fájlnév slug → ReleaseAsset kind - TARGET_KINDS = { - "win-x86" => "win_x86", - "win-x64" => "win_x64", - "linux-x86" => "linux_x86", - "linux-x64" => "linux_x64", - "mac-x64" => "mac_x64", - "mac-arm64" => "mac_arm64", - "mac-universal" => "mac_universal" - }.freeze - - private - - def binary_asset_paths(versioned) - TARGET_KINDS.each_with_object({}) do |(slug, kind), assets| - filename = "#{versioned}-#{slug}.zip" - assets[kind] = full_path(filename) if File.file?(full_path(filename)) - end - end - end -end diff --git a/apps/api/app/services/concerns/software_updater/builds/build_cartridge.rb b/apps/api/app/services/concerns/software_updater/builds/build_cartridge.rb new file mode 100644 index 0000000..fcea2a8 --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/builds/build_cartridge.rb @@ -0,0 +1,22 @@ +module SoftwareUpdater + module Builds + module BuildCartridge + extend ActiveSupport::Concern + + included do + register_expected_kind "cartridge" + end + + def cartridge_ext + raise NotImplementedError, "#{self.class} must implement #cartridge_ext" + end + + private + + def cartridge_asset_path(versioned) + path = full_path("#{versioned}#{cartridge_ext}") + { "cartridge" => path } if File.file?(path) + end + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/builds/build_docs.rb b/apps/api/app/services/concerns/software_updater/builds/build_docs.rb new file mode 100644 index 0000000..5a6f63b --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/builds/build_docs.rb @@ -0,0 +1,24 @@ +module SoftwareUpdater + module Builds + module BuildDocs + extend ActiveSupport::Concern + + included do + register_expected_kind "docs" + register_prepare_step :prepare_docs + end + + private + + def prepare_docs(versioned) + docs_zip = "#{versioned}-docs.zip" + extract_zip_to_dir(docs_zip, "#{versioned}-docs") if File.file?(full_path(docs_zip)) + end + + def docs_asset_path(versioned) + docs_dir = "#{versioned}-docs" + { "docs" => full_path(docs_dir) } if dir_exists?(docs_dir) + end + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/builds/build_linux_x64.rb b/apps/api/app/services/concerns/software_updater/builds/build_linux_x64.rb new file mode 100644 index 0000000..0f6054d --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/builds/build_linux_x64.rb @@ -0,0 +1,18 @@ +module SoftwareUpdater + module Builds + module BuildLinuxX64 + extend ActiveSupport::Concern + + included do + register_expected_kind "linux_x64" + end + + private + + def linux_x64_asset_path(versioned) + path = full_path("#{versioned}-linux-x64.zip") + { "linux_x64" => path } if File.file?(path) + end + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/builds/build_mac_arm64.rb b/apps/api/app/services/concerns/software_updater/builds/build_mac_arm64.rb new file mode 100644 index 0000000..49cccd2 --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/builds/build_mac_arm64.rb @@ -0,0 +1,18 @@ +module SoftwareUpdater + module Builds + module BuildMacArm64 + extend ActiveSupport::Concern + + included do + register_expected_kind "mac_arm64" + end + + private + + def mac_arm64_asset_path(versioned) + path = full_path("#{versioned}-mac-arm64.zip") + { "mac_arm64" => path } if File.file?(path) + end + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/builds/build_mac_universal.rb b/apps/api/app/services/concerns/software_updater/builds/build_mac_universal.rb new file mode 100644 index 0000000..a038b65 --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/builds/build_mac_universal.rb @@ -0,0 +1,18 @@ +module SoftwareUpdater + module Builds + module BuildMacUniversal + extend ActiveSupport::Concern + + included do + register_expected_kind "mac_universal" + end + + private + + def mac_universal_asset_path(versioned) + path = full_path("#{versioned}-mac-universal.zip") + { "mac_universal" => path } if File.file?(path) + end + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/builds/build_mac_x64.rb b/apps/api/app/services/concerns/software_updater/builds/build_mac_x64.rb new file mode 100644 index 0000000..cea3027 --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/builds/build_mac_x64.rb @@ -0,0 +1,18 @@ +module SoftwareUpdater + module Builds + module BuildMacX64 + extend ActiveSupport::Concern + + included do + register_expected_kind "mac_x64" + end + + private + + def mac_x64_asset_path(versioned) + path = full_path("#{versioned}-mac-x64.zip") + { "mac_x64" => path } if File.file?(path) + end + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/builds/build_source.rb b/apps/api/app/services/concerns/software_updater/builds/build_source.rb new file mode 100644 index 0000000..832e4a3 --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/builds/build_source.rb @@ -0,0 +1,22 @@ +module SoftwareUpdater + module Builds + module BuildSource + extend ActiveSupport::Concern + + included do + register_expected_kind "source" + end + + def source_ext + raise NotImplementedError, "#{self.class} must implement #source_ext" + end + + private + + def source_asset_path(versioned) + path = full_path("#{versioned}#{source_ext}") + { "source" => path } if File.file?(path) + end + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/builds/build_web.rb b/apps/api/app/services/concerns/software_updater/builds/build_web.rb new file mode 100644 index 0000000..89eeb9a --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/builds/build_web.rb @@ -0,0 +1,23 @@ +module SoftwareUpdater + module Builds + module BuildWeb + extend ActiveSupport::Concern + + included do + register_expected_kind "html" + register_prepare_step :prepare_web + end + + private + + def prepare_web(versioned) + extract_zip_to_dir("#{versioned}.html.zip", versioned) + end + + def html_asset_path(versioned) + path = full_path(versioned) + { "html" => path } if dir_exists?(versioned) + end + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/builds/build_win_x64.rb b/apps/api/app/services/concerns/software_updater/builds/build_win_x64.rb new file mode 100644 index 0000000..fbbef2c --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/builds/build_win_x64.rb @@ -0,0 +1,18 @@ +module SoftwareUpdater + module Builds + module BuildWinX64 + extend ActiveSupport::Concern + + included do + register_expected_kind "win_x64" + end + + private + + def win_x64_asset_path(versioned) + path = full_path("#{versioned}-win-x64.zip") + { "win_x64" => path } if File.file?(path) + end + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/builds/build_win_x86.rb b/apps/api/app/services/concerns/software_updater/builds/build_win_x86.rb new file mode 100644 index 0000000..777882d --- /dev/null +++ b/apps/api/app/services/concerns/software_updater/builds/build_win_x86.rb @@ -0,0 +1,18 @@ +module SoftwareUpdater + module Builds + module BuildWinX86 + extend ActiveSupport::Concern + + included do + register_expected_kind "win_x86" + end + + private + + def win_x86_asset_path(versioned) + path = full_path("#{versioned}-win-x86.zip") + { "win_x86" => path } if File.file?(path) + end + end + end +end diff --git a/apps/api/app/services/concerns/software_updater/updatable.rb b/apps/api/app/services/concerns/software_updater/updatable.rb index abd6a5b..d9d7e55 100644 --- a/apps/api/app/services/concerns/software_updater/updatable.rb +++ b/apps/api/app/services/concerns/software_updater/updatable.rb @@ -4,13 +4,33 @@ module SoftwareUpdater include ArchiveExtraction include MetadataParsing include SoftwarePersistence - include BinaryAttachment class_methods do def platform(value = nil) @platform = value if value @platform ||= name.demodulize.delete_suffix("Service").downcase end + + def label(value = nil) + @label = value if value + @label ||= platform.titleize + end + + def expected_kinds + @expected_kinds ||= [] + end + + def register_expected_kind(kind) + expected_kinds << kind unless expected_kinds.include?(kind) + end + + def prepare_steps + @prepare_steps ||= [] + end + + def register_prepare_step(method_name) + prepare_steps << method_name unless prepare_steps.include?(method_name) + end end def update(name, version) @@ -27,7 +47,7 @@ module SoftwareUpdater upsert_external_link(software.id, "Repository", repo_url) if repo_url.present? release = create_release_if_not_exists(software_id: software.id, version: version) - sync_release_assets(release, asset_paths(versioned).merge(binary_asset_paths(versioned))) + sync_release_assets(release, collect_asset_paths(versioned)) release end end @@ -35,15 +55,18 @@ module SoftwareUpdater private def prepare_files(versioned) - extract_zip_to_dir("#{versioned}.html.zip", versioned) + self.class.prepare_steps.each { |step| send(step, versioned) } end def parse_metadata(versioned) parse_json_metadata(full_path("#{versioned}.metadata.json")) end - def asset_paths(versioned) - { "html" => full_path(versioned) } + def collect_asset_paths(versioned) + self.class.expected_kinds.each_with_object({}) do |kind, hash| + result = send(:"#{kind}_asset_path", versioned) + hash.merge!(result) if result + end end end end diff --git a/apps/api/app/services/software_updater/bevy_service.rb b/apps/api/app/services/software_updater/bevy_service.rb index f4b754d..30f8a38 100644 --- a/apps/api/app/services/software_updater/bevy_service.rb +++ b/apps/api/app/services/software_updater/bevy_service.rb @@ -1,5 +1,10 @@ module SoftwareUpdater class BevyService include Updatable + include Builds::BuildWeb + include Builds::BuildWinX64 + include Builds::BuildLinuxX64 + + label "Bevy" end end diff --git a/apps/api/app/services/software_updater/c64_service.rb b/apps/api/app/services/software_updater/c64_service.rb index 1a7cae3..3032693 100644 --- a/apps/api/app/services/software_updater/c64_service.rb +++ b/apps/api/app/services/software_updater/c64_service.rb @@ -1,15 +1,10 @@ module SoftwareUpdater class C64Service include Updatable + include Builds::BuildCartridge - private + label "C64" - def prepare_files(_versioned) - # a c64 buildhez nem tartozik html zip, csak a .prg fájl - end - - def asset_paths(versioned) - { "cartridge" => full_path("#{versioned}.prg") } - end + def cartridge_ext = ".prg" end end diff --git a/apps/api/app/services/software_updater/ebitengine_service.rb b/apps/api/app/services/software_updater/ebitengine_service.rb index 77bde66..2f44406 100644 --- a/apps/api/app/services/software_updater/ebitengine_service.rb +++ b/apps/api/app/services/software_updater/ebitengine_service.rb @@ -1,5 +1,13 @@ module SoftwareUpdater class EbitengineService include Updatable + include Builds::BuildWeb + include Builds::BuildWinX86 + include Builds::BuildWinX64 + include Builds::BuildLinuxX64 + include Builds::BuildMacX64 + include Builds::BuildMacArm64 + + label "Ebitengine" end end diff --git a/apps/api/app/services/software_updater/godot_service.rb b/apps/api/app/services/software_updater/godot_service.rb index b245044..6e292c2 100644 --- a/apps/api/app/services/software_updater/godot_service.rb +++ b/apps/api/app/services/software_updater/godot_service.rb @@ -1,5 +1,12 @@ module SoftwareUpdater class GodotService include Updatable + include Builds::BuildWeb + include Builds::BuildWinX86 + include Builds::BuildWinX64 + include Builds::BuildLinuxX64 + include Builds::BuildMacUniversal + + label "Godot" end end diff --git a/apps/api/app/services/software_updater/love_service.rb b/apps/api/app/services/software_updater/love_service.rb index f58ce77..d07eb27 100644 --- a/apps/api/app/services/software_updater/love_service.rb +++ b/apps/api/app/services/software_updater/love_service.rb @@ -1,5 +1,11 @@ module SoftwareUpdater class LoveService include Updatable + include Builds::BuildWeb + include Builds::BuildWinX64 + include Builds::BuildLinuxX64 + include Builds::BuildMacUniversal + + label "LÖVE" end end diff --git a/apps/api/app/services/software_updater/phaser_service.rb b/apps/api/app/services/software_updater/phaser_service.rb index 1626724..407cb91 100644 --- a/apps/api/app/services/software_updater/phaser_service.rb +++ b/apps/api/app/services/software_updater/phaser_service.rb @@ -1,5 +1,8 @@ module SoftwareUpdater class PhaserService include Updatable + include Builds::BuildWeb + + label "Phaser" end end diff --git a/apps/api/app/services/software_updater/tic80_service.rb b/apps/api/app/services/software_updater/tic80_service.rb index b88ad3e..928fe0a 100644 --- a/apps/api/app/services/software_updater/tic80_service.rb +++ b/apps/api/app/services/software_updater/tic80_service.rb @@ -1,32 +1,25 @@ module SoftwareUpdater class Tic80Service include Updatable + include Builds::BuildCartridge + include Builds::BuildSource + include Builds::BuildWeb + include Builds::BuildDocs + include Builds::BuildWinX64 + include Builds::BuildLinuxX64 + include Builds::BuildMacX64 + + label "TIC-80" + + def cartridge_ext = ".tic" + def source_ext = ".lua" private - # a docs opcionális: a régebbi sémájú tic80 pipeline-ok nem töltenek fel - # docs zipet, e nélkül is regisztrálni kell a többi assetet - def prepare_files(versioned) - extract_zip_to_dir("#{versioned}.html.zip", versioned) - docs_zip = "#{versioned}-docs.zip" - extract_zip_to_dir(docs_zip, "#{versioned}-docs") if File.file?(full_path(docs_zip)) - end - def parse_metadata(versioned) parse_lua_metadata(full_path("#{versioned}.lua")) end - def asset_paths(versioned) - paths = { - "cartridge" => full_path("#{versioned}.tic"), - "source" => full_path("#{versioned}.lua"), - "html" => full_path(versioned) - } - docs_dir = "#{versioned}-docs" - paths["docs"] = full_path(docs_dir) if dir_exists?(docs_dir) - paths - end - def parse_lua_metadata(source_path) metadata = {} File.foreach(source_path) do |line| diff --git a/apps/api/config/routes.rb b/apps/api/config/routes.rb index dfa77df..887ea21 100644 --- a/apps/api/config/routes.rb +++ b/apps/api/config/routes.rb @@ -15,6 +15,8 @@ Rails.application.routes.draw do get "rss/releases", to: "rss#releases" get "rss/howtos", to: "rss#howtos" get "download", to: "downloads#show" + get "builds", to: "builds#index" + get "softwares/:name/builds", to: "software_builds#show" end get "update", to: "update#update" get "file/*path", to: "files#show", format: false diff --git a/apps/api/spec/controllers/builds_controller_spec.rb b/apps/api/spec/controllers/builds_controller_spec.rb new file mode 100644 index 0000000..57e7dcb --- /dev/null +++ b/apps/api/spec/controllers/builds_controller_spec.rb @@ -0,0 +1,16 @@ +require "rails_helper" + +RSpec.describe Api::BuildsController, type: :request do + describe "GET /api/builds" do + it "returns the global build matrix" do + get "/api/builds" + + expect(response).to have_http_status(:ok) + json = JSON.parse(response.body) + expect(json["platforms"]).to be_a(Hash) + expect(json["platforms"]["tic80"]["label"]).to eq("TIC-80") + expect(json["platforms"]["tic80"]["kinds"]).to include("cartridge") + expect(json["allKinds"]).to be_an(Array) + end + end +end diff --git a/apps/api/spec/controllers/software_builds_controller_spec.rb b/apps/api/spec/controllers/software_builds_controller_spec.rb new file mode 100644 index 0000000..33e12b3 --- /dev/null +++ b/apps/api/spec/controllers/software_builds_controller_spec.rb @@ -0,0 +1,29 @@ +require "rails_helper" + +RSpec.describe Api::SoftwareBuildsController, type: :request do + describe "GET /api/softwares/:name/builds" do + let!(:software) { create(:software, name: "test-game", platform: "love") } + let!(:release) { create(:release, software: software, version: "2.0.0") } + + before do + ReleaseAsset.create!(release: release, kind: "html", path: "/test/html") + end + + it "returns per-software build info" do + get "/api/softwares/test-game/builds" + + expect(response).to have_http_status(:ok) + json = JSON.parse(response.body) + expect(json["platform"]).to eq("love") + expect(json["expected"]).to include("html", "win_x64") + expect(json["releases"]["2.0.0"]["actual"]).to include("html") + expect(json["releases"]["2.0.0"]["missing"]).to include("win_x64") + end + + it "returns 404 for unknown software" do + get "/api/softwares/nonexistent/builds" + + expect(response).to have_http_status(:not_found) + end + end +end diff --git a/apps/api/spec/services/builds_service_spec.rb b/apps/api/spec/services/builds_service_spec.rb new file mode 100644 index 0000000..0182d78 --- /dev/null +++ b/apps/api/spec/services/builds_service_spec.rb @@ -0,0 +1,50 @@ +require "rails_helper" + +RSpec.describe BuildsService do + describe "#index" do + subject(:result) { described_class.new.index } + + it "returns all supported platforms with expected kinds" do + expect(result[:platforms]).to have_key("tic80") + expect(result[:platforms]["tic80"][:label]).to eq("TIC-80") + expect(result[:platforms]["tic80"][:kinds]).to include("cartridge", "html") + end + + it "returns c64 with only cartridge" do + expect(result[:platforms]["c64"][:kinds]).to eq(["cartridge"]) + end + + it "returns allKinds matching ReleaseAsset::KINDS" do + expect(result[:allKinds]).to eq(ReleaseAsset::KINDS) + end + + it "includes all supported platforms" do + UpdateService::SUPPORTED_PLATFORMS.each do |platform| + expect(result[:platforms]).to have_key(platform) + end + end + end + + describe "#show" do + let!(:software) { create(:software, name: "test-game", platform: "love") } + let!(:release) { create(:release, software: software, version: "1.0.0") } + + before do + ReleaseAsset.create!(release: release, kind: "html", path: "/test/html") + ReleaseAsset.create!(release: release, kind: "win_x64", path: "/test/win") + end + + it "returns expected and actual kinds per release" do + result = described_class.new.show("test-game") + + expect(result[:platform]).to eq("love") + expect(result[:expected]).to match_array(%w[html win_x64 linux_x64 mac_universal]) + expect(result[:releases]["1.0.0"][:actual]).to match_array(%w[html win_x64]) + expect(result[:releases]["1.0.0"][:missing]).to match_array(%w[linux_x64 mac_universal]) + end + + it "raises RecordNotFound for unknown software" do + expect { described_class.new.show("nonexistent") }.to raise_error(ActiveRecord::RecordNotFound) + end + end +end diff --git a/apps/api/spec/services/software_updater/tic80_service_spec.rb b/apps/api/spec/services/software_updater/tic80_service_spec.rb index 55e54c3..d891fe9 100644 --- a/apps/api/spec/services/software_updater/tic80_service_spec.rb +++ b/apps/api/spec/services/software_updater/tic80_service_spec.rb @@ -63,11 +63,17 @@ RSpec.describe SoftwareUpdater::Tic80Service do it "registers binary assets when slug zips are present" do write_zip("#{versioned}-win-x64.zip", "game.exe" => "MZ") - write_zip("#{versioned}-mac-universal.zip", "game" => "bin") + write_zip("#{versioned}-mac-x64.zip", "game" => "bin") release = described_class.new.update(name, version) - expect(asset_kinds(release)).to include("win_x64", "mac_universal") + expect(asset_kinds(release)).to include("win_x64", "mac_x64") expect(asset_kinds(release)).not_to include("linux_x64") end + + it "exposes expected_kinds from included Build concerns" do + expect(described_class.expected_kinds).to match_array( + %w[cartridge source html docs win_x64 linux_x64 mac_x64] + ) + end end diff --git a/apps/frontend/src/api/builds.api.ts b/apps/frontend/src/api/builds.api.ts new file mode 100644 index 0000000..5e679c3 --- /dev/null +++ b/apps/frontend/src/api/builds.api.ts @@ -0,0 +1,9 @@ +import type { BuildsData } from '../lib/interfaces/builds.interface' + +const index = async (): Promise => { + const res = await fetch('/api/builds') + if (!res.ok) throw new Error(`HTTP ${res.status}`) + return res.json() +} + +export default { index } diff --git a/apps/frontend/src/i18n/locales/en.ts b/apps/frontend/src/i18n/locales/en.ts index 12bc605..9ef52c5 100644 --- a/apps/frontend/src/i18n/locales/en.ts +++ b/apps/frontend/src/i18n/locales/en.ts @@ -179,6 +179,10 @@ export default { new: 'New', read: 'Read', }, + builds: { + title: 'Build Matrix', + subtitle: 'Which engines build for which platforms.', + }, code: { title: 'Codebase', subtitle: 'Explore our collection of open-source projects, study our source code, and contribute to our independent game development tools.', diff --git a/apps/frontend/src/i18n/locales/hu.ts b/apps/frontend/src/i18n/locales/hu.ts index ce63916..bf2ee25 100644 --- a/apps/frontend/src/i18n/locales/hu.ts +++ b/apps/frontend/src/i18n/locales/hu.ts @@ -179,6 +179,10 @@ export default { new: 'Új', read: 'Olvasás', }, + builds: { + title: 'Build mátrix', + subtitle: 'Melyik engine melyik platformra fordít.', + }, code: { title: 'Kódbázis', subtitle: 'Fedezd fel nyílt forráskódú projektek gyűjteményét, tanulmányozd forráskódunkat, és járulj hozzá független játékfejlesztő eszközeinkhez.', diff --git a/apps/frontend/src/lib/interfaces/builds.interface.ts b/apps/frontend/src/lib/interfaces/builds.interface.ts new file mode 100644 index 0000000..678f9dd --- /dev/null +++ b/apps/frontend/src/lib/interfaces/builds.interface.ts @@ -0,0 +1,9 @@ +export interface PlatformBuilds { + label: string + kinds: string[] +} + +export interface BuildsData { + platforms: Record + allKinds: string[] +} diff --git a/apps/frontend/src/page/builds/BuildsIndexPage.vue b/apps/frontend/src/page/builds/BuildsIndexPage.vue new file mode 100644 index 0000000..a638984 --- /dev/null +++ b/apps/frontend/src/page/builds/BuildsIndexPage.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/apps/frontend/src/page/catalog/CatalogIndexPage.vue b/apps/frontend/src/page/catalog/CatalogIndexPage.vue index 2097aa2..4aa9385 100644 --- a/apps/frontend/src/page/catalog/CatalogIndexPage.vue +++ b/apps/frontend/src/page/catalog/CatalogIndexPage.vue @@ -18,6 +18,12 @@ + +