Skip to content

Commit da5df27

Browse files
committed
feat: step 3 - obtain router token
1 parent 48a6cf6 commit da5df27

File tree

5 files changed

+157
-47
lines changed

5 files changed

+157
-47
lines changed

cli/src/commands/demo/command.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ora from 'ora';
33
import { program } from 'commander';
44
import type { FederatedGraph, Subgraph, WhoAmIResponse } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
55
import { config } from '../../core/config.js';
6+
import { createRouterToken, deleteRouterToken } from '../../core/router-token.js';
67
import { BaseCommandOptions } from '../../core/types/types.js';
78
import { waitForKeyPress, rainbow } from '../../utils.js';
89
import type { UserInfo } from './types.js';
@@ -230,6 +231,42 @@ async function handleStep2(
230231
}
231232
}
232233

234+
async function handleStep3(opts: BaseCommandOptions, { userInfo }: { userInfo: UserInfo }) {
235+
function retryFn() {
236+
resetScreen(userInfo);
237+
return handleStep3(opts, { userInfo });
238+
}
239+
240+
const tokenParams = {
241+
client: opts.client,
242+
tokenName: config.demoRouterTokenName,
243+
graphName: config.demoGraphName,
244+
namespace: config.demoNamespace,
245+
};
246+
247+
// Delete existing token first (idempotent — no error if missing)
248+
const deleteResult = await deleteRouterToken(tokenParams);
249+
if (deleteResult.error) {
250+
console.error(`Failed to clean up existing router token: ${deleteResult.error.message}`);
251+
await waitForKeyPress({ r: retryFn, R: retryFn }, 'Hit [r] to retry. CTRL+C to quit.');
252+
return;
253+
}
254+
255+
const spinner = ora().start('Generating router token…');
256+
const createResult = await createRouterToken(tokenParams);
257+
258+
if (createResult.error) {
259+
spinner.fail(`Failed to generate router token: ${createResult.error.message}`);
260+
await waitForKeyPress({ r: retryFn, R: retryFn }, 'Hit [r] to retry. CTRL+C to quit.');
261+
return;
262+
}
263+
264+
spinner.succeed('Router token generated.');
265+
console.log(`\n${pc.bold(createResult.token)}\n`);
266+
267+
// TODO: Step 3b — run router Docker container
268+
}
269+
233270
async function handleGetOnboardingResponse(client: BaseCommandOptions['client'], userInfo: UserInfo) {
234271
const onboardingCheck = await checkExistingOnboarding(client);
235272

@@ -310,6 +347,8 @@ export default function (opts: BaseCommandOptions) {
310347
}
311348

312349
await handleStep2(opts, { onboarding: onboardingCheck, userInfo, supportDir, signal: controller.signal });
350+
351+
await handleStep3(opts, { userInfo });
313352
} finally {
314353
process.off('SIGINT', cleanup);
315354
process.off('SIGTERM', cleanup);

cli/src/commands/router/commands/token/commands/create.ts

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { Command } from 'commander';
22
import pc from 'picocolors';
3-
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
43
import { BaseCommandOptions } from '../../../../../core/types/types.js';
5-
import { getBaseHeaders } from '../../../../../core/config.js';
4+
import { createRouterToken } from '../../../../../core/router-token.js';
65

76
export default (opts: BaseCommandOptions) => {
87
const command = new Command('create');
@@ -20,39 +19,34 @@ export default (opts: BaseCommandOptions) => {
2019
'Prints the token in raw format. This is useful if you want to pipe the token into another command.',
2120
);
2221
command.action(async (name, options) => {
23-
const resp = await opts.client.platform.createFederatedGraphToken(
24-
{
25-
tokenName: name,
26-
graphName: options.graphName,
27-
namespace: options.namespace,
28-
},
29-
{
30-
headers: getBaseHeaders(),
31-
},
32-
);
22+
const result = await createRouterToken({
23+
client: opts.client,
24+
tokenName: name,
25+
graphName: options.graphName,
26+
namespace: options.namespace,
27+
});
3328

34-
if (resp.response?.code === EnumStatusCode.OK) {
35-
if (options.raw) {
36-
console.log(resp.token);
37-
return;
38-
}
39-
40-
console.log(`${pc.green(`Successfully created token ${pc.bold(name)} for graph ${pc.bold(options.graphName)}`)}`);
41-
console.log('');
42-
console.log(`${pc.bold(resp.token)}\n`);
43-
console.log(pc.yellow('---'));
44-
console.log(pc.yellow(`Please store the token in a secure place. It will not be shown again.`));
45-
console.log(pc.yellow(`You can use the token only to authenticate against the Cosmo Platform from the routers.`));
46-
console.log(pc.yellow('---'));
47-
} else {
29+
if (result.error) {
4830
console.log(`${pc.red('Could not create token for graph')}`);
49-
if (resp.response?.details) {
50-
console.log(pc.red(pc.bold(resp.response?.details)));
31+
if (result.error.message) {
32+
console.log(pc.red(pc.bold(result.error.message)));
5133
}
5234
process.exitCode = 1;
53-
// eslint-disable-next-line no-useless-return
5435
return;
5536
}
37+
38+
if (options.raw) {
39+
console.log(result.token);
40+
return;
41+
}
42+
43+
console.log(`${pc.green(`Successfully created token ${pc.bold(name)} for graph ${pc.bold(options.graphName)}`)}`);
44+
console.log('');
45+
console.log(`${pc.bold(result.token)}\n`);
46+
console.log(pc.yellow('---'));
47+
console.log(pc.yellow(`Please store the token in a secure place. It will not be shown again.`));
48+
console.log(pc.yellow(`You can use the token only to authenticate against the Cosmo Platform from the routers.`));
49+
console.log(pc.yellow('---'));
5650
});
5751

5852
return command;

cli/src/commands/router/commands/token/commands/delete.ts

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { Command } from 'commander';
22
import pc from 'picocolors';
3-
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
43
import inquirer from 'inquirer';
54
import { BaseCommandOptions } from '../../../../../core/types/types.js';
6-
import { getBaseHeaders } from '../../../../../core/config.js';
5+
import { deleteRouterToken } from '../../../../../core/router-token.js';
76

87
export default (opts: BaseCommandOptions) => {
98
const command = new Command('delete');
@@ -27,28 +26,24 @@ export default (opts: BaseCommandOptions) => {
2726
return;
2827
}
2928
}
30-
const resp = await opts.client.platform.deleteRouterToken(
31-
{
32-
tokenName: name,
33-
fedGraphName: options.graphName,
34-
namespace: options.namespace,
35-
},
36-
{
37-
headers: getBaseHeaders(),
38-
},
39-
);
4029

41-
if (resp.response?.code === EnumStatusCode.OK) {
42-
console.log(pc.dim(pc.green(`A router token called '${name}' was deleted.`)));
43-
} else {
30+
const result = await deleteRouterToken({
31+
client: opts.client,
32+
tokenName: name,
33+
graphName: options.graphName,
34+
namespace: options.namespace,
35+
});
36+
37+
if (result.error) {
4438
console.log(`Failed to delete router token ${pc.bold(name)}.`);
45-
if (resp.response?.details) {
46-
console.log(pc.red(pc.bold(resp.response?.details)));
39+
if (result.error.message) {
40+
console.log(pc.red(pc.bold(result.error.message)));
4741
}
4842
process.exitCode = 1;
49-
// eslint-disable-next-line no-useless-return
5043
return;
5144
}
45+
46+
console.log(pc.dim(pc.green(`A router token called '${name}' was deleted.`)));
5247
});
5348

5449
return command;

cli/src/core/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const config = {
4444
dockerBuilderName: 'cosmo-builder' as const,
4545
demoRouterPort: 3002 as const,
4646
demoPluginNames: ['products', 'reviews'] as const,
47+
demoRouterTokenName: 'demo-router-token' as const,
4748
};
4849

4950
export const getBaseHeaders = (): HeadersInit => {

cli/src/core/router-token.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
2+
import { getBaseHeaders } from './config.js';
3+
import type { BaseCommandOptions } from './types/types.js';
4+
5+
export interface CreateRouterTokenParams {
6+
client: BaseCommandOptions['client'];
7+
tokenName: string;
8+
graphName: string;
9+
namespace?: string;
10+
}
11+
12+
export interface CreateRouterTokenResult {
13+
error: Error | null;
14+
token?: string;
15+
}
16+
17+
export interface DeleteRouterTokenParams {
18+
client: BaseCommandOptions['client'];
19+
tokenName: string;
20+
graphName: string;
21+
namespace?: string;
22+
}
23+
24+
export interface DeleteRouterTokenResult {
25+
error: Error | null;
26+
}
27+
28+
/**
29+
* Creates a router token for a federated graph.
30+
* Never calls program.error() — caller decides how to handle errors.
31+
*/
32+
export async function createRouterToken(params: CreateRouterTokenParams): Promise<CreateRouterTokenResult> {
33+
const { client, tokenName, graphName, namespace } = params;
34+
35+
const resp = await client.platform.createFederatedGraphToken(
36+
{
37+
tokenName,
38+
graphName,
39+
namespace,
40+
},
41+
{
42+
headers: getBaseHeaders(),
43+
},
44+
);
45+
46+
if (resp.response?.code === EnumStatusCode.OK) {
47+
return { error: null, token: resp.token };
48+
}
49+
50+
return { error: new Error(resp.response?.details ?? 'Could not create router token') };
51+
}
52+
53+
/**
54+
* Deletes a router token. Idempotent — returns success if token doesn't exist.
55+
* Never calls program.error() — caller decides how to handle errors.
56+
*/
57+
export async function deleteRouterToken(params: DeleteRouterTokenParams): Promise<DeleteRouterTokenResult> {
58+
const { client, tokenName, graphName, namespace } = params;
59+
60+
const resp = await client.platform.deleteRouterToken(
61+
{
62+
tokenName,
63+
fedGraphName: graphName,
64+
namespace,
65+
},
66+
{
67+
headers: getBaseHeaders(),
68+
},
69+
);
70+
71+
if (resp.response?.code === EnumStatusCode.OK) {
72+
return { error: null };
73+
}
74+
75+
// Treat "doesn't exist" as success (idempotent)
76+
if (resp.response?.details?.includes("doesn't exist")) {
77+
return { error: null };
78+
}
79+
80+
return { error: new Error(resp.response?.details ?? 'Could not delete router token') };
81+
}

0 commit comments

Comments
 (0)