diff --git a/.github/workflows/protocol-publish.yml b/.github/workflows/protocol-publish.yml index e98bbb1ce..df84c0fc4 100644 --- a/.github/workflows/protocol-publish.yml +++ b/.github/workflows/protocol-publish.yml @@ -9,8 +9,13 @@ on: - master paths: - '.github/workflows/protocol-publish.yml' - - 'scripts/package-protocol.mjs' - 'libcockatrice_protocol/**' + workflow_dispatch: + inputs: + version: + description: 'Semver to publish (e.g. 15.0.0). Leave blank to dry-run only.' + required: false + default: '' concurrency: group: "${{ github.workflow }} @ ${{ github.ref_name }}" @@ -21,6 +26,7 @@ jobs: name: Build and publish protocol package if: ${{ github.repository_owner == 'Cockatrice' }} runs-on: ubuntu-latest + timeout-minutes: 10 permissions: contents: read @@ -29,7 +35,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 - # No submodules; proto files live in this repo. - name: Set up Node.js uses: actions/setup-node@v4 @@ -40,27 +45,56 @@ jobs: - name: Determine package version id: pkgver + shell: bash run: | - if [ "${{ github.event_name }}" = "release" ]; then - VERSION="${{ github.event.release.tag_name }}" - else - VERSION="0.0.0-pr${{ github.event.pull_request.number }}" - fi - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + case "$GITHUB_EVENT_NAME" in + release) + version="${{ github.event.release.tag_name }}" + version="${version#v}" + publish=true + ;; + workflow_dispatch) + version="${{ inputs.version }}" + if [[ -n "$version" ]]; then + version="${version#v}" + publish=true + else + version="0.0.0-manual" + publish=false + fi + ;; + *) # pull_request + version="0.0.0-pr${{ github.event.pull_request.number }}" + publish=false + ;; + esac + echo "version=$version" >>"$GITHUB_OUTPUT" + echo "publish=$publish" >>"$GITHUB_OUTPUT" - - name: Build package directory - run: node scripts/package-protocol.mjs --version "${{ steps.pkgver.outputs.version }}" + - 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: Dry-run pack (pull_request) - if: ${{ github.event_name == 'pull_request' }} + - name: Pack and inspect (dry-run) + if: ${{ steps.pkgver.outputs.publish != 'true' }} working-directory: build/protocol-package run: | npm pack - ls -la *.tgz - tar -tzf *.tgz | sort + ls -la ./*.tgz + tar -tzf ./*.tgz | sort - - name: Publish to GitHub Packages (release) - if: ${{ github.event_name == 'release' }} + - name: Publish to GitHub Packages + if: ${{ steps.pkgver.outputs.publish == 'true' }} working-directory: build/protocol-package env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/libcockatrice_protocol/CMakeLists.txt b/libcockatrice_protocol/CMakeLists.txt index 5eccdf236..ead5fab1f 100644 --- a/libcockatrice_protocol/CMakeLists.txt +++ b/libcockatrice_protocol/CMakeLists.txt @@ -11,11 +11,14 @@ 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}") +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 + "${CMAKE_CURRENT_BINARY_DIR}/libcockatrice/protocol/protocol_version.h" @ONLY ) add_subdirectory(libcockatrice/protocol/pb) 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/scripts/package-protocol.mjs b/scripts/package-protocol.mjs deleted file mode 100755 index ec1dbb40a..000000000 --- a/scripts/package-protocol.mjs +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env node -// Assembles a publish-ready @cockatrice/protocol package directory from -// libcockatrice_protocol/. Run by .github/workflows/protocol-publish.yml. -// stdlib only. - -import { readdirSync, readFileSync, mkdirSync, copyFileSync, writeFileSync, rmSync, existsSync } from "node:fs"; -import { join, resolve, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; - -const REQUIRED_CORE_PROTOS = ["commands.proto", "server_message.proto", "event_server_identification.proto"]; - -function parseArgs(argv) { - const args = { version: null, outDir: null }; - for (let i = 0; i < argv.length; i++) { - const a = argv[i]; - if (a === "--version") args.version = argv[++i]; - else if (a.startsWith("--version=")) args.version = a.slice("--version=".length); - else if (a === "--out-dir") args.outDir = argv[++i]; - else if (a.startsWith("--out-dir=")) args.outDir = a.slice("--out-dir=".length); - else die(`unknown argument: ${a}`); - } - if (!args.version) die("missing required --version "); - return args; -} - -function die(msg) { - console.error(`package-protocol: ${msg}`); - process.exit(1); -} - -function normalizeVersion(raw) { - const stripped = raw.replace(/^v/, ""); - if (!/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(stripped)) { - die(`version "${raw}" is not a valid semver`); - } - return stripped; -} - -const __filename = fileURLToPath(import.meta.url); -const repoRoot = resolve(dirname(__filename), ".."); -const protoSrcDir = join(repoRoot, "libcockatrice_protocol", "libcockatrice", "protocol", "pb"); -const versionJsonSrc = join(repoRoot, "libcockatrice_protocol", "protocol_version.json"); -const licenseSrc = join(repoRoot, "LICENSE"); - -const { version: rawVersion, outDir: outDirArg } = parseArgs(process.argv.slice(2)); -const pkgVersion = normalizeVersion(rawVersion); -const outDir = resolve(outDirArg ?? join(repoRoot, "build", "protocol-package")); - -if (existsSync(outDir)) rmSync(outDir, { recursive: true, force: true }); -mkdirSync(join(outDir, "pb"), { recursive: true }); - -const protoFiles = readdirSync(protoSrcDir).filter((f) => f.endsWith(".proto")); -if (protoFiles.length === 0) die(`no .proto files found in ${protoSrcDir}`); - -const missing = REQUIRED_CORE_PROTOS.filter((f) => !protoFiles.includes(f)); -if (missing.length > 0) { - die(`required core proto files missing: ${missing.join(", ")} (layout of ${protoSrcDir} changed?)`); -} - -for (const f of protoFiles) { - copyFileSync(join(protoSrcDir, f), join(outDir, "pb", f)); -} - -copyFileSync(versionJsonSrc, join(outDir, "protocol_version.json")); -copyFileSync(licenseSrc, join(outDir, "LICENSE")); - -const pkgJson = { - name: "@cockatrice/protocol", - version: pkgVersion, - 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", - }, -}; -writeFileSync(join(outDir, "package.json"), JSON.stringify(pkgJson, null, 2) + "\n"); - -const readme = `# @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\`. -`; -writeFileSync(join(outDir, "README.md"), readme); - -console.log(`package-protocol: assembled ${outDir}`); -console.log(` name: ${pkgJson.name}`); -console.log(` version: ${pkgJson.version}`); -console.log(` proto: ${protoFiles.length} files`);