Skip to content

Commit 5673e25

Browse files
authored
Merge branch 'main' into sandbox-json-noninteractive
2 parents 428bf0e + 60a0c2f commit 5673e25

3 files changed

Lines changed: 59 additions & 13 deletions

File tree

auth.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export function createTrpcClient(
113113
retryLink({
114114
retry(opts) {
115115
if (context.debug) {
116-
console.log(opts);
116+
console.error(opts);
117117
}
118118

119119
if (
@@ -243,7 +243,8 @@ export async function getAuth(
243243
message: "",
244244
color: "yellow",
245245
});
246-
console.log(`Visit ${authUrl} to authorize deploying your project.`);
246+
// Prompt-style message — goes to stderr so it doesn't pollute `--json` callers' stdout.
247+
console.error(`Visit ${authUrl} to authorize deploying your project.`);
247248
if (!quiet) {
248249
spinner.start();
249250
}
@@ -310,7 +311,8 @@ export function tokenExchange(
310311
const { token, user } = await res.json();
311312
spinner.stop();
312313
if (!quiet) {
313-
console.log(
314+
// Status message — stderr so JSON callers' stdout stays clean.
315+
console.error(
314316
`${
315317
green("✔")
316318
} Authorization successful. Authenticated as ${user.name}\n`,
@@ -368,7 +370,8 @@ export async function authedFetch(
368370
});
369371

370372
if (res.status === 401) {
371-
console.log(await res.text());
373+
// Diagnostic body — stderr only, and only when debugging.
374+
if (context.debug) console.error(await res.text());
372375
tokenStorage.remove();
373376
auth = await getAuth(context);
374377

deploy/mod.ts

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,17 @@ interface WhoamiOrg {
246246
plan: string | null;
247247
}
248248

249+
interface AccountMe {
250+
user: {
251+
id: string;
252+
name: string | null;
253+
email: string | null;
254+
avatarUrl: string | null;
255+
githubLogin: string | null;
256+
} | null;
257+
tokenType: string;
258+
}
259+
249260
const whoamiCommand = new Command<GlobalContext>()
250261
.description(
251262
"Verify the current Deno Deploy token and list reachable organizations",
@@ -260,16 +271,16 @@ const whoamiCommand = new Command<GlobalContext>()
260271
// AUTH_INVALID_TOKEN envelope from the errorLink if the token is bad,
261272
// without ever calling `requireInteractive()` or opening a browser.
262273
const trpcClient = createTrpcClient(options);
263-
const orgs = await trpcClient.query("orgs.list") as WhoamiOrg[];
274+
const [me, orgs] = await Promise.all([
275+
trpcClient.query("account.me") as Promise<AccountMe>,
276+
trpcClient.query("orgs.list") as Promise<WhoamiOrg[]>,
277+
]);
264278

265279
if (options.json) {
266280
writeJsonResult({
267281
authenticated: true,
268-
// The deployng tRPC router does not currently expose user identity,
269-
// so we surface what we can (orgs the token can reach). When that
270-
// procedure lands, this output will gain a `user` field; existing
271-
// consumers reading `authenticated` / `orgs[]` keep working.
272-
user: null,
282+
user: me.user,
283+
tokenType: me.tokenType,
273284
orgs: orgs.map((org) => ({
274285
id: org.id,
275286
slug: org.slug,
@@ -280,8 +291,15 @@ const whoamiCommand = new Command<GlobalContext>()
280291
return;
281292
}
282293

294+
const who = me.user
295+
? (me.user.githubLogin
296+
? `@${me.user.githubLogin}`
297+
: me.user.email ?? me.user.name ?? me.user.id)
298+
: `org-scoped token (${me.tokenType})`;
283299
console.log(
284-
`${green("✔")} Authenticated. ${orgs.length} reachable organization${
300+
`${
301+
green("✔")
302+
} Authenticated as ${who}. ${orgs.length} reachable organization${
285303
orgs.length === 1 ? "" : "s"
286304
}:`,
287305
);
@@ -300,7 +318,27 @@ export const deployCommand = new Command()
300318
.description(`Interact with Deno Deploy
301319
302320
Calling this subcommand without any further subcommands will
303-
deploy your local directory to the specified application.`)
321+
deploy your local directory to the specified application.
322+
323+
For non-interactive use (CI, AI agents), authenticate via the
324+
DENO_DEPLOY_TOKEN env var (or --token) and pass --json --non-interactive
325+
to every subcommand. The CLI then emits a single JSON object on stdout,
326+
a structured { error: { code, message, hint } } envelope on stderr,
327+
and a stable exit code (0 OK, 1 GENERIC, 2 USAGE, 3 AUTH, 4 NOT_FOUND,
328+
5 CONFLICT, 6 NETWORK). See https://docs.deno.com/runtime/reference/cli/deploy/#agent--ci-usage
329+
for the full reference.`)
330+
.example(
331+
"Verify the active token",
332+
"whoami --json",
333+
)
334+
.example(
335+
"Deploy current directory non-interactively",
336+
"--json --non-interactive --org my-org --app my-app --prod",
337+
)
338+
.example(
339+
"Create a static app from CI",
340+
"create --json --non-interactive --org my-org --app my-app --source local --runtime-mode static --static-dir dist --region us",
341+
)
304342
.globalOption("--endpoint <endpoint:string>", "the endpoint", {
305343
default: "https://console.deno.com",
306344
hidden: true,

sandbox/mod.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,12 @@ Example:
627627
export const sandboxCommand = new Command<GlobalContext>()
628628
.name("deno sandbox")
629629
.version(VERSION)
630-
.description("Interact with sandboxes")
630+
.description(`Interact with sandboxes
631+
632+
For headless / CI use, authenticate via the DENO_DEPLOY_TOKEN env var
633+
(or --token) — no browser flow is opened when a token is supplied.
634+
See https://docs.deno.com/runtime/reference/cli/sandbox/ for the
635+
full reference.`)
631636
.globalOption("--endpoint <endpoint:string>", "the endpoint", {
632637
default: "https://console.deno.com",
633638
hidden: true,

0 commit comments

Comments
 (0)