@@ -5,7 +5,7 @@ import pc from 'picocolors';
55import ora from 'ora' ;
66import { program } from 'commander' ;
77import { z } from 'zod' ;
8- import type { WhoAmIResponse } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb' ;
8+ import type { FederatedGraph , Subgraph , WhoAmIResponse } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb' ;
99import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb' ;
1010import { BaseCommandOptions } from '../../core/types/types.js' ;
1111import { getBaseHeaders , config } from '../../core/config.js' ;
@@ -14,6 +14,10 @@ import { waitForKeyPress, rainbow, visibleLength } from '../../utils.js';
1414const ONBOARDING_REPO = 'wundergraph/cosmo-onboarding' ;
1515const ONBOARDING_BRANCH = 'main' ;
1616const ONBOARDING_DIR_PREFIX = 'plugins/' ;
17+ const DEMO_GRAPH_NAME = 'demo' as const ;
18+ const DEMO_NAMESPACES = 'default' as const ;
19+ const DEMO_LABEL_MATCHER = `graph=demo` as const ;
20+ const DEMO_ROUTER_PORT = 3002 as const ;
1721
1822const GitHubTreeSchema = z . object ( {
1923 tree : z . array (
@@ -89,6 +93,298 @@ function printHello() {
8993 console . log ( 'This command will guide you through the inital setup to create your first federated graph.' ) ;
9094}
9195
96+ async function fetchFederatedGraphByName (
97+ client : BaseCommandOptions [ 'client' ] ,
98+ { name, namespace } : { name : string ; namespace : string } ,
99+ ) {
100+ const { response, graph, subgraphs } = await client . platform . getFederatedGraphByName (
101+ {
102+ name,
103+ namespace,
104+ } ,
105+ {
106+ headers : getBaseHeaders ( ) ,
107+ } ,
108+ ) ;
109+
110+ switch ( response ?. code ) {
111+ case EnumStatusCode . OK : {
112+ return { data : { graph, subgraphs } , error : null } ;
113+ }
114+ case EnumStatusCode . ERR_NOT_FOUND : {
115+ return { data : null , error : null } ;
116+ }
117+ default : {
118+ return {
119+ data : null ,
120+ error : new Error ( response ?. details ?? 'An unknown error occured' ) ,
121+ } ;
122+ }
123+ }
124+ }
125+
126+ async function cleanUpFederatedGraph (
127+ client : BaseCommandOptions [ 'client' ] ,
128+ graphData : {
129+ graph : FederatedGraph ;
130+ subgraphs : Subgraph [ ] ;
131+ } ,
132+ ) {
133+ const subgraphDeleteResponses = await Promise . all (
134+ graphData . subgraphs . map ( ( { name, namespace } ) =>
135+ client . platform . deleteFederatedSubgraph (
136+ {
137+ namespace,
138+ subgraphName : name ,
139+ disableResolvabilityValidation : false ,
140+ } ,
141+ {
142+ headers : getBaseHeaders ( ) ,
143+ } ,
144+ ) ,
145+ ) ,
146+ ) ;
147+
148+ const failedSubgraphDeleteResponses = subgraphDeleteResponses . filter (
149+ ( { response } ) => response ?. code !== EnumStatusCode . OK ,
150+ ) ;
151+
152+ if ( failedSubgraphDeleteResponses . length > 0 ) {
153+ return {
154+ error : new Error (
155+ failedSubgraphDeleteResponses . map ( ( { response } ) => response ?. details ?? 'Unknown error occurred.' ) . join ( '. ' ) ,
156+ ) ,
157+ } ;
158+ }
159+
160+ const federatedGraphDeleteResponse = await client . platform . deleteFederatedGraph (
161+ {
162+ name : graphData . graph . name ,
163+ namespace : graphData . graph . namespace ,
164+ } ,
165+ {
166+ headers : getBaseHeaders ( ) ,
167+ } ,
168+ ) ;
169+
170+ switch ( federatedGraphDeleteResponse . response ?. code ) {
171+ case EnumStatusCode . OK : {
172+ return {
173+ error : null ,
174+ } ;
175+ }
176+ default : {
177+ return {
178+ error : new Error ( federatedGraphDeleteResponse . response ?. details ?? 'Unknown error occurred.' ) ,
179+ } ;
180+ }
181+ }
182+ }
183+
184+ async function createFederatedGraph (
185+ client : BaseCommandOptions [ 'client' ] ,
186+ options : {
187+ name : string ;
188+ namespace : string ;
189+ labelMatcher : string ;
190+ routingUrl : URL ;
191+ } ,
192+ ) {
193+ const createFedGraphResponse = await client . platform . createFederatedGraph (
194+ {
195+ name : options . name ,
196+ namespace : options . namespace ,
197+ routingUrl : options . routingUrl . toString ( ) ,
198+ labelMatchers : [ options . labelMatcher ] ,
199+ } ,
200+ {
201+ headers : getBaseHeaders ( ) ,
202+ } ,
203+ ) ;
204+
205+ switch ( createFedGraphResponse . response ?. code ) {
206+ case EnumStatusCode . OK : {
207+ return { error : null } ;
208+ }
209+ default : {
210+ return {
211+ error : new Error ( createFedGraphResponse . response ?. details ?? 'An unknown error occured' ) ,
212+ } ;
213+ }
214+ }
215+ }
216+
217+ async function handleGetFederatedGraphResponse (
218+ client : BaseCommandOptions [ 'client' ] ,
219+ {
220+ onboarding,
221+ userInfo,
222+ } : {
223+ onboarding : {
224+ finishedAt ?: string ;
225+ } ;
226+ userInfo : UserInfo ;
227+ } ,
228+ ) {
229+ function retryFn ( ) {
230+ resetScreen ( userInfo ) ;
231+ return handleGetFederatedGraphResponse ( client , {
232+ onboarding,
233+ userInfo,
234+ } ) ;
235+ }
236+
237+ const spinner = ora ( ) . start ( ) ;
238+ const getFederatedGraphResponse = await fetchFederatedGraphByName ( client , {
239+ name : DEMO_GRAPH_NAME ,
240+ namespace : DEMO_NAMESPACES ,
241+ } ) ;
242+
243+ if ( getFederatedGraphResponse . error ) {
244+ spinner . fail ( `Failed to retrieve graph information ${ getFederatedGraphResponse . error } ` ) ;
245+ await waitForKeyPress (
246+ {
247+ r : retryFn ,
248+ R : retryFn ,
249+ } ,
250+ 'Hit [r] to refresh. CTRL+C to quit' ,
251+ ) ;
252+ return ;
253+ }
254+
255+ if ( getFederatedGraphResponse . data ?. graph ) {
256+ spinner . succeed ( `Federated graph ${ pc . bold ( getFederatedGraphResponse . data ?. graph ?. name ) } exists.` ) ;
257+ } else {
258+ spinner . stop ( ) ;
259+ }
260+
261+ return getFederatedGraphResponse . data ;
262+ }
263+
264+ async function cleanupFederatedGraph (
265+ client : BaseCommandOptions [ 'client' ] ,
266+ {
267+ graphData,
268+ userInfo,
269+ } : {
270+ graphData : {
271+ graph : FederatedGraph ;
272+ subgraphs : Subgraph [ ] ;
273+ } ;
274+ userInfo : UserInfo ;
275+ } ,
276+ ) {
277+ function retryFn ( ) {
278+ resetScreen ( userInfo ) ;
279+ cleanupFederatedGraph ( client , { graphData, userInfo } ) ;
280+ }
281+
282+ const spinner = ora ( ) . start ( `Removing federated graph ${ pc . bold ( graphData . graph . name ) } …` ) ;
283+ const deleteResponse = await cleanUpFederatedGraph ( client , graphData ) ;
284+
285+ if ( deleteResponse . error ) {
286+ spinner . fail ( `Removing federated graph ${ graphData . graph . name } failed.` ) ;
287+ console . error ( deleteResponse . error . message ) ;
288+
289+ await waitForKeyPress (
290+ {
291+ Enter : ( ) => undefined ,
292+ r : retryFn ,
293+ R : retryFn ,
294+ } ,
295+ `Failed to delete the federated graph ${ pc . bold ( graphData . graph . name ) } . [ENTER] to continue, [r] to retry. CTRL+C to quit.` ,
296+ ) ;
297+ }
298+
299+ spinner . succeed ( `Federated graph ${ pc . bold ( graphData . graph . name ) } removed.` ) ;
300+ }
301+
302+ async function handleCreateFederatedGraphResponse (
303+ client : BaseCommandOptions [ 'client' ] ,
304+ {
305+ onboarding,
306+ userInfo,
307+ } : {
308+ onboarding : {
309+ finishedAt ?: string ;
310+ } ;
311+ userInfo : UserInfo ;
312+ } ,
313+ ) {
314+ function retryFn ( ) {
315+ resetScreen ( userInfo ) ;
316+ handleCreateFederatedGraphResponse ( client , { onboarding, userInfo } ) ;
317+ }
318+
319+ const routingUrl = new URL ( 'http://localhost' ) ;
320+ routingUrl . port = String ( DEMO_ROUTER_PORT ) ;
321+
322+ const federatedGraphSpinner = ora ( ) . start ( ) ;
323+ const createGraphResponse = await createFederatedGraph ( client , {
324+ name : DEMO_GRAPH_NAME ,
325+ namespace : DEMO_NAMESPACES ,
326+ labelMatcher : DEMO_LABEL_MATCHER ,
327+ routingUrl,
328+ } ) ;
329+
330+ if ( createGraphResponse . error ) {
331+ federatedGraphSpinner . fail ( createGraphResponse . error . message ) ;
332+
333+ await waitForKeyPress (
334+ {
335+ r : retryFn ,
336+ R : retryFn ,
337+ } ,
338+ 'Hit [r] to refresh. CTRL+C to quit' ,
339+ ) ;
340+ return ;
341+ }
342+
343+ federatedGraphSpinner . succeed ( `Federated graph ${ pc . bold ( 'demo' ) } succesfully created.` ) ;
344+ }
345+
346+ async function handleStep2 (
347+ opts : BaseCommandOptions ,
348+ {
349+ onboarding,
350+ userInfo,
351+ } : {
352+ onboarding : {
353+ finishedAt ?: string ;
354+ } ;
355+ userInfo : UserInfo ;
356+ } ,
357+ ) {
358+ const graphData = await handleGetFederatedGraphResponse ( opts . client , {
359+ onboarding,
360+ userInfo,
361+ } ) ;
362+
363+ const graph = graphData ?. graph ;
364+ const subgraphs = graphData ?. subgraphs ?? [ ] ;
365+ if ( graph ) {
366+ const cleanupFn = async ( ) =>
367+ await cleanupFederatedGraph ( opts . client , {
368+ graphData : { graph, subgraphs } ,
369+ userInfo,
370+ } ) ;
371+ await waitForKeyPress (
372+ {
373+ Enter : ( ) => undefined ,
374+ d : cleanupFn ,
375+ D : cleanupFn ,
376+ } ,
377+ 'Hit [ENTER] to continue or [d] to delete the federated graph and its subgraphs to start over. CTRL+C to quit.' ,
378+ ) ;
379+ return ;
380+ }
381+
382+ await handleCreateFederatedGraphResponse ( opts . client , {
383+ onboarding,
384+ userInfo,
385+ } ) ;
386+ }
387+
92388async function checkExistingOnboarding ( client : BaseCommandOptions [ 'client' ] ) {
93389 const { response, finishedAt, enabled } = await client . platform . getOnboarding (
94390 { } ,
@@ -148,7 +444,7 @@ async function handleGetOnboardingResponse(client: BaseCommandOptions['client'],
148444}
149445
150446async function handleStep1 ( opts : BaseCommandOptions , userInfo : UserInfo ) {
151- await handleGetOnboardingResponse ( opts . client , userInfo ) ;
447+ return await handleGetOnboardingResponse ( opts . client , userInfo ) ;
152448}
153449
154450async function fetchUserInfo ( client : BaseCommandOptions [ 'client' ] ) {
@@ -267,6 +563,12 @@ export default function (opts: BaseCommandOptions) {
267563 const userInfo = await getUserInfo ( opts . client ) ;
268564 resetScreen ( userInfo ) ;
269565
270- await handleStep1 ( opts , userInfo ) ;
566+ const onboardingCheck = await handleStep1 ( opts , userInfo ) ;
567+
568+ if ( ! onboardingCheck ) {
569+ return ;
570+ }
571+
572+ await handleStep2 ( opts , { onboarding : onboardingCheck , userInfo } ) ;
271573 } ;
272574}
0 commit comments