diff --git a/.github/workflows/protocol-publish.yml b/.github/workflows/protocol-publish.yml new file mode 100644 index 000000000..4c690ab0f --- /dev/null +++ b/.github/workflows/protocol-publish.yml @@ -0,0 +1,89 @@ +name: Publish @cockatrice/protocol + +on: + release: + types: + - released # stable releases only; prereleases intentionally skipped + pull_request: + branches: + - master + paths: + - '.github/workflows/protocol-publish.yml' + - 'libcockatrice_protocol/**' + workflow_dispatch: + +concurrency: + group: "${{ github.workflow }} @ ${{ github.ref_name }}" + cancel-in-progress: ${{ github.event_name != 'release' }} + +jobs: + publish: + name: Build and publish protocol package + if: ${{ github.repository_owner == 'Cockatrice' }} + runs-on: ubuntu-latest + timeout-minutes: 10 + + permissions: + contents: read + packages: write + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Determine package version + id: pkgver + shell: bash + run: | + # Cockatrice stable tags: YYYY-MM-DD-Release-X.Y.Z. Non-release events get a + # placeholder version and the publish step is skipped. + tag="${{ github.event.release.tag_name }}" + if [[ "$GITHUB_EVENT_NAME" == "release" ]]; then + if [[ "$tag" =~ Release-([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + echo "version=${BASH_REMATCH[1]}" >>"$GITHUB_OUTPUT" + else + echo "::error::Release tag '$tag' does not end in Release-X.Y.Z; refusing to publish." + exit 1 + fi + else + echo "version=0.0.0-dryrun" >>"$GITHUB_OUTPUT" + fi + + - name: Assemble package + shell: bash + env: + PKG_VERSION: ${{ steps.pkgver.outputs.version }} + run: | + pkg=build/protocol-package + rm -rf "$pkg" + mkdir -p "$pkg/pb" + cp libcockatrice_protocol/libcockatrice/protocol/pb/*.proto "$pkg/pb/" + cp libcockatrice_protocol/protocol_version.json "$pkg/" + cp libcockatrice_protocol/npm/package.json libcockatrice_protocol/npm/README.md "$pkg/" + cp LICENSE "$pkg/" + npm --prefix "$pkg" version --no-git-tag-version --allow-same-version "$PKG_VERSION" + + - name: Pack and inspect (dry-run) + if: ${{ github.event_name != 'release' }} + working-directory: build/protocol-package + run: | + npm pack + ls -la ./*.tgz + tar -tzf ./*.tgz | sort + + - name: Publish to GitHub Packages + if: ${{ github.event_name == 'release' }} + uses: JS-DevTools/npm-publish@v4 + with: + package: build/protocol-package + registry: https://npm.pkg.github.com + token: ${{ secrets.GITHUB_TOKEN }} + access: restricted + provenance: true + strategy: upgrade diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp index 7e20f2722..5b273a629 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp @@ -21,9 +21,10 @@ #include #include #include +#include #include -static const unsigned int protocolVersion = 14; +static const unsigned int protocolVersion = COCKATRICE_PROTOCOL_VERSION; RemoteClient::RemoteClient(QObject *parent, INetworkSettingsProvider *_networkSettingsProvider) : AbstractClient(parent), networkSettingsProvider(_networkSettingsProvider), timeRunning(0), lastDataReceived(0), diff --git a/libcockatrice_protocol/CMakeLists.txt b/libcockatrice_protocol/CMakeLists.txt index 7dc0a0360..ead5fab1f 100644 --- a/libcockatrice_protocol/CMakeLists.txt +++ b/libcockatrice_protocol/CMakeLists.txt @@ -1,5 +1,26 @@ # Top-level wrapper for the protobuf library +# Single source of truth for the network protocol version. The same JSON file is +# shipped in the @cockatrice/protocol npm package so TypeScript consumers read +# the identical value at runtime. Regex-extracted (instead of string(JSON ...)) +# so we keep the project's CMake 3.10 floor. +set(PROTOCOL_VERSION_JSON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/protocol_version.json") +file(READ "${PROTOCOL_VERSION_JSON_PATH}" PROTOCOL_VERSION_JSON) +string(REGEX MATCH "\"protocolVersion\"[ \t\r\n]*:[ \t\r\n]*([0-9]+)" _ "${PROTOCOL_VERSION_JSON}") +if(NOT CMAKE_MATCH_1) + message(FATAL_ERROR "Failed to extract protocolVersion from ${PROTOCOL_VERSION_JSON_PATH}") +endif() +set(COCKATRICE_PROTOCOL_VERSION "${CMAKE_MATCH_1}") +set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS "${PROTOCOL_VERSION_JSON_PATH}" +) +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/protocol_version.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/libcockatrice/protocol/protocol_version.h" @ONLY +) + add_subdirectory(libcockatrice/protocol/pb) add_library(libcockatrice_protocol STATIC) diff --git a/libcockatrice_protocol/npm/README.md b/libcockatrice_protocol/npm/README.md new file mode 100644 index 000000000..3d77b4bdf --- /dev/null +++ b/libcockatrice_protocol/npm/README.md @@ -0,0 +1,30 @@ +# @cockatrice/protocol + +Network protocol artifacts for [Cockatrice](https://github.com/Cockatrice/Cockatrice): the `.proto` +definitions used by the desktop client, Servatrice, and the webclient, plus the +authoritative protocol version constant they all share. + +## Install + +```sh +npm install @cockatrice/protocol +``` + +The package is published to GitHub Packages under the `@cockatrice` scope; consumers +need an `.npmrc` entry pointing the scope at `https://npm.pkg.github.com` and a +`GITHUB_TOKEN` with `read:packages`. + +## Contents + +- `pb/*.proto` — every protobuf schema file from `libcockatrice_protocol`. +- `protocol_version.json` — `{ "protocolVersion": }`. Identical to the file + the C++ build reads via `configure_file()`. + +## Usage (TypeScript) + +```ts +import protocolVersionInfo from "@cockatrice/protocol/protocol_version.json" with { type: "json" }; +export const PROTOCOL_VERSION = protocolVersionInfo.protocolVersion; +``` + +Point your protobuf code-generator (e.g. buf) at `node_modules/@cockatrice/protocol/pb`. diff --git a/libcockatrice_protocol/npm/package.json b/libcockatrice_protocol/npm/package.json new file mode 100644 index 000000000..3e544ac2a --- /dev/null +++ b/libcockatrice_protocol/npm/package.json @@ -0,0 +1,25 @@ +{ + "name": "@cockatrice/protocol", + "version": "0.0.0", + "description": "Cockatrice network protocol: .proto definitions and protocol version constant.", + "license": "GPL-2.0-or-later", + "repository": { + "type": "git", + "url": "git+https://github.com/Cockatrice/Cockatrice.git" + }, + "homepage": "https://github.com/Cockatrice/Cockatrice", + "files": [ + "pb/", + "protocol_version.json", + "LICENSE", + "README.md" + ], + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "restricted" + }, + "exports": { + "./protocol_version.json": "./protocol_version.json", + "./pb/*.proto": "./pb/*.proto" + } +} diff --git a/libcockatrice_protocol/protocol_version.h.in b/libcockatrice_protocol/protocol_version.h.in new file mode 100644 index 000000000..b32feb730 --- /dev/null +++ b/libcockatrice_protocol/protocol_version.h.in @@ -0,0 +1,3 @@ +#pragma once +// Generated by configure_file() from protocol_version.json. Do not edit. +#define COCKATRICE_PROTOCOL_VERSION @COCKATRICE_PROTOCOL_VERSION@ diff --git a/libcockatrice_protocol/protocol_version.json b/libcockatrice_protocol/protocol_version.json new file mode 100644 index 000000000..6ca32ae7d --- /dev/null +++ b/libcockatrice_protocol/protocol_version.json @@ -0,0 +1,3 @@ +{ + "protocolVersion": 14 +} diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index bc90a3ef1..5677e0d74 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -79,6 +79,7 @@ #include #include #include +#include #include #include #include @@ -88,7 +89,7 @@ inline Q_LOGGING_CATEGORY(AbstractServerSocketInterfaceLog, "abstract_server_soc inline Q_LOGGING_CATEGORY(TcpServerSocketInterfaceLog, "tcp_server_socket_interface"); inline Q_LOGGING_CATEGORY(WebsocketServerSocketInterfaceLog, "websocket_server_socket_interface"); -static const int protocolVersion = 14; +static const int protocolVersion = COCKATRICE_PROTOCOL_VERSION; AbstractServerSocketInterface::AbstractServerSocketInterface(Servatrice *_server, Servatrice_DatabaseInterface *_databaseInterface,