Skip to content

feat(cli): add appkit doctor command - #489

Open
IamGalymzhan wants to merge 18 commits into
mainfrom
feat/doctor-command
Open

feat(cli): add appkit doctor command#489
IamGalymzhan wants to merge 18 commits into
mainfrom
feat/doctor-command

Conversation

@IamGalymzhan

@IamGalymzhan IamGalymzhan commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

appkit doctor - cli command that checks connectivity to the Databricks resources

The command does four level of checks:

  1. auth - validates DATABRICKS_HOST and authenticates to the workspace
  2. config - validates that each needed environmental variable is present (e.g. warehouse_id, genie_space_id)
  3. existence - checks that given resource exists and reachable by read requests ( .get() or SELECT 1) to check connection
  4. wiring - checks that resources are declared correctly across appkit.plugings.jsonapps.yamldatabricks.yml

Output is a list of resources and problems if any with hints. Also there is options --json for machine readable output. and --detail for more detailed overview with error itself.

Examples:

Failed auth

image

Genie Space is not provided, Job is not accessible or doesn't exists

image

Files are not added to the app.yaml

image

Everything is setup correctly

image

How it works:

  1. Resolve all resources needed for the app from appkit.plugins.json
  2. Auth: calls currentUser.me() to check that auth is fine
  3. Config: for each resource declared checks that corresponding environmental variable is present
  4. Existence: for each resource declared tries to connect and check if resource exists / has permission to access
  5. Wiring: checks that all required resources are declared correctly across appkit.plugins.json, databricks.yml and apps.yaml
  6. Print a report for user or for the agent with --json option

@github-actions

Copy link
Copy Markdown
Contributor

🔬  Run evals on this PR  ·  Go to Evals Monitor →

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 797 KB (+34 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 820 KB (+29 KB) 286 KB (+9.9 KB)
Type declarations 302 KB (+17 KB) 103 KB (+5.9 KB)
Source maps 1.6 MB (+65 KB) 536 KB (+21 KB)
Other 11 KB 3.7 KB
Total 2.7 MB (+110 KB) 929 KB (+37 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 86 KB (+10 B) 2.5 KB 89 KB (+10 B) external 281 KB (+34 B)
./beta 44 KB (+4.6 KB) 429 B (+198 B) 45 KB (+4.8 KB) external 129 KB (+9.5 KB)
./type-generator 19 KB 0 B 19 KB external 54 KB

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 82 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 29 KB
./beta stream-manager.js initial 5.8 KB
./beta wide-event-emitter.js initial 3.2 KB
./beta databricks.js initial 3.0 KB
./beta configuration.js initial 2.1 KB
./beta service-context.js initial 1.3 KB
./beta client-options.js initial 220 B
./beta supervisor-api.js lazy 184 B
./beta databricks.js lazy 132 B
./beta index.js lazy 113 B
./type-generator index.js initial 19 KB

@databricks/appkit-ui

npm tarball (packed): 305 KB (+101 B) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 360 KB (+390 B) 119 KB (+60 B)
Type declarations 205 KB 74 KB
Source maps 686 KB (+390 B) 224 KB (+66 B)
CSS 16 KB 3.3 KB
Total 1.2 MB (+780 B) 422 KB (+126 B)
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 4.3 KB 49 KB 54 KB 208 KB 12 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 429 KB (+55 B) 49 KB 478 KB (+55 B) 1.3 MB 168 KB (+390 B)
./react/beta 20 B 0 B 20 B 0 B 0 B

Chunks:

Entry Chunk Load Size (gz)
./js index.js initial 4.2 KB
./js chunk initial 120 B
./js apache-arrow lazy 49 KB
./js/beta beta.js initial 20 B
./react index.js initial 427 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 20 B

Diagnose whether an AppKit app's declared Databricks resources are actually
usable, beyond the startup env-var check. Registered as `appkit doctor`.

Three layers per resource:
- auth: validate DATABRICKS_HOST, then currentUser.me() (once, app-wide)
- config: offline env-var presence check
- existence: live per-type probe — control-plane .get() for warehouse,
  serving, genie, job, volume, vector index, uc_function; a real SELECT 1
  connection for Lakebase/postgres. Errors are classified (NOT_FOUND,
  INVALID_VALUE, ACCESS_DENIED) with clean one-line messages.

Actionable hints translate opaque failures into the fix: expired/missing
credentials to the right `databricks auth login`, a serving endpoint keyed by
id to its name, and a Lakebase auth failure to the PGUSER/identity mismatch.

Reaches the Databricks SDK / @databricks/appkit only through a runtime
import in databricks-client.ts, keeping the SDK-free shared package free of
the dependency and degrading gracefully when it is absent.

Output is a friendly list (errors first; plugin/type + reason shown only on
rows needing attention) or --json; exit code is non-zero on any error so it
can gate CI.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
@IamGalymzhan
IamGalymzhan force-pushed the feat/doctor-command branch from 8907117 to 0744f9a Compare July 24, 2026 11:24
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks CLI):

gh run download 30629284434 -R databricks/appkit -n appkit-template-0.48.0-pr.8dea943-feat-doctor-command-489 -D appkit-pr-489 \
  && unzip -o "appkit-pr-489/appkit-template-0.48.0-pr.8dea943-feat-doctor-command-489.zip" -d "appkit-pr-489" \
  && databricks apps init --template "appkit-pr-489"

The template pins @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

@IamGalymzhan
IamGalymzhan force-pushed the feat/doctor-command branch from fc846c6 to 1dde6f9 Compare July 27, 2026 09:40
Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
@IamGalymzhan
IamGalymzhan force-pushed the feat/doctor-command branch from 1dde6f9 to 7569c08 Compare July 27, 2026 09:55
Run existence probes concurrently via Promise.all (independent network
reads; input order preserved), cache DATABRICKS_HOST in a local, and drop
a redundant `unknown | undefined` parameter type.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
@IamGalymzhan
IamGalymzhan marked this pull request as ready for review July 27, 2026 13:13
@IamGalymzhan
IamGalymzhan requested a review from a team as a code owner July 27, 2026 13:13
IamGalymzhan and others added 15 commits July 29, 2026 10:48
…bility

- Add --env-file flag to load an explicit env file (e.g. .env.local),
  overriding the auto-loaded .env so doctor checks the app's real env.
- Add --detail flag to show the full raw SDK error; by default the report
  shows a short "authentication failed" headline plus an actionable hint.
- Make auth hints action-first and reference the profile/host in use:
  classify unreachable-host (DNS/TLS) failures, recover the SDK-resolved
  profile so the login hint targets the right one, and surface it in the
  header.
- Collapse auth-skipped resources into a single line, keep rows with real
  findings, and nudge toward --detail at the end.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
Match the capitalized "Details:" label used in the rendered report.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
…port

Extend `appkit doctor` to understand Databricks Asset Bundles and to
render a cleaner, single-list report.

- Classify each resource's provenance from databricks.yml + app.yaml
  (bundle.ts): external (probed live) vs bundle-managed (created on
  deploy, reported as such rather than a false NOT_FOUND).
- Add an offline three-file wiring check (checks-wiring.ts) that spans
  app.yaml <-> databricks.yml <-> plugin env vars, catching gaps the
  official validators miss: VALUEFROM_UNBOUND, BUNDLE_REF_MISSING, and
  ENV_UNWIRED. A wiring error gates the exit code for pre-deploy CI.
- Filter to plugins marked requiredByTemplate: true.
- Redesign the report: one flat, severity-sorted checklist (no titled
  sub-sections), every row sharing one shape (glyph + label, indented
  detail, hint set off by blank lines). Reserve colour for actionable
  tokens (cyan ids/code spans, bold env vars) via picocolors, which
  auto-disables for non-TTY / NO_COLOR.
- Shorten and standardise error messages: drop redundant snake_case
  types and SDK noise, quote the value you'd act on, and give missing
  env vars an actionable hint.
- Add --env-file and --detail flags.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
CI forces colour on, so picocolors wraps the summary line in escape
codes; the exact-match and startsWith assertions failed against
`\x1b[32m3 ok\x1b[39m`. Strip ANSI in the capture helper so assertions
are colour-agnostic in any environment.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
…L, drop --manifest

Act on code-review findings for the doctor command:

- Declare js-yaml + @types/js-yaml in shared's package.json. bundle.ts
  imported js-yaml but only resolved via pnpm hoisting / being bundled
  into appkit — a fragile packaging gap. Declare it like dotenv/picocolors.
- Surface malformed bundle YAML instead of swallowing it. readYaml now
  throws on a present-but-unparseable databricks.yml/app.yaml rather than
  returning null (which set present:false, skipping all wiring checks and
  reporting a false all-clear on deploy-breaking config). Absent files
  still degrade gracefully.
- Remove the --manifest flag. Multiple plugin manifests is a non-scenario
  (appkit.plugins.json is generated at a fixed path), and the flag opened
  two footguns: a mistyped path silently checked nothing, and it could
  desync from the fixed bundle-file paths. Dropping it closes both.
- Document the known limitation that used non-GA plugins aren't checked
  (plugin sync strips requiredByTemplate for non-GA), with a fast-follow
  note, in both the filter comment and the README.
- Comment the deliberate, unrestored process.env mutation in
  getServiceClient (safe in a one-shot CLI).

Adds tests for the malformed-YAML throw.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
…tion

The prior code set process.env.DATABRICKS_CONFIG_PROFILE to apply an
explicit --profile — a hidden, unrestored global side effect. The SDK's
own Config exposes a first-class `profile` field, so pass it straight
into new WorkspaceClient({ profile }) instead. No env mutation, no leak
past the call, and it correctly overrides an ambient
DATABRICKS_CONFIG_PROFILE. Verified against the real SDK.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
Address a security finding: sensitive data could reach captured output.

- Strip URL userinfo (user:pass@) from DATABRICKS_HOST at capture, so
  credentials embedded in the host never reach the report or --json.
  Validation still runs on the raw value.
- Gate auth.raw in --json behind --detail, matching the human report.
  A bare --json omits the raw SDK error (CI commonly captures --json);
  --json --detail opts back in.

Also folds in pending doctor cleanups the tree already carried and that
these changes build on: an errorMessage() helper in a new utils.ts, and
STATUS_SEVERITY / AUTH_UNAVAILABLE_CODE constants shared via types.ts to
stop run.ts and report.ts drifting.

Adds tests for host sanitization and the --json raw gate (92 total).

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
ENV_UNWIRED was always a warning, but exitCodeFor gates only on errors —
so a required plugin's env var with no app.yaml entry (set locally via
.env, unset in the deployed container) exited 0, the exact
works-locally-breaks-on-deploy case the wiring check exists to catch.

Gate on target.required: error for required (fails the exit code, blocks
pre-deploy CI), warn for optional. Tests cover both.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
No probe, currentUser.me(), or Lakebase SELECT 1 had a deadline, so a
reachable-but-unresponsive endpoint would hang doctor forever — and as a
CI gate, the job would never return.

Add withTimeout (Promise.race against a 10s wall-clock deadline) around
each existence probe and the auth me() call. It can't cancel the
underlying request, but it stops doctor *waiting* so the report returns:
a timed-out probe becomes a PROBE_TIMEOUT error row, a timed-out auth
becomes an auth failure. The timer is always cleared, so a fast result
leaves nothing pending and the CLI exits promptly.

Lakebase connection setup was already bounded (connectionTimeoutMillis:
10s); this covers a hung query on an established connection too.

Tests: withTimeout unit tests (fake timers), a hung-probe run test, and
a hung-auth test.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
summary counted only resources; printReport folded auth + wiring in at
render time but never wrote it back, so printReportJson emitted the
unfolded counts. A --json consumer reading summary.error === 0 missed
auth failures and wiring errors (e.g. a required ENV_UNWIRED) — the exact
CI-gate case.

Build summary over everything with a status (resources + auth + wiring)
once in runDoctor, and add a top-level exitCode as the unambiguous
pass/fail signal. printReport now renders the summary verbatim (no
re-folding, so human and --json agree) and exitCodeFor just reads the
field. Moves the aggregation coverage to run.test.ts.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
The probe's own .catch covered a probe rejection, but checkResource had
no top-level guard: an unexpected throw (a config-layer error, a
synchronous throw in probe dispatch, anything else) rejected Promise.all
and lost the entire report to a stack trace.

Wrap each resource in checkResourceSafe, mapping any throw to a
PROBE_EXCEPTION error row so the run always resolves and the rest of the
report survives. Test verifies a synchronous probe throw becomes a row
rather than crashing (confirmed it fails without the guard).

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
The Lakebase pool's default error-only logger dumps the raw SDK ApiError
(stack + full response blob) to stderr on a failed token fetch, burying
doctor's clean one-line classification. Pass logger: { error: false } so
doctor owns the failure output. Reorder createLakebasePool so a
caller-supplied logger overrides the appkit default.

Also trims the Lakebase auth hint and prunes redundant tests.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
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