Skip to content

feat(cli): difyctl release pipeline + tokenless installers#37036

Open
GareArc wants to merge 8 commits into
mainfrom
feat/difyctl-release-v1
Open

feat(cli): difyctl release pipeline + tokenless installers#37036
GareArc wants to merge 8 commits into
mainfrom
feat/difyctl-release-v1

Conversation

@GareArc
Copy link
Copy Markdown
Contributor

@GareArc GareArc commented Jun 4, 2026

Summary

Reworks the difyctl release pipeline and installers so binaries are published as public GitHub Releases and installed without authentication. Replaces the previous flow, which built per-run Actions artifacts and required a token to download them.

Closes WTA-426

What changed

Release workflow (.github/workflows/cli-release.yml)

  • Trigger-agnostic entry. Runs from workflow_dispatch, workflow_call, repository_dispatch (difyctl-release), and push of a difyctl-v* tag. The version is always sourced from cli/package.json, never inferred from the trigger.
  • Two-job split: validaterelease.
    • validate reads the manifest, checks the manifest schema, enforces that a pushed tag equals difyctl-v<version>, and runs an idempotency guard (gh release view) so an already-published version is skipped.
    • release builds all targets, generates checksums, and publishes the GitHub Release (creating the difyctl-v<version> tag).
  • Immutable releases. A published version is never overwritten; the idempotency guard makes re-runs no-ops. Fixing a shipped bug means a patch bump.
  • Serialized concurrency. A single fixed concurrency group with cancel-in-progress: false ensures releases never run in parallel and an in-flight release is never cancelled mid-publish.

Build tooling

  • cli/.bun-version (new). Pins Bun for reproducible bun build --compile output across all targets.

Before / after

Before After
Artifact source Actions workflow artifacts (per run) Public GitHub Releases
Install auth GH_TOKEN required None
Triggers dispatch + tag dispatch + call + repository_dispatch + tag
Idempotency none gh release view guard, immutable
Concurrency per-ref, cancel-in-progress single group, never cancelled
Windows install not supported install.ps1
Checksum verify fail-closed sha256 (sh + ps1)

Release flow

flowchart TD
    A[workflow_dispatch] --> V
    B[workflow_call] --> V
    C[repository_dispatch: difyctl-release] --> V
    D[push tag difyctl-v*] --> V

    subgraph validate
        V[read version from cli/package.json] --> G1[validate manifest]
        G1 --> G2{tag == difyctl-v&lt;version&gt;?}
        G2 -- no --> X[fail]
        G2 -- yes / non-tag --> G3{release already exists?}
    end

    G3 -- yes --> S[skip: immutable]
    G3 -- no --> R

    subgraph release
        R[build all targets] --> CK[generate sha256 checksums]
        CK --> P[publish GitHub Release + tag]
    end
Loading

Targets: linux-x64, linux-arm64, darwin-x64, darwin-arm64, windows-x64.exe, plus a difyctl-v<version>-checksums.txt.

Install flow

flowchart TD
    I[install-cli.sh / install.ps1] --> Q[resolve highest tag via git/matching-refs]
    Q --> C{channel}
    C -- stable --> ST[highest non-prerelease]
    C -- rc --> RC[highest -rc.N]
    ST --> DL[download asset + checksums.txt]
    RC --> DL
    DL --> Vfy{sha256 matches?}
    Vfy -- no --> Fail[abort]
    Vfy -- yes --> Inst[install to PREFIX/bin/difyctl]
Loading

Testing

  • vitest run cli/scripts/install-cli.test.ts — resolver unit tests pass (channel filter, rc ordering).

…allers

- cli-release.yml: workflow_dispatch/workflow_call/repository_dispatch/tag
  triggers; version sourced from cli/package.json (not the trigger). Split into
  validate + release jobs with an idempotency guard (gh release view) and a
  tag-vs-manifest match guard so a difyctl-v* tag push must equal the manifest
  version (prevents a dangling/mismatched tag). Releases may be cut from any
  branch. Concurrency serialized (fixed group, cancel-in-progress: false) so
  releases never double-publish.
- install-cli.sh: tokenless resolution via git/matching-refs, channel-aware
  (stable/rc) selection, fail-closed sha256 verification; distinguishes a fetch
  failure (network / API rate limit) from "no release found".
- install.ps1: Windows installer at parity with install-cli.sh.
- install-cli.test.ts: resolver unit tests (rc ordering, channel filter).
- .bun-version: pin Bun 1.3.11 for reproducible --compile builds.
@GareArc GareArc requested review from a team, crazywoola and laipz8200 as code owners June 4, 2026 02:25
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jun 4, 2026
GareArc added 2 commits June 3, 2026 20:38
Centralize release naming (tag, asset filenames, checksum file, target
list, arch/os tokens) so producers derive from one source instead of
duplicating literals across build, checksum, and workflow files.

- cli/package.json: add difyctl.release block (tagPrefix, binName,
  checksumsSuffix, targets[id/bunTarget/exe]) as the naming data source.
- cli/scripts/release-naming.mjs: new format source + validator; subcommands
  tag/asset/checksums/tag-prefix/targets/validate.
- release-build.sh, release-write-checksums.sh: derive target list and asset
  names from release-naming.mjs; drop hardcoded maps and literals.
- release-validate-manifest.sh: run release-naming.mjs validate, rejecting a
  malformed difyctl.release before any build derives from it.
- cli-release.yml: emit tagPrefix from the manifest step; tag guard,
  idempotency check, tag_name, and files glob all derive from it.
- install-cli.sh, install.ps1: standalone installers stay hardcoded by
  convention; add a comment pointing at the source.
Add a channel registry to release-naming.mjs as the single source for which
version forms are releasable and resolvable, and enforce it before any release
is cut.

- release-naming.mjs: CHANNELS dispatch table { name, prerelease, versionForm };
  new channels/prerelease subcommands; validate now rejects any version that
  does not match its declared channel form. Each versionForm is pinned to what
  the installer channel filters accept (stable = no prerelease; rc = -rc.N with
  nothing trailing), so a version that passes validate is guaranteed resolvable
  at install time. Closes the silent-publish hole (e.g. channel=rc + 1.2.3-rc5
  with no dot, a bare version on rc, or trailing +build on rc).
- release-validate-manifest.sh: drop the hardcoded version regex and rc|stable
  case; release-naming.mjs validate is the single source for those rules.
- cli-release.yml: emit prerelease from the registry and use it for the release
  prerelease flag instead of a hardcoded channel != 'stable'.

Extend by adding one CHANNELS entry; validity, version-form enforcement, and the
prerelease flag all derive from it.
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jun 4, 2026
GareArc and others added 5 commits June 3, 2026 21:01
Run scripts/release-validate-manifest.sh as an ubuntu-only step in cli-tests.yml
(before pnpm ci) so a malformed version, channel, or difyctl.release block fails
the PR instead of only the release. Same gate the release workflow uses;
pnpm ci stays pure-node and portable.
Replace the per-field manifest plumbing in cli-release.yml with a single
generic emitter, so adding a release field no longer means editing the
workflow in multiple places.

- release-naming.mjs: add a github-env subcommand emitting every field CI needs
  (version, channel, prerelease, minDify, maxDify, tagPrefix) as key=value lines.
- cli-release.yml: each job pipes `release-naming.mjs github-env` into
  $GITHUB_ENV once and references ${{ env.<field> }} (or shell $field); drop the
  per-field run reads, the enumerated job outputs (now just already_exists), and
  all fromJSON use sites. Both jobs check out the same validated commit, so each
  self-derives with no cross-job output to thread.
- release-write-checksums.sh: self-derive CLI_VERSION from package.json when
  unset, mirroring release-build.sh, so the release job needs no version env.

Adding a release field now touches only githubEnv().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant