Skip to content

feat(publish): add extctl publish — stage, screenshot, and PR extensions to the marketplace - #93

Merged
LukasHirt merged 2 commits into
mainfrom
feat/publish-command
Jul 23, 2026
Merged

feat(publish): add extctl publish — stage, screenshot, and PR extensions to the marketplace#93
LukasHirt merged 2 commits into
mainfrom
feat/publish-command

Conversation

@LukasHirt

Copy link
Copy Markdown
Owner

Summary

  • Adds extctl publish / publish approve / publish retry-screenshots: scans for web-extensions releases not yet in owncloud/marketplace, stages a submission per extension (bundle, resolved license/tags/minOCIS, dedicated Claude-generated screenshots) locally, then pushes + opens a PR only on explicit approve — so tags/minOCIS/screenshots always get human review before anything goes public.
  • License/minOCIS are never guessed: missing license fails that extension rather than staging a wrong one; minOCIS falls back through previous-release reuse → git-history inference → left unset.
  • Screenshots come from a dedicated Claude-written Playwright spec (never the extension's own acceptance.spec.ts), and the writing session now has scoped Bash access (Bash(pnpm playwright test *) only) to actually run its own spec against a live oCIS and fix real failures before returning, with a fresh-spec outer retry loop and a visually-degenerate-image check as safety nets.
  • Several infra bugs found and fixed along the way (see CLAUDE.md for full detail): a claude -p prompt-misreading issue fixed via claude.anchorPrompt, a Docker Desktop/virtiofs mount bug (ensureExternalSitesManifest), missing pnpm install in the capture path, a stale-auth-cache-vs-fresh-oCIS bug (clearStaleAuthCache), and failed tests' screenshots being surfaced as successful captures.

Test plan

  • go build ./... / go vet ./... / go test ./... all clean
  • End-to-end validated against a real fork (LukasHirt/appstore): staged and captured genuinely passing, good-quality screenshots for photo-addon@0.1.0
  • Approved advanced-search@0.1.0 end-to-end — PR opened successfully (feat(advanced-search): add 0.1.0 to the marketplace appstore#12)
  • Verified .gitignore/staged diff excludes all local checkouts, runs/, and config before committing

🤖 Generated with Claude Code

…ons to the marketplace

Closes the last gap in the pipeline: extctl release tags a merged extension
and web-extensions' own CI builds a GitHub Release from it, but nothing
ever got the extension into owncloud/marketplace, where oCIS actually reads
its catalog from.

extctl publish scans for released-but-unpublished extensions and, per
extension, downloads the release bundle, resolves license/subtitle from its
own package.json (never guessed — missing license fails that extension
rather than staging a wrong one), resolves tags/minOCIS by reusing the
extension's most recent prior marketplace release if one exists, else
Claude inference (tags) or git-history inference (minOCIS), else left
unset — and generates screenshots via a dedicated Claude-written Playwright
spec (never the extension's own acceptance.spec.ts, which optimizes for
functional coverage, not for looking good publicly).

Deliberately stops at a local commit rather than pushing and opening a PR:
`extctl publish approve` is the separate, explicit step that does that, so
every submission (tags, minOCIS, screenshots) gets human review first, and
`extctl publish retry-screenshots` can redo just the screenshots without
restaging everything else.

Screenshot capture reliability went through several real rounds of
hardening this session, each traced to a genuine root cause rather than
papered over with retries:
- claude -p's headless first-turn handling could misread a long, structured
  prompt as reference context instead of a request — every extctl prompt
  now gets an explicit trailing task marker appended (claude.anchorPrompt).
- A Docker Desktop/virtiofs bug breaks a file-mount stacked on a
  directory-mount when the underlying file doesn't exist yet, which it
  never does for most extensions on a fresh checkout (ensureExternalSitesManifest).
- The capture path never ran `pnpm install`, so the playwright binary
  simply didn't exist on a fresh checkout.
- A cached Playwright storageState survives independently of the oCIS
  instance that issued it — since capture always tears down and recreates
  oCIS, a stale cached token fails signature verification against the new
  instance's regenerated key, looking exactly like an auth flake
  (clearStaleAuthCache).
- A failed test's screenshot (Playwright attaches one regardless of
  pass/fail) was being surfaced as a successful capture; now only passed
  results are ever collected.
- The screenshot-writing Claude session now gets scoped Bash access
  (Bash(pnpm playwright test *) only) to actually run the spec it wrote
  against a live oCIS and fix real failures before returning, instead of
  writing blind — with a fresh-spec outer retry loop and a
  visually-degenerate-image check as further safety nets.

See CLAUDE.md for the full command reference and the reasoning behind each
of these fixes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Lukas Hirt <info@hirt.cz>
@LukasHirt LukasHirt self-assigned this Jul 23, 2026
A cloned repo doesn't inherit its source's local git config — identity set
via `git config` (without --global) lives in .git/config, which `git clone`
doesn't copy. Four new tests cloned into a fresh checkout and then
committed into it without setting an identity there, relying on a global
git config to paper over it — worked on a dev machine with one configured,
failed on CI runners that have none (same class of bug already hit and
fixed once before for TestRebaseOntoOrigin's otherClone).

Reproduced locally with HOME/GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM pointed
away from any real config, matching a CI runner; all four now pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Lukas Hirt <info@hirt.cz>
@LukasHirt
LukasHirt merged commit 98a58cc into main Jul 23, 2026
3 checks passed
@LukasHirt
LukasHirt deleted the feat/publish-command branch July 23, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant