chore(dev): release 5.0.0-beta.1#19
Closed
github-actions[bot] wants to merge 56 commits into
Closed
Conversation
Citty-based devicecloud.dev CLI, forked out of devicecloud-dev/dcd/cli and rewritten to add browser-based `dcd login` on top of the existing `DEVICE_CLOUD_API_KEY` flow. Ships `cloud`, `upload`, `list`, `status`, `artifacts`, `live`, plus `login` / `logout` / `whoami` / `switch-org`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eliminates the deprecated glob@10.5.0 warning users saw during
`npm install -g`. Both replacements are drop-in at the call sites:
- `methods.ts` now builds zips with yazl (walks directories via
`readdirSync({ recursive: true, withFileTypes: true })`) and
normalizes entry paths to POSIX separators.
- `execution-plan.service.ts` uses Node 22's built-in `fs.globSync`
with a `statSync` post-filter to replace the `nodir: true` option.
Drops the `glob@>=10.2.0 <10.5.0` pnpm override (only existed to
patch archiver's transitive) and the unused `@types/glob-to-regexp`.
Also casts `Buffer` to `Uint8Array<ArrayBuffer>` at the two
`new Blob([...])` call sites — @types/node v25 widened Buffer's
backing type, which TS rejects as a BlobPart. The cast is safe
because Node Buffers never use SharedArrayBuffer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous loopback handler read tokens from the redirect URL's query string. That forced the DCD frontend to navigate the browser to the loopback with tokens in the URL, which persisted them in browser history and in Referer headers. Move to POST with a JSON body so the tokens stay out of the URL entirely. Also tighten the trust boundary: CORS is now scoped to the exact origin of the frontend URL the CLI opened (from `new URL(frontendUrl).origin`), so a different page loaded in the same browser can't hit the loopback. Body is capped at 64 KB. Requires the matching DCD frontend change (feat/cli-login in dcd repo). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sparse-checks out moropo-com/dcd via a read-only deploy key to get the mock-api needed by the integration tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ESLint v9 drops support for .eslintrc + .eslintignore; add a minimal flat config using @eslint/js + typescript-eslint. Register eslint-plugin-unicorn and eslint-plugin-import with no rules enabled so the many legacy `// eslint-disable-next-line unicorn/...` comments in the codebase resolve rather than error. Mocha 11 loads spec files via dynamic import(), which bypasses the ts-node/register CJS hook. Replace it with `node --import tsx` in .mocharc.json so relative TS imports (without .ts extension) resolve. Drop the now-redundant TS_NODE_COMPILER_OPTIONS shim from the test runner. Fix the only real lint errors that surfaced: `let` -> `const` in cloud.ts, and a `no-control-regex` disable on the ANSI-strip in styling.ts. Update report-download.service.test.ts to pass AuthContext instead of the removed apiKey string. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Safari blocks HTTPS -> HTTP fetches to 127.0.0.1 as mixed content, so the previous loopback-based login failed with "Load failed" there. It also couldn't work from SSH sessions where the browser runs on a different machine from the CLI. Replace the loopback server with a PKCE S256 flow: the CLI mints state + code_verifier, opens <frontend>/cli-login with state + sha256(verifier), then polls POST /cli-login/claim on the dcd api every 2s until the frontend hands the session off. A leaked URL alone is inert — the raw verifier never leaves the CLI process. Rename the --no-browser flag's underlying arg from `no-browser` to `browser` (default true), because citty treats `--no-<flag>` as negating `<flag>`. Without this, --no-browser was silently ignored. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The web screen no longer knows or asks about orgs. After the PKCE claim returns a session, the CLI fetches /me/orgs with the fresh Bearer token and runs the same interactive picker used by `dcd switch-org` — single-org users auto-pick, multi-org users get a @clack/prompts select. Config is only written after a pick, so half-finished logins don't leave a partial session behind. switch-org and whoami: stop surfacing org ids. The positional on switch-org now matches by name (case-insensitive) with slug/id as quiet fallbacks, and the "switched to" / "logged in as" output drops the `(id)` parenthetical. whoami no longer prints the org id either. Extract fetchOrgs + pickOrg into src/utils/orgs.ts so login and switch-org share one source of truth for org selection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pin @xmldom/xmldom override to >=0.9.10 to clear the pnpm audit advisories that were surfacing through app-info-parser's copy of plist. Replace app-info-parser with node-apk in AndroidMetadataExtractor — same package/versionCode/versionName against the sample APKs, one transitive dep (node-forge) instead of half a dozen, and bundled TypeScript types so the hand-written declare-module shims can go. Drop consola (never imported), @types/tar (tar ships its own types), and ts-node (mocha already runs via tsx from .mocharc.json); update the single-test command in CLAUDE.md accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ask to confirm before overwriting an existing stored session. Silent clobber is fine if you know what you're doing but surprising if you run `dcd login` by accident while already authenticated; default to "no" so Enter keeps the session. Drop the post-authorize polling interval from 2s to 1s — fits the api's 60/min rate limit on /cli-login/claim exactly and feels snappy after the user clicks Authorize in the browser. Also suggest `dcd switch-org` after login when the user has multiple orgs. dcd logout: pass `scope: 'local'` to supabase-js signOut. The default is 'global', which revokes every session for the user (browser, mobile, other CLIs) — so running `dcd logout` would kick you out of the web app too. 'local' just clears this client's SDK state. Surface the api response body on fetchOrgs failures, and on a /me/orgs rejection probe Supabase's /auth/v1/user directly so we can tell whether it's an api misconfiguration or a dead Supabase session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consistent use of the `symbols.success/info/warning` helpers instead of
inline glyphs + colors.warning('⚠'), so updates to glyph style only
touch one place. `styling.box` now strips ANSI escapes when computing
line widths, so colored content inside the box pads correctly instead
of over-flowing.
Also: "Switched to X" / "Submitted" messages bold the verb for visual
parity with "Logged in" and other success lines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runs AndroidMetadataExtractor and MetadataExtractorService against test/fixtures/wikipedia.apk to pin the node-apk swap: asserts the extracted package id, covers canHandle() + unsupported extensions, and checks that a malformed .apk surfaces an error from the extractor but is swallowed (with a warning) by the service's try/catch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps runMain to record `command started` / `command completed` / `command failed` events around every invocation, and hooks telemetry.flushSync into logger.error so process.exit doesn't abandon in-flight events. flushSync shells out to curl because process.exit bypasses beforeExit, so an async fetch in flush() would be killed mid-flight. resolveAuth calls telemetry.configure once per invocation so the auth token never has to be re-derived for subsequent events. Unauthenticated commands (--help, --version, pre-login) buffer in memory and drop on exit by design — there's no identity to attach. Opt out per-invocation with DCD_TELEMETRY_DISABLED=1. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ships dcd as a single-file binary alongside the existing npm package so users without Node 22+ can install in one line: curl -fsSL https://get.devicecloud.dev/install.sh | sh irm https://get.devicecloud.dev/install.ps1 | iex Build: scripts/build-binaries.mjs invokes bun build --compile for five targets (darwin arm64/x64, linux arm64/x64, windows x64) and emits a SHA256SUMS manifest. .github/workflows/release-binaries.yml fires on the same release: published event as npm-publish.yml, so one tag produces both artifacts. Bun's Node-compat layer handles every current dep (supabase-js, tus-js-client, tar, node-apk, yazl, plist, bplist-parser, node-stream-zip) — verified locally with `dcd --help`, `whoami`, and `list` exercising the supabase path end-to-end from the compiled binary. New `dcd upgrade` subcommand atomically replaces the running binary on POSIX: download to <execPath>.new, verify SHA256 against SHA256SUMS, rename(2) over the running exec. Safe mid-run because the kernel keeps the old inode alive until the process exits. Refuses to run for npm-installed users (detected via process.versions.bun — set by Bun's runtime and inherited by bun-compiled binaries); prints manual installer instructions on Windows since you can't rename over a running .exe. version.service swaps the `npm view` shell-out for a fetch against get.devicecloud.dev/latest.json with a 3s timeout, so binary users (no `npm` on PATH) still get the outdated-version hint. The hint itself branches between `dcd upgrade` and `npm install -g @devicecloud.dev/dcd@latest` based on install method. install.sh / install.ps1 detect platform via uname / IsWow64, verify SHA256 before installing, honor DCD_VERSION, DCD_INSTALL_DIR, and DCD_DOWNLOAD_BASE overrides. install.sh tested end-to-end against a local python http.server serving a staged release (download → verify → install → PATH hint). The Railway service that fronts get.devicecloud.dev lives in the infra repo at infra/get-devicecloud-dev — until that's deployed and Cloudflare DNS is in place, the curl|sh URLs in the README won't resolve. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GHSA-jxxr-4gwj-5jf2 widened the vulnerable brace-expansion range to <5.0.6, so the previous pin to 5.0.5 now lands inside it. Bump the override target to 5.0.6 and add a ws override for GHSA-58qx-3vcg-4xpx (via @supabase/realtime-js). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Footnote pointing staff at the internal docs site without disrupting the customer-facing install flow at the top.
docs: link to internal documentation site
Add both to REMOVED_MAESTRO_VERSIONS so the CLI hard-errors locally, matching the dcd API gate. JSDoc example in the generated schema types also pruned to keep the dev-server example aligned with reality.
….4.0 Deprecate Maestro 1.39.1 and 2.4.0
Adds a single `Release` workflow that runs release-please against both branches: pushes to `production` cut a stable release (npm `latest`, no -beta suffix), pushes to `dev` cut a beta prerelease (npm `beta`, e.g. 5.1.0-beta.0, GitHub pre-release badge). Merging the Release PR creates the tag + GitHub Release and chains directly into npm publish and binary uploads — chained as needs-jobs because GITHUB_TOKEN-created releases don't fire `release: published`. Converts npm-publish.yml and release-binaries.yml from `release: published` triggers into reusable `workflow_call` workflows so the orchestrator can drive them with the right inputs (release_type, tag_name). Drops the post-publish tag-creation step from npm-publish — release-please owns the tag now. Also drops contents:write on the npm job; it no longer pushes anything. Adds the two release-please config files (prod + beta) plus their manifests pinned at 5.0.0, the current published version. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- telemetry: redact --api-key/--env/--app-url values from argv before shipping events; pass auth headers to curl via 0600 config file instead of process arguments - polling: report PASSED only when every test passed (unknown/cancelled statuses and empty result sets no longer exit 0 in CI); retry on empty results; stop spinner on fatal polling errors; follow retry_of chains when grouping retries; fix off-by-one in failure limit - cloud: defer process.exit until after finally so --json output prints on failed runs and temp files are cleaned; ship failure telemetry on RUN_FAILED; re-emit non-punycode warnings; guard failure-path artifact downloads from masking the run-failed exit code - index: replace citty runMain with a replica that records failure telemetry, honors CliError.exitCode, and prints clean errors for all commands instead of stack traces - gateways: stream downloads via pipeline() so mid-download network errors fail fast instead of hanging or truncating silently; typed ApiError with HTTP status; parse JSON responses inside error handling with operation context; auth-mode-neutral 401 message - auth: serialize session refresh behind a lockfile and re-read/merge config to survive concurrent invocations (CI matrices); sessionOnly option so switch-org works with DEVICE_CLOUD_API_KEY exported; switch-org defaults to the logged-in env's API URL instead of prod - paths: segment-aware common-root computation and anchored prefix stripping shared via src/utils/paths.ts (replaceAll corrupted paths when the root substring recurred or collapsed to '') - methods: abort before upload on 401/403 from SHA dedup check; accept zips without explicit directory entries (ignoring __MACOSX/.DS_Store) and close zip handles on all paths; remove dead code - execution-plan: apply --exclude-flows to workspace config globs; fix double directory join for string runFlow/runScript deps; parse YAML docs beyond the second separator; dedupe sequential flows - cloud/upload: positional flow file works with --app-file/--app-url; '.apk' extension check no longer matches 'myapk'; moropo temp dirs cleaned up after runs and on download errors - metadata-extractor: route sync throws in zip ready handler to reject instead of crashing; close handles on all paths; dedupe parseInfoPlist - misc: corrupt config warns instead of silently logging out; version compare handles prereleases; pagination hint uses actual page size; parseIntFlag rejects negatives/garbage; abort timer cleanup in connectivity checks; no retries on 4xx for Expo URL downloads; env-aware console URLs; correct --json/--json-file exit-code docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rtion - test-runner: run the suite with an isolated DCD_CONFIG_DIR so the CLI under test can never read or refresh the developer's real session (five auth tests previously depended on local login state) - test-runner: replace the fixed 3s sleep with readiness polling against the mock API (30s deadline, fails loudly), forward mock API output with a prefix instead of leaving pipes unread, fail the run if the server dies mid-suite, kill the server's whole process group on POSIX, and treat signal-killed mocha as failure instead of exit 0 - metadata-extractor test: the expect.fail call was caught by its own catch block (AssertionError instanceof Error), so the test could never fail; assert via a threw flag instead Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- cli-ci: drop --ignore-registry-errors so a failed audit can't pass silently; scope the workflow token to contents: read - npm-publish: refuse prod-tag publishes from any ref other than the production branch (workflow_dispatch could previously publish any non-beta version to npm latest, bypassing release-please) - install.sh: wrap the body in main() so a truncated curl | sh download executes nothing - install.ps1: pin TLS 1.2 on Windows PowerShell 5.x; read the raw unexpanded User PATH and write it back as ExpandString (previously froze %VAR% entries); append instead of prepend and skip when already present Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- src/types/schema.types.ts was a byte-identical, hand-maintained copy of the generated file with no importers; delete it and its eslint ignore entry - CLAUDE.md described the abandoned loopback-server login flow; it now documents the implemented PKCE + claim-polling flow, and the telemetry section reflects the new index.ts runner Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The self-hosted runner's git upgrade made cone-mode sparse-checkout
hard-error on file patterns ("'api/swagger.json' is not a directory"),
breaking every CI run since June 10 with no repo change. Switch the
dcd checkout to non-cone patterns, which support file paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…84353683 Add Claude Code GitHub Workflow
The review workflow ran on PR #5 and completed the review, but pull-requests: read meant all 15 attempts to post inline comments were denied and the PR received nothing while the check still showed green. The official claude-code-action review examples use write. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ci: grant pull-requests write so Claude review can post its comments
The code-review plugin's spec stops before posting anything when --comment is absent: "If --comment argument was NOT provided, stop here. Do not post any GitHub comments." Combined with show_full_output: false, the review on PR #7 ran to completion and was entirely discarded. The pull-requests: write permission from #6 was necessary but not sufficient — this flag is the missing half. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ci: pass --comment so the Claude review is actually posted to the PR
strict: false had the entire strict family off for a published binary parsing loosely-typed API responses. The burn-down turned out to be small — five errors: - upgrade.ts: DOM/Node web-stream typing clash on Readable.fromWeb (same cast pattern already used in api-gateway.ts and expo.ts) - results-polling: TestResult indexed an optional `results` array; duration_seconds needed an explicit null for absent values - version.service: resolveMaestroVersion could carry undefined through to its return — now fails fast with a clear message when compatibility data provides no default version Also closes the "tests get zero static checking" gap: tsconfig.test.json type-checks src + test strictly via `pnpm typecheck` (new CI step), and `pnpm lint` now covers test/ (with chai's property-style assertions exempted from no-unused-expressions). forceConsistentCasingInFileNames enabled alongside. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix: enable TypeScript strict mode and type-check tests in CI
The upload pipeline materialized the whole binary up to three times:
prepareFileForUpload read it into a File (with .app dirs zipped in
memory first), the TUS path copied it again via Buffer.from(await
file.arrayBuffer()), and the Backblaze simple path made a third copy.
A 500 MB binary peaked at 2.18 GB RSS; large iOS zips could OOM CI
runners.
Everything now streams from disk via a small UploadSource descriptor
({ diskPath, name, size, contentType }):
- .app dirs are zipped to a temp file through yazl's output stream
(cleaned up in a finally) instead of being buffered
- SHA-256 is computed from a read stream
- the Supabase TUS upload feeds tus-js-client a createReadStream with
uploadSize — it buffers one 6 MB chunk at a time
- the Backblaze large path uses the existing readFileChunk disk reads
(previously dead code — the in-memory File always shadowed it)
- the Backblaze simple path keeps one transient buffer, bounded by the
server only choosing that strategy for small binaries (S3 pre-signed
PUTs reject chunked encoding, ruling out a stream body)
Peak RSS uploading a 500 MB binary: 2.18 GB before, 165 MB after.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR #9's review ran 40 turns, found issues, and hit 46 permission denials trying to post them — the plugin command's allowed-tools frontmatter doesn't reach the inline-comment MCP tool in the action's headless mode (the gh pr comment path worked on #7, inline comments never have). Pass the same tool list through --allowedTools, the mechanism the action's docs prescribe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ci: explicitly allow the review's posting tools via claude_args
fix: stream uploads from disk instead of holding binaries in memory
New high-severity advisory against esbuild <0.28.1 (via tsx) started failing the security audit on every PR. Dev-only dependency; tsx and the full test suite verified working on 0.28.1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(deps): override esbuild to >=0.28.1 for GHSA-gv7w-rqvm-qjhr
The five live subcommands (start/install/exec/stop/status) each built their own fetch() with hand-spread auth.headers and per-call handleApiError, the one architecture violation flagged in the review (CLAUDE.md: commands orchestrate services, no I/O logic). They also bypassed ApiGateway's network-error enhancement, so a DNS/connection failure surfaced as a bare "fetch failed" TypeError instead of the friendly diagnostic every other command gives. Adds startLiveSession/installLiveBinary/execLiveYaml/stopLiveSession/ getLiveSession to ApiGateway (with typed LiveSession/LiveSessionSummary/ LiveExecResult interfaces, since the swagger has no /live schema), each following the existing try/fetch/handleApiError/enhanceFetchError skeleton. live.ts keeps all its presentation logic and just calls the gateway. Net -66 lines in the command. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses code review on PR #13: cross-layer domain types belong in src/types/domain/ per CLAUDE.md (alongside auth.types.ts and device.types.ts), and the non-obvious reason these are hand-defined (swagger has no /live routes for openapi-typescript to generate from) is now captured in a comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
refactor: route live session commands through ApiGateway
…docs Fixes three issues found during a full feature sweep of v5.0.0: - #15: `dcd live <subcommand>` printed the parent "Live Session Commands" menu after every subcommand, because citty invokes a parent command's `run` after dispatching to a subcommand. `liveCommand.run` now returns early when the first positional matches a subcommand name (same rule citty uses to dispatch). Also fixes the systemic doubled error wording ("Failed to execute test failed: ...") in ApiGateway.handleApiError. - #16: session commands defaulted --api-url to prod and ignored the api_url stored by `dcd login`, so a dev/staging Bearer token was sent to prod and rejected as "Invalid or expired JWT". Added resolveApiUrl() (explicit flag > stored config.api_url > prod) and routed cloud, upload, list, status, artifacts and live through it. switch-org now uses the shared helper instead of a hand-rolled copy. - #17: --env was described as "environment variable files" (valueHint: path) but actually parses KEY=VALUE pairs. Corrected the description and valueHint. Adds 6 unit tests covering resolveApiUrl precedence and the live menu behavior. Full suite: 128 passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `pnpm audit --audit-level moderate` CI step flagged three advisories in transitive/direct deps (unrelated to the feature fixes): - ws <8.21.0 (high, GHSA-96hv-2xvq-fx4p) via @supabase/realtime-js - tar <=7.5.15 (moderate, GHSA-vmf3-w455-68vh) - js-yaml <=4.1.1 (moderate, GHSA-h67p-54hq-rp68) Extend the existing pnpm.overrides pattern to force patched versions: ws 8.21.0, tar 7.5.16, js-yaml 4.2.0. `pnpm audit` is now clean; full suite still 128 passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix: honor logged-in env for api-url, stop live menu leak, fix --env docs
- live: new `hierarchy`, `screenshot`, `run` subcommands; `--wait` on start/install (streams device phases); `--file` on exec; `--device-locale` and `--android-device`/`--android-api-level` on start; richer `status` - live run: submit async and poll (with keepalive) so long flows aren't capped by the server's 120s exec limit; falls back to blocking on older servers - results polling: tolerate more transient failures (10->30) with backoff and a resume hint pointing at `dcd status` - fix: recover `--yaml "- ..."` values citty drops for leading-dash args - gitignore local `flows/` scratch directory (Builds on the existing config-store resolveApiUrl env handling.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
generic-tablet isn't built for live; the supported set is pixel-6, pixel-6-pro, pixel-7, pixel-7-pro (validated server-side). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Release-As: 5.0.0-beta.1 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ef520e7 to
e64e77e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 I have created a release beep boop
5.0.0-beta.1 (2026-06-18)
Features
Bug Fixes
Dependencies
Code Refactoring
Miscellaneous
This PR was generated with Release Please. See documentation.