Add one-click install/sign-in to sentry-triage setup gate (#2831)

The sentry-triage canvas's setup gate previously told users to ask
Copilot to install its optional `sentry` dependency and reload
extensions, and to run `sentry auth login` from a terminal. Both
flows were unreliable or high-friction in practice.

This adds real one-click buttons for both steps:

- sentryClient.mjs — installPackage() runs npm install (async, via
  execFile so the shared extension process's event loop isn't
  blocked) rooted at the file's own directory via import.meta.url, so
  the path is never guessed. loadFactory() falls back to importing
  the package's resolved entry file by absolute path when the bare
  import('sentry') fails, since Node caches a negative resolution for
  a bare specifier for the life of the process — while still
  distinguishing a genuinely missing package from a transitive-
  dependency defect so the latter isn't masked as "package missing."
  login() fails fast with a clear message when SENTRY_AUTH_TOKEN/
  SENTRY_TOKEN is active in the environment (which takes precedence
  over the OAuth login this button drives), and treats an empty/
  falsy auth.login() result as a failed sign-in instead of silently
  re-probing.
- preflight.mjs — installDependencies() runs the install and
  re-probes the connection; authenticate() classifies the new error
  codes for gate messaging.
- server.mjs / extension.mjs — wire POST /api/install-dependencies
  and POST /api/auth-login routes (through the existing CSRF/Host
  gate) to the new preflight functions, publishing refreshed
  connection state to the canvas.
- components/page.mjs / styles.mjs — the install and sign-in
  buttons, with loading/success/failure states and accessible live-
  region status updates; a live multi-org <select> dropdown that
  rebuilds in place once org discovery completes post-signin,
  without requiring a canvas reopen.
- README.md — documents the one-click flow; manual npm install /
  sentry auth login kept as a fallback.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Liz Tom
2026-08-28 09:57:28 +10:00
committed by GitHub
co-authored by Copilot App
parent 634b92f887
commit bbde357dbd
7 changed files with 554 additions and 62 deletions
+44 -3
View File
@@ -15,7 +15,7 @@
// buildConnections() is pure so it can be unit-tested offline; checkConnections()
// is the thin wrapper that talks to Sentry.
import { whoami, SentryError } from './sentryClient.mjs'
import { whoami, installPackage, login, SentryError } from './sentryClient.mjs'
// Shape the raw signals into the connection status the UI consumes. Pure.
export function buildConnections({
@@ -60,6 +60,12 @@ const msg = (err) => (err instanceof Error ? err.message : String(err))
// network blip and not an auth failure, so it gets its own gate branch.
const isPackageMissing = (err) => Boolean(err) && err.code === 'SENTRY_PACKAGE_MISSING'
// An active SENTRY_AUTH_TOKEN/SENTRY_TOKEN in the environment takes precedence
// over any OAuth login, so the "Sign in" button can't do anything useful while
// one is set — sentryClient's login() detects this up front and fails fast
// with this code instead of running an OAuth flow that can't take effect.
const isEnvTokenActive = (err) => Boolean(err) && err.code === 'SENTRY_ENV_TOKEN_ACTIVE'
// Text used for classification: the message plus any CLI stderr, since a rejected
// credential (HTTP 401/403) often surfaces its status in stderr rather than the
// Error message.
@@ -95,11 +101,14 @@ const humanizeSentryError = (err) => {
if (isPackageMissing(err)) {
return 'The Sentry CLI isnt installed for this canvas yet. Ask Copilot to “install the sentry-triage dependencies and reload extensions,” then run `npx sentry auth login` from the extension folder and re-open this canvas.'
}
if (isEnvTokenActive(err)) {
return t
}
if (isNotAuthenticated(err)) {
return 'Sentry isnt connected yet. Run `npx sentry auth login` from the extension folder, then re-open this canvas.'
return 'Sentry isnt connected yet.'
}
if (isAuthFailure(err)) {
return 'Sentry rejected your credential (expired or invalid). Run `npx sentry auth login` from the extension folder, then re-open this canvas.'
return 'Sentry rejected your credential (expired or invalid). Sign in again below.'
}
if (isTransient(err)) {
return 'Couldnt reach Sentry just now (network). It should recover on the next check.'
@@ -174,3 +183,35 @@ export async function checkConnections() {
}
return shape(result)
}
// One-click fix for the package-missing gate: run `npm install` in the
// extension's own directory (via sentryClient's installPackage, so the path is
// never guessed by an agent or user) and immediately re-probe. Returns the fresh
// connection state either way so the gate/setup UI can render the outcome —
// success clears the gate, and a failed install surfaces as a normal probe error
// (e.g. still package-missing, or an npm/network failure) rather than throwing.
export async function installDependencies() {
try {
await installPackage()
} catch (err) {
console.error('[sentry-triage] npm install failed:', err instanceof Error ? err.message : err)
}
const { connections } = await checkConnectionsOnce()
return connections
}
// One-click fix for the "not authenticated" setup gate: run the SDK's own
// OAuth device-code login (sentryClient's login(), the in-process equivalent
// of `sentry auth login`) and immediately re-probe. Only ever called for a
// package-present, not-signed-in state — the gate never shows this button
// while the package itself is missing (see components/page.mjs) — so unlike
// installDependencies() a thrown login error (user closed the browser tab,
// denied consent, or the device code expired) is left to propagate: the
// caller (extension.mjs onAuthenticate) surfaces it to the gate rather than
// silently falling back to a generic "still signed out" re-probe, since the
// specific reason (denied vs. expired vs. cancelled) is worth showing.
export async function authenticate() {
await login()
const { connections } = await checkConnectionsOnce()
return connections
}