Context
Raised while re-syncing ptr727/ESPHome-NonRoot from template PR #167 (the downstream heads-up issue asked to migrate the bespoke version tracker to the canonical check-upstream-version-task.yml).
Gap
The canonical check-upstream-version-task.yml serializes only a single bare-string version: the resolver prints one line to stdout (echo "version=$version" >> "$GITHUB_OUTPUT") and the state file is written raw (printf '%s\n' "$VERSION" > "$STATE_FILE", default upstream-version.json).
That cannot serve a wrapper that pins multiple upstream components consumed by the build. Concrete downstream example: ESPHome-NonRoot pins both esphome and esphome-device-builder, and build-docker-task.yml reads each by key (.version, .device_builder_version) to set the image tag and build-args. There is no clean way to carry two keyed versions through a single stdout line / bare-string file, so the downstream keeps a bespoke check-esphome-version.yml instead of converging on the canonical task.
A second, smaller wrinkle: the default state file is named upstream-version.json but holds a bare string, so the .json extension is misleading today.
Proposal
Make the canonical task serialize a structured JSON state file that supports one or many keyed versions, so a single-version wrapper and a multi-component wrapper both use the same task:
- State file = a JSON object of
name -> version (one key for the common single-version case, N keys for a multi-component wrapper). The build reads each component by key. This also makes the .json extension honest.
- Resolver contract: the
resolver-command prints a JSON object (e.g. {"esphome":"2026.6.2","esphome-device-builder":"1.0.12"}) instead of one line; for the trivial single-version case it prints {"version":"X"}. Alternatively accept multiple named resolvers as a map input.
- Bump-PR title/body summarize all changed keys (only the keys that actually moved). ESPHome-NonRoot's existing
check-esphome-version.yml is a working reference for that title/body logic (per-key "Bump X to Y" composition + a body listing each component and its PyPI link).
- Keep the consolidated default filename
upstream-version.json at the repo root beside version.json.
Outcome
With structured multi-key state, ESPHome-NonRoot can rename esphome-version.json -> upstream-version.json, adopt the canonical task, and retire its bespoke tracker - and any future multi-component wrapper gets the same path without re-inventing it.
Filed per the template's "Staying in Sync and Reporting Drift Upstream" contract rather than only patching downstream.
Context
Raised while re-syncing
ptr727/ESPHome-NonRootfrom template PR #167 (the downstream heads-up issue asked to migrate the bespoke version tracker to the canonicalcheck-upstream-version-task.yml).Gap
The canonical
check-upstream-version-task.ymlserializes only a single bare-string version: the resolver prints one line to stdout (echo "version=$version" >> "$GITHUB_OUTPUT") and the state file is written raw (printf '%s\n' "$VERSION" > "$STATE_FILE", defaultupstream-version.json).That cannot serve a wrapper that pins multiple upstream components consumed by the build. Concrete downstream example: ESPHome-NonRoot pins both
esphomeandesphome-device-builder, andbuild-docker-task.ymlreads each by key (.version,.device_builder_version) to set the image tag and build-args. There is no clean way to carry two keyed versions through a single stdout line / bare-string file, so the downstream keeps a bespokecheck-esphome-version.ymlinstead of converging on the canonical task.A second, smaller wrinkle: the default state file is named
upstream-version.jsonbut holds a bare string, so the.jsonextension is misleading today.Proposal
Make the canonical task serialize a structured JSON state file that supports one or many keyed versions, so a single-version wrapper and a multi-component wrapper both use the same task:
name -> version(one key for the common single-version case, N keys for a multi-component wrapper). The build reads each component by key. This also makes the.jsonextension honest.resolver-commandprints a JSON object (e.g.{"esphome":"2026.6.2","esphome-device-builder":"1.0.12"}) instead of one line; for the trivial single-version case it prints{"version":"X"}. Alternatively accept multiple named resolvers as a map input.check-esphome-version.ymlis a working reference for that title/body logic (per-key "Bump X to Y" composition + a body listing each component and its PyPI link).upstream-version.jsonat the repo root besideversion.json.Outcome
With structured multi-key state, ESPHome-NonRoot can rename
esphome-version.json->upstream-version.json, adopt the canonical task, and retire its bespoke tracker - and any future multi-component wrapper gets the same path without re-inventing it.Filed per the template's "Staying in Sync and Reporting Drift Upstream" contract rather than only patching downstream.