Skip to content

Commit

Permalink
Add --debug option for commands that run GraphQL queries
Browse files Browse the repository at this point in the history
  • Loading branch information
lemonmade committed Sep 1, 2024
1 parent 03d600b commit 4b9d7e2
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/seven-onions-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@watching/cli': patch
---

Add `--debug` option for commands that run GraphQL queries
24 changes: 20 additions & 4 deletions packages/cli/source/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,20 @@ async function run() {
switch (command) {
case 'sign-in': {
const {signIn} = await import('./commands/sign-in');
await signIn({ui});
const {'--debug': debug} = arg(
{'--debug': Boolean},
{argv: remainingArgs, permissive: true},
);
await signIn({ui, debug});
break;
}
case 'sign-out': {
const {signOut} = await import('./commands/sign-out');
await signOut({ui});
const {'--debug': debug} = arg(
{'--debug': Boolean},
{argv: remainingArgs, permissive: true},
);
await signOut({ui, debug});
break;
}
case 'create': {
Expand All @@ -74,12 +82,20 @@ async function run() {
}
case 'push': {
const {push} = await import('./commands/push');
await push({ui});
const {'--debug': debug} = arg(
{'--debug': Boolean},
{argv: remainingArgs, permissive: true},
);
await push({ui, debug});
break;
}
case 'publish': {
const {publish} = await import('./commands/publish');
await publish({ui});
const {'--debug': debug} = arg(
{'--debug': Boolean},
{argv: remainingArgs, permissive: true},
);
await publish({ui, debug});
break;
}
default: {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/source/commands/publish/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import type {

import publishLatestClipsExtensionVersion from './graphql/PublishLatestClipsExtensionVersionMutation.graphql';

export async function publish({ui}: {ui: Ui}) {
const {graphql} = await authenticate({ui});
export async function publish({ui, debug}: {ui: Ui; debug?: boolean}) {
const {graphql} = await authenticate({ui, debug});

const localApp = await loadLocalApp();

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/source/commands/push/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type ConfigurationField = NonNullable<
ConfigurationFieldInput[keyof ConfigurationFieldInput]
>;

export async function push({ui}: {ui: Ui}) {
export async function push({ui, debug = false}: {ui: Ui; debug?: boolean}) {
const localApp = await loadLocalApp();

if (localApp.extensions.length === 0) {
Expand All @@ -49,7 +49,7 @@ export async function push({ui}: {ui: Ui}) {

verifyLocalBuild(localApp, ui);

const authenticatedContext = await authenticate({ui});
const authenticatedContext = await authenticate({ui, debug});

const {graphql} = authenticatedContext;

Expand Down
6 changes: 3 additions & 3 deletions packages/cli/source/commands/sign-in/sign-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
userFromLocalAuthentication,
} from '../../utilities/authentication';

export async function signIn({ui}: {ui: Ui}) {
const existingUser = await userFromLocalAuthentication();
export async function signIn({ui, debug}: {ui: Ui; debug?: boolean}) {
const existingUser = await userFromLocalAuthentication({ui, debug});

if (existingUser) {
ui.Heading('success!', {style: (content, style) => style.green(content)});
Expand All @@ -19,7 +19,7 @@ export async function signIn({ui}: {ui: Ui}) {
return;
}

const user = await authenticate({ui});
const user = await authenticate({ui, debug});

ui.Heading('success!', {style: (content, style) => style.green(content)});
ui.TextBlock(
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/source/commands/sign-out/sign-out.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type {Ui} from '../../ui';
import {deleteAuthentication} from '../../utilities/authentication';

export async function signOut({ui}: {ui: Ui}) {
await deleteAuthentication();
export async function signOut({ui, debug}: {ui: Ui; debug?: boolean}) {
await deleteAuthentication({ui, debug});

ui.Heading('success!', {style: (content, style) => style.green(content)});
ui.TextBlock(
Expand Down
106 changes: 93 additions & 13 deletions packages/cli/source/utilities/authentication/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import * as path from 'path';
import {writeFile, mkdir, rm as remove, readFile} from 'fs/promises';
import open from 'open';

import {createGraphQLFetch, type GraphQLFetch} from '@quilted/graphql';
import {
createGraphQLFetch,
type GraphQLFetch,
type GraphQLFetchContext,
} from '@quilted/graphql';

import {PrintableError} from '../../ui';
import type {Ui} from '../../ui';
Expand All @@ -27,10 +31,17 @@ export interface User {
const USER_CACHE_DIRECTORY = path.resolve(homedir(), '.watch');
const CREDENTIALS_FILE = path.resolve(USER_CACHE_DIRECTORY, 'credentials');

export async function authenticate({ui}: {ui: Ui}): Promise<User> {
export async function authenticate({
ui,
debug,
}: {
ui: Ui;
debug?: boolean;
}): Promise<User> {
if (process.env.WATCH_ACCESS_TOKEN) {
const userFromEnvironmentAccessToken = await userFromAccessToken(
process.env.WATCH_ACCESS_TOKEN,
{ui, debug},
);

if (userFromEnvironmentAccessToken == null) {
Expand All @@ -47,20 +58,29 @@ export async function authenticate({ui}: {ui: Ui}): Promise<User> {
}
}

const alreadyAuthenticatedUser = await userFromLocalAuthentication();
const alreadyAuthenticatedUser = await userFromLocalAuthentication({
ui,
debug,
});

if (alreadyAuthenticatedUser) return alreadyAuthenticatedUser;

const user = await authenticateFromWebAuthentication({ui});
const user = await authenticateFromWebAuthentication({ui, debug});

return user;
}

export async function deleteAuthentication() {
export async function deleteAuthentication({
ui,
debug,
}: {
ui: Ui;
debug?: boolean;
}) {
const accessToken = await accessTokenFromCacheDirectory();

if (accessToken) {
const mutate = graphqlFromAccessToken(accessToken);
const mutate = graphqlFromAccessToken(accessToken, {ui, debug});
await mutate(deleteAccessTokenForCliMutation, {
variables: {token: accessToken},
});
Expand All @@ -69,13 +89,21 @@ export async function deleteAuthentication() {
}
}

export async function userFromLocalAuthentication() {
export async function userFromLocalAuthentication({
ui,
debug,
}: {
ui: Ui;
debug?: boolean;
}) {
const accessTokenFromRoot = await accessTokenFromCacheDirectory();

if (accessTokenFromRoot == null) return;

const userFromRootAccessToken =
await userFromAccessToken(accessTokenFromRoot);
const userFromRootAccessToken = await userFromAccessToken(
accessTokenFromRoot,
{ui, debug},
);

if (userFromRootAccessToken == null) {
await remove(USER_CACHE_DIRECTORY, {recursive: true, force: true});
Expand All @@ -97,19 +125,66 @@ async function accessTokenFromCacheDirectory(): Promise<string | undefined> {
}
}

function graphqlFromAccessToken(accessToken: string) {
return createGraphQLFetch({
function graphqlFromAccessToken(
accessToken: string,
{ui, debug = false}: {ui: Ui; debug?: boolean},
) {
const baseFetchGraphQL = createGraphQLFetch({
url: watchUrl('/api/graphql'),
headers: {
'X-Access-Token': accessToken,
},
});

if (!debug) return baseFetchGraphQL;

return async function fetchGraphQL(query, options) {
const context: GraphQLFetchContext = {};

ui.TextBlock(`[debug] Performing GraphQL query: ${(query as any).name}`, {
style: (content, style) => style.dim(content),
});
ui.TextBlock((query as any).source, {
style: (content, style) => style.dim(content),
});
ui.TextBlock(`Variables: ${JSON.stringify(options?.variables ?? {})}`, {
style: (content, style) => style.dim(content),
});

const result = await baseFetchGraphQL(query, options, context);

if (context.request) {
ui.TextBlock(
`[debug] Performed GraphQL request: ${context.request.method.toUpperCase()} ${
context.request.url
}`,
{
style: (content, style) => style.dim(content),
},
);
}

ui.TextBlock(
`[debug] GraphQL response: ${(query as any).name} (status: ${
context.response?.status ?? 'unknown'
})`,
{
style: (content, style) => style.dim(content),
},
);
ui.TextBlock(JSON.stringify(result), {
style: (content, style) => style.dim(content),
});

return result;
} satisfies GraphQLFetch;
}

async function userFromAccessToken(
accessToken: string,
{ui, debug = false}: {ui: Ui; debug?: boolean},
): Promise<User | undefined> {
const graphql = graphqlFromAccessToken(accessToken);
const graphql = graphqlFromAccessToken(accessToken, {ui, debug});

const {data} = await graphql(checkAuthFromCliQuery);

Expand All @@ -119,17 +194,22 @@ async function userFromAccessToken(
}

export async function authenticateFromWebAuthentication({
ui,
to = '/app/developer/cli/authenticate',
debug = false,
...rest
}: Omit<PerformWebAuthenticationOptions, 'to'> & {
ui: Ui;
to?: PerformWebAuthenticationOptions['to'];
debug?: boolean;
}) {
const {token} = await performWebAuthentication<{token: string}>({
ui,
to,
...rest,
});

const user = await userFromAccessToken(token);
const user = await userFromAccessToken(token, {ui, debug});

if (user == null) {
throw new PrintableError(
Expand Down

0 comments on commit 4b9d7e2

Please sign in to comment.