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 -1
View File
@@ -77,7 +77,7 @@ function hasValidToken(req, token) {
return value === token
}
export function startServer({ port = 0, onRefresh, onAction, onWorkSelected, onRecheck, onListProjects, onResolveProject, onInvalidateEnrichment, defaults } = {}) {
export function startServer({ port = 0, onRefresh, onAction, onWorkSelected, onRecheck, onInstallDependencies, onAuthenticate, onListProjects, onResolveProject, onInvalidateEnrichment, defaults } = {}) {
// Per-instance state + SSE clients — never shared across canvas instances.
const state = createState()
state.applyRepoDefaults(defaults)
@@ -225,6 +225,49 @@ export function startServer({ port = 0, onRefresh, onAction, onWorkSelected, onR
return
}
// One-click install for the "package-missing" setup gate. Runs `npm install`
// in the extension's own directory (via onInstallDependencies) and returns
// the freshly re-probed connection state, so the gate updates without
// requiring the canvas to be reopened.
if (req.method === 'POST' && req.url === '/api/install-dependencies') {
Promise.resolve(onInstallDependencies ? onInstallDependencies() : null)
.then((connections) => {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ ok: true, connections: connections || state.getConnections() }))
})
.catch((err) => {
console.error('[sentry-triage] install-dependencies failed:', err instanceof Error ? err.message : err)
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ ok: true, connections: state.getConnections() }))
})
return
}
// One-click sign-in for the "not authenticated" setup gate (only shown once
// the package is installed — see components/page.mjs). Runs the SDK's own
// OAuth device-code login, which opens the user's browser directly, and
// returns the freshly re-probed connection state. Unlike install, a failed
// login (denied consent, expired code, closed tab) is reported back as
// `ok:false` with a message instead of silently falling back to the stale
// connection state — the specific reason is worth surfacing in the gate.
if (req.method === 'POST' && req.url === '/api/auth-login') {
Promise.resolve(onAuthenticate ? onAuthenticate() : null)
.then((connections) => {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ ok: true, connections: connections || state.getConnections() }))
})
.catch((err) => {
console.error('[sentry-triage] auth-login failed:', err instanceof Error ? err.message : err)
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({
ok: false,
error: err instanceof Error ? err.message : String(err),
connections: state.getConnections(),
}))
})
return
}
// Fetch the Sentry project list for an org so the project field can render
// as a dropdown. Fire-and-forget from the client's perspective: the fetched
// list is broadcast to all clients over SSE by the handler. Used when the