Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Three packages: `gcb-cli`, `gcb-client`, `gcb-host`
# Three packages: `gcb-core`, `gcb-client`, `gcb-host`

- **Date:** 2026-10-23
- **Status:** proposed; supersedes the single-package + esbuild-bundle plan
Expand All @@ -10,19 +10,19 @@ Split the repository into Yarn workspaces under `packages/`:

```text
packages/
cli/ gcb-cli shared CLI runtime, argument parsing, protocol, types
client/ gcb-client container side; depends on gcb-cli
host/ gcb-host host side; depends on gcb-cli and yaml
core/ gcb-core shared command runtime, argument parsing, protocol, types
client/ gcb-client container side; depends on gcb-core
host/ gcb-host host side; depends on gcb-core and yaml
```

Dependency graph:

```text
gcb-client ──> gcb-cli
gcb-host ──> gcb-cli, yaml
gcb-client ──> gcb-core
gcb-host ──> gcb-core, yaml
```

There is no dependency from `gcb-cli` to either side, and no dependency from
There is no dependency from `gcb-core` to either side, and no dependency from
`gcb-client` to `gcb-host`. The container installs only `gcb-client`; the host installs only
`gcb-host`. Both top-level packages expose the same `gcb` bin, so the external command is still one
name.
Expand All @@ -31,18 +31,18 @@ name.

- npm installs peers in ways that are easy to get wrong; a package boundary is enforced by the
dependency graph instead of by documentation.
- `gcb-cli` is a base dependency, not a dispatcher that owns both backends. It must not be able to
- `gcb-core` is a base dependency, not a dispatcher that owns both backends. It must not be able to
load `yaml`.
- A missing `yaml` must be impossible on a host and irrelevant in a container, not a runtime
surprise.

## Bin ownership

`gcb-client` and `gcb-host` each declare `"bin": { "gcb": "./bin/gcb.js" }`. The bin is a thin
wrapper: it imports the shared runner from `gcb-cli`, passes its own command backend, and exits with
wrapper: it imports the shared runner from `gcb-core`, passes its own command backend, and exits with
the result.

`gcb-cli` may carry a development bin, but it is not the published entry point. Relying on npm to
`gcb-core` may carry a development bin, but it is not the published entry point. Relying on npm to
link a dependency's bin into the global prefix is not something to build a release on.

Only one of the two top-level packages should be installed in a given environment. Installing both
Expand All @@ -53,7 +53,7 @@ globally would race for the same `gcb` link; that is documented rather than work
- `gcb-client` (implemented): `git-credential-*`, `setup`, `probe`, `logs`, `pr`.
- `gcb-host` (implemented): `app`, `host`, `daemon`, `init`, `stage`, `compose`, `diagnose`,
`authorize`, `host-opener`.
- `gcb-cli`: help/version, argument parsing, command lookup, error formatting, and the shared
- `gcb-core`: help/version, argument parsing, command lookup, error formatting, and the shared
wire/types.

`gh` and `config` are roadmap commands, not implemented yet. When `gcb gh` lands it will bypass the
Expand All @@ -72,16 +72,16 @@ self-contained artifact. That is a separate decision from this package split.
## Packaging and tests

- Root is private; `workspaces: ["packages/*"]`.
- Each package has its own `package.json`, `tsconfig.json`, `src/`, `dist/`, and `files`.
- Versions stay in lockstep; `gcb-client` and `gcb-host` depend on the exact `gcb-cli` version.
- Each package has its own `package.json`, `tsconfig.json`, `src/`, `lib/`, and `files`.
- Versions stay in lockstep; `gcb-client` and `gcb-host` depend on the exact `gcb-core` version.
- CI adds a real package test:
1. `npm pack` all three;
2. install `gcb-client` into a temporary prefix, assert `gcb --help` and `gcb setup --help`
work and `yaml` is absent;
3. install `gcb-host` into another prefix, assert `gcb app --help` and `gcb daemon --help` work
and `yaml` is present.
- Unit tests may continue to import `packages/*/src` through a development export condition; the
packaged entry points use `dist`.
packaged entry points use `lib`.

## Consequences

Expand Down
5 changes: 5 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Changesets

Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a tool to manage versioning and changelogs. Read the full documentation at https://changesets.dev.

We also have a quick list of common questions to get you started engaging with this project in our [FAQ](https://changesets.dev/faq).
19 changes: 19 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "https://unpkg.com/@changesets/config/schema.json",
"changelog": [
"@changesets/changelog-github",
{
"repo": "code-vaults/git-credential-broker"
}
],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"fixed": [
[
"gcb-core",
"gcb-client",
"gcb-host"
]
]
}
7 changes: 7 additions & 0 deletions .changeset/initial-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'gcb-core': patch
'gcb-client': patch
'gcb-host': patch
---

Prepare the first public release of the renamed packages.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ jobs:
run: |
$ErrorActionPreference = 'Continue'
$log = Join-Path $env:RUNNER_TEMP 'daemon.txt'
node --conditions=gcb-development packages/host/bin/gcb.js daemon --check *> $log
node --conditions=development packages/host/bin/gcb.js daemon --check *> $log
$code = $LASTEXITCODE
$out = Get-Content $log -Raw
Write-Host "exit=$code"
Expand All @@ -109,7 +109,7 @@ jobs:
run: |
$ErrorActionPreference = 'Continue'
$log = Join-Path $env:RUNNER_TEMP 'setup.txt'
node --conditions=gcb-development packages/client/bin/gcb.js setup *> $log
node --conditions=development packages/client/bin/gcb.js setup *> $log
$code = $LASTEXITCODE
$out = Get-Content $log -Raw
Write-Host "exit=$code"
Expand All @@ -126,7 +126,7 @@ jobs:
run: |
$ErrorActionPreference = 'Continue'
$log = Join-Path $env:RUNNER_TEMP 'compose.txt'
node --conditions=gcb-development packages/host/bin/gcb.js compose --socket-dir C:\sock *> $log
node --conditions=development packages/host/bin/gcb.js compose --socket-dir C:\sock *> $log
$code = $LASTEXITCODE
$out = Get-Content $log -Raw
Write-Host "exit=$code"
Expand All @@ -142,7 +142,7 @@ jobs:

- name: help still works
shell: pwsh
run: node --conditions=gcb-development packages/client/bin/gcb.js --help
run: node --conditions=development packages/client/bin/gcb.js --help

wsl:
timeout-minutes: 20
Expand Down
132 changes: 132 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
name: Release

on:
push:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# A publish that has started has to finish: cancelling it can leave some packages on npm and the
# rest not.
cancel-in-progress: false

permissions:
contents: read

jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
# `id-token` is npm trusted publishing; `contents` and `pull-requests` are the version commit
# and the release pull request.
contents: write
id-token: write
pull-requests: write
outputs:
version: ${{ steps.version.outputs.version }}
published: ${{ steps.changesets.outputs.published }}
steps:
- name: Checkout Repo
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Changesets writes each changelog from the commits since the last release.
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: lts/*
cache: yarn

- name: Enable Corepack
run: corepack enable

- name: Install Dependencies
run: yarn install --immutable

- name: Build
run: yarn build

- name: Read the package version
id: version
run: echo "version=$(jq -r .version packages/core/package.json)" >> "$GITHUB_OUTPUT"

- name: Create Release Pull Request or Publish to npm
Comment thread
JounQin marked this conversation as resolved.
Resolved
id: changesets
uses: changesets/action@ae32849d5ba541f9ae29e40e22a623bc13562f51 # v2.1.2
with:
commit-message: 'chore: release packages'
pr-title: 'chore: release packages'
version-script: yarn run version
publish-script: yarn release

image:
name: Publish gcb-${{ matrix.target }}
needs: release
if: needs.release.outputs.published == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
target: [client, host]
steps:
- name: Checkout Repo
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup QEMU
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
with:
platforms: arm64

- name: Setup Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1

- name: Log in to GHCR
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Work out the tags
id: tags
env:
VERSION: ${{ needs.release.outputs.version }}
IMAGE: gcb-${{ matrix.target }}
run: |
{
echo "ghcr.io/code-vaults/$IMAGE:latest"
echo "ghcr.io/code-vaults/$IMAGE:sha-${GITHUB_SHA}"
echo "ghcr.io/code-vaults/$IMAGE:${VERSION}"
} > "$RUNNER_TEMP/tags"
{
echo 'tags<<TAGS'
cat "$RUNNER_TEMP/tags"
echo 'TAGS'
} >> "$GITHUB_OUTPUT"

- name: Build and push
Comment thread
JounQin marked this conversation as resolved.
Resolved
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
target: ${{ matrix.target }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.tags.outputs.tags }}
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.version=${{ needs.release.outputs.version }}
org.opencontainers.image.title=gcb-${{ matrix.target }}
provenance: true
sbom: true
cache-from: type=gha,scope=${{ matrix.target }}
cache-to: type=gha,mode=max,scope=${{ matrix.target }}
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ corepack yarn pack:check # build, then show exactly what npm would publish
The CLI runs from source too, so there is no need to build to try a command:

```sh
node --conditions=gcb-development packages/client/bin/gcb.js --help
node --conditions=gcb-development packages/host/bin/gcb.js compose --socket-dir /tmp/sock # reads --config, $GIT_BROKER_CONFIG, else ./broker.config.yml
node --conditions=development packages/client/bin/gcb.js --help
node --conditions=development packages/host/bin/gcb.js compose --socket-dir /tmp/sock # reads --config, $GIT_BROKER_CONFIG, else ./broker.config.yml
```

`yarn test` builds first because the end-to-end suite execs `packages/client/bin/gcb.js` as a **real**
Expand All @@ -79,7 +79,7 @@ sources must stay erasable (`erasableSyntaxOnly` is on: no enums, no parameter p
`setup` cannot know the PATH of a later git process; do not pretend a setup-time check is a
guarantee. If a review finding depends on an unsupported shape, answer it in the PR and leave
the code alone.
- **The gh action catalog is the one source for an action.** `packages/cli/src/actions.ts`
- **The gh action catalog is the one source for an action.** `packages/core/src/actions.ts`
defines its command, flags, positionals, permissions and timeout; the token mint, the creation
preflight, the command table and the docs must read it rather than keep a second list. The broker
rebuilds a canonical argv from validated values and never forwards the caller’s argv. Add an
Expand Down Expand Up @@ -310,7 +310,7 @@ Full detail and the measurements behind them:

| Path | What |
|---|---|
| `packages/cli/src/` | shared parser, help/version, protocol, types, platform, paths and the gh action catalog |
| `packages/core/src/` | shared parser, help/version, protocol, types, platform, paths and the gh action catalog |
| `packages/client/src/helper.ts` | the container-side credential helper |
| `packages/client/src/commands/` | setup, probe, logs, pr, gh and remotes |
| `packages/host/src/broker.ts` | unix socket server, refusal codes, audit records |
Expand Down
Loading
Loading