Skip to content
This repository was archived by the owner on Sep 10, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2

# Keeps the pinned GitHub Actions current. Without this they silently drift:
# major tags like `actions/checkout@v7` float across patches but never across
# majors, so a workflow keeps working while falling years behind — which is
# majors, so a workflow keeps working while falling years behind, which is
# exactly how this repo ended up on `pnpm/action-setup@v4` after the workspace
# moved to pnpm 11 (v6 is where pnpm 11 support landed).
#
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ name: Checks
# Releases are tag-triggered rather than main-triggered, so without it a merge
# to `main` would go unverified until someone cut a tag.
#
# `workflow_call` re-executes these jobs rather than reusing an earlier result —
# `workflow_call` re-executes these jobs rather than reusing an earlier result,
# a tag can point at any commit, so the run gating a publish has to be against
# the tagged tree.
#
# Validates the Flutter/Dart engine package in isolation so `main` stays green
# for the apps that depend on it. No secrets needed — the engine reads no
# for the apps that depend on it. No secrets needed; the engine reads no
# env/Firebase config itself (apps inject runtime config via EngineConfig).
# The server it talks to is validated in the eigen-server repo's own CI.
on:
Expand Down Expand Up @@ -47,15 +47,15 @@ jobs:
# markdown LINK, which requires a matching reference definition at the
# bottom of the file. Without one `## [Unreleased]` parses as ordinary
# text: `cider release` cannot find it, leaves the entries where they sit,
# and appends an EMPTY version section instead — while exiting 0.
# and appends an EMPTY version section instead, while exiting 0.
#
# That is exactly how the first release pull request came out, and nothing
# anywhere said so. The next reviewer would have shipped an empty section
# to pub.dev, where changelogs are rendered and versions cannot be
# unpublished.
#
# `cider log` writes both halves, so this can only break when CHANGELOG.md
# is edited by hand — precisely when nobody is thinking about cider's
# is edited by hand, precisely when nobody is thinking about cider's
# parser. Runs before the toolchain because it needs nothing but grep.
- name: Changelog headings are link references
run: |
Expand All @@ -73,7 +73,7 @@ jobs:
done < <(grep -o '^## \[[^]]*\]' CHANGELOG.md | sed 's/^## \[//; s/\]$//')

[ "$missing" -eq 0 ] || {
echo "::error::Add entries with 'cider log <category> \"…\"' rather than editing CHANGELOG.md directly — it maintains these definitions for you."
echo "::error::Add entries with 'cider log <category> \"…\"' rather than editing CHANGELOG.md directly; it maintains these definitions for you."
exit 1
}

Expand All @@ -87,8 +87,8 @@ jobs:
run: flutter pub get

# Hand-written source only. `dart format` has no exclude flag, and
# `build_runner` output does not always survive it — so listing the
# files keeps the check on hand-written sources only — the committed
# `build_runner` output does not always survive it, so listing the
# files keeps the check on hand-written sources only: the committed
# generated code is already formatted by its builders and is verified
# instead by the build_runner + git-diff drift guard below.
- name: Check formatting
Expand Down Expand Up @@ -116,8 +116,8 @@ jobs:

# The RPS example is its own package with its own resolution, so the job
# above does not reach it. It is also the only place the framework is
# exercised the way a real game uses it — through the barrel, against a
# hand-built GameContentContext — and its twin fixtures are the Dart half of
# exercised the way a real game uses it (through the barrel, against a
# hand-built GameContentContext) and its twin fixtures are the Dart half of
# a contract the server's CI checks from the other side.
example:
name: Example (RPS / ${{ matrix.target }})
Expand Down
34 changes: 17 additions & 17 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ name: Release
#
# Tag-triggered rather than push-triggered because that is what pub.dev's
# automated publishing requires: it trusts a GitHub OIDC token only when the
# token's ref is a TAG matching the pattern configured on the package — set at
# token's ref is a TAG matching the pattern configured on the package, set at
# Admin → Automated publishing to repository `eigeninteractive/eigen-flutter`,
# tag pattern `v{{version}}`. No pub credential is stored in GitHub secrets at
# all — the OIDC identity IS the authentication.
# all: the OIDC identity IS the authentication.
#
# One package. `eigen_api` — the generated REST client — is published by the
# One package. `eigen_api`, the generated REST client, is published by the
# ENGINE repo (eigen-server/clients/dart), because that is where the wire
# contract lives. This package consumes it as an ordinary versioned dependency,
# so the two release independently.
#
# Generated code (`*.g.dart`, `*.freezed.dart`) is committed, so the tarball
# compiles for consumers and publishing needs no `.pubignore` — `dart pub
# compiles for consumers and publishing needs no `.pubignore`; `dart pub
# publish` falls back to `.gitignore`, which excludes build output and
# credentials while keeping the generated sources. `checks.yml` proves that
# committed output is current, so nothing is regenerated below.
Expand All @@ -38,8 +38,8 @@ permissions:
jobs:
# The same gate every pull request clears, re-run against the tagged commit.
# A tag can point at any commit, so this is a fresh execution rather than a
# lookup of whatever the branch run concluded. Sharing the definition — not
# re-typing format/analyze/test here — is the point: a check added to
# lookup of whatever the branch run concluded. Sharing the definition, not
# re-typing format/analyze/test here, is the point: a check added to
# checks.yml automatically guards releases too.
gate:
name: Checks
Expand Down Expand Up @@ -79,9 +79,9 @@ jobs:
exit 0

# Makes the pipeline idempotent. `dart pub publish` fails on a version
# that already exists, which turns every harmless re-run — a retried job,
# that already exists, which turns every harmless re-run (a retried job,
# a re-pushed tag, a version published by hand before this automation
# existed — into a red release someone has to explain.
# existed) into a red release someone has to explain.
#
# Checking first turns those into a clean skip, and keeps the state that
# matters (what is on pub.dev) authoritative over the state that does not
Expand All @@ -92,7 +92,7 @@ jobs:
v=$(grep -m1 '^version:' pubspec.yaml | awk '{print $2}')
if curl -sf https://pub.dev/api/packages/eigen_flutter \
| jq -e --arg v "$v" '.versions[] | select(.version == $v)' >/dev/null; then
echo "::notice::eigen_flutter $v is already on pub.dev — skipping publish."
echo "::notice::eigen_flutter $v is already on pub.dev. Skipping publish."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
Expand All @@ -103,7 +103,7 @@ jobs:
# and nothing here did.
#
# `dart-lang/setup-dart` does this exchange for you, which is why the
# pub.dev instructions never mention it — but this job installs the SDK
# pub.dev instructions never mention it, but this job installs the SDK
# through `subosito/flutter-action`, because publishing a Flutter package
# needs the Flutter SDK, and that action has no equivalent step.
#
Expand All @@ -123,7 +123,7 @@ jobs:
-H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" | jq -r '.value')

[ -n "$PUB_TOKEN" ] && [ "$PUB_TOKEN" != "null" ] || {
echo "::error::could not mint an OIDC token for pub.dev — publishing would fall back to an interactive prompt and hang"
echo "::error::could not mint an OIDC token for pub.dev; publishing would fall back to an interactive prompt and hang"
exit 1
}

Expand All @@ -133,7 +133,7 @@ jobs:
dart pub token add https://pub.dev --env-var PUB_TOKEN

# Surfaces score-affecting problems (missing docs, unresolved links,
# platform support) before they are permanent — pub.dev versions cannot be
# platform support) before they are permanent; pub.dev versions cannot be
# unpublished, only retracted.
- name: Dry run
if: steps.published.outputs.skip == 'false'
Expand All @@ -143,8 +143,8 @@ jobs:
if: steps.published.outputs.skip == 'false'
run: dart pub publish --force

# eigen-web publishes the compatibility table — which engine line each
# `eigen_flutter` release speaks — and pub.dev is the authority for it.
# eigen-web publishes the compatibility table, which engine line each
# `eigen_flutter` release speaks, and pub.dev is the authority for it.
# Nothing over there can notice a publish on its own, so tell it.
#
# NOT gated on `skip`: a re-run after a failed dispatch is exactly when
Expand All @@ -155,15 +155,15 @@ jobs:
# The version travels in the payload because pub.dev serves a publish from
# its API a moment after the call returns. The receiving workflow waits for
# this exact version rather than reading whatever is there when it happens
# to start — otherwise it would regenerate from the state just before this
# to start, or else it would regenerate from the state just before this
# release and silently produce no change at all.
#
# Failing here fails the release, matching eigen-server: a published client
# with an unnotified docs site is a state worth stopping on. The recovery
# is the `Sync compatibility table` workflow's manual trigger.
#
# GITHUB_TOKEN cannot do this — it is scoped to the repository that minted
# it, same organization or not — so this needs the App. Its credentials
# GITHUB_TOKEN cannot do this; it is scoped to the repository that minted
# it, same organization or not, so this needs the App. Its credentials
# must exist HERE (`RELEASE_APP_CLIENT_ID` variable,
# `RELEASE_APP_PRIVATE_KEY` secret) and it must be installed on eigen-web.
- uses: actions/create-github-app-token@v3
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: Tag
# publishing trusts a GitHub OIDC token only when the token's ref is a TAG
# matching the pattern configured on the package. `release.yml` therefore
# cannot run on a branch push, and something has to create the tag. Doing it
# here rather than by hand removes the one release step with no safety net —
# here rather than by hand removes the one release step with no safety net:
# a mistyped or forgotten tag.
#
# Deliberately keyed on the FILE rather than on the pull request that changed
Expand Down Expand Up @@ -48,7 +48,7 @@ jobs:
# think nothing had ever been released.
fetch-depth: 0

# pubspec.yaml also changes for ordinary dependency edits — an `eigen_api`
# pubspec.yaml also changes for ordinary dependency edits, e.g. an `eigen_api`
# constraint bump, say. Those leave `version:` alone, so this must decide
# "has this version already been released?" and exit cleanly when it has.
# Being idempotent is what lets the trigger stay this simple.
Expand All @@ -57,7 +57,7 @@ jobs:
# theoretical: 0.1.0 was published by hand before this automation existed,
# so it had no tag. "No tag" therefore meant "unreleased" only by
# accident, and the first dependency edit afterwards duly tagged an
# unrelated commit as v0.1.0 — a tag whose tree was never what shipped.
# unrelated commit as v0.1.0: a tag whose tree was never what shipped.
#
# release.yml already makes the same argument for its own publish step:
# what is on pub.dev is the state that matters, and which refs happen to
Expand All @@ -71,22 +71,22 @@ jobs:
[ -n "$v" ] || { echo "::error file=pubspec.yaml::no version: line"; exit 1; }

# Cheap and local first. Once a version is tagged, nothing else here
# needs to run — including on every unrelated pubspec.yaml edit.
# needs to run, including on every unrelated pubspec.yaml edit.
if git rev-parse -q --verify "refs/tags/v$v" >/dev/null; then
echo "v$v is already tagged — nothing to release."
echo "v$v is already tagged. Nothing to release."
exit 0
fi

# Refuse to guess. Tagging blind is what created the bad ref, and a
# release is re-runnable: a transient pub.dev failure costs one rerun,
# whereas a wrong tag has to be noticed before it can be undone.
published=$(curl -sf --retry 3 https://pub.dev/api/packages/eigen_flutter) || {
echo "::error::could not reach pub.dev to check whether $v is already released — refusing to tag blind"
echo "::error::could not reach pub.dev to check whether $v is already released; refusing to tag blind"
exit 1
}

if jq -e --arg v "$v" '.versions[] | select(.version == $v)' >/dev/null <<<"$published"; then
echo "::notice::eigen_flutter $v is already on pub.dev — nothing to release."
echo "::notice::eigen_flutter $v is already on pub.dev. Nothing to release."
exit 0
fi

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: Version
# or `gh workflow run version.yml -f bump=minor`.
#
# This is the only manual decision in a release: which bump. Everything after it
# is automatic — merging the pull request this opens tags the commit
# is automatic: merging the pull request this opens tags the commit
# (`tag.yml`), and the tag publishes to pub.dev (`release.yml`).
#
# `cider` owns the arithmetic and the changelog. Contributors have already
Expand Down Expand Up @@ -99,18 +99,18 @@ jobs:
# report success and create nothing: the branch updated, and there was
# no pull request to merge. That is the exact recovery path taken when
# a release pull request came out wrong, so it failed at the one moment
# it was needed — and reported "Refreshed" while doing it.
# it was needed, and reported "Refreshed" while doing it.
state=$(gh pr view "release/v$V" --json state --jq .state 2>/dev/null || echo NONE)
if [ "$state" = "OPEN" ]; then
echo "Refreshed the existing pull request for v$V."
exit 0
fi
[ "$state" = "NONE" ] || echo "The previous pull request for v$V is $state — opening a new one."
[ "$state" = "NONE" ] || echo "The previous pull request for v$V is $state. Opening a new one."

body="Raises \`eigen_flutter\` to **v$V** (\`$BUMP\`) and dates the changelog section.

Merging this tags \`v$V\`, which publishes to pub.dev. Published versions
cannot be unpublished — only retracted — so read the changelog diff before
cannot be unpublished, only retracted, so read the changelog diff before
merging."

gh pr create --base main --title "Release v$V" --body "$body"
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ build/
pubspec.lock

# Generated code (`*.g.dart`, `*.freezed.dart`) is deliberately NOT ignored: it
# is committed, so the published package compiles for consumers — who never run
# build_runner on a dependency — and so `git diff` after `build_runner build`
# is committed, so the published package compiles for consumers (who never run
# build_runner on a dependency) and so `git diff` after `build_runner build`
# is the drift guard that proves it is current. This is why there is no
# `.pubignore`: publishing falls back to this file, which correctly excludes
# build output and the credentials below while keeping the generated sources.
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ and is the source of truth. Retrieve it rather than relying on memory:
| Any page as Markdown | append `.md` to its URL |
| HTTP contract (OpenAPI 3.1) | `https://eigeninteractive.com/openapi.json` |

The docs are task-first, and each page covers **both halves** of a task — the
TypeScript rules and the Dart client — because they are one change.
The docs are task-first, and each page covers **both halves** of a task, the
TypeScript rules and the Dart client, because they are one change.

Start points: `/docs/getting-started/your-first-game.md` (a whole game, both
languages), `/docs/build-a-game/the-contract.md` (what you write and what the
Expand Down
Loading