@@ -4,6 +4,7 @@ import terminalLink from "terminal-link";
44import { getGlobalActions , getModels } from "../services/app/app.js" ;
55import {
66 CREATE_ACTION_MUTATION ,
7+ CREATE_ENVIRONMENT_MUTATION ,
78 CREATE_MODEL_FIELDS_MUTATION ,
89 CREATE_MODEL_MUTATION ,
910 CREATE_ROUTE_MUTATION ,
@@ -49,9 +50,7 @@ export class AddClientError extends GGTError {
4950export type AddArgs = typeof args ;
5051export type AddArgsResult = ArgsDefinitionResult < AddArgs > ;
5152
52- export const args = {
53- ...SyncJsonArgs ,
54- } ;
53+ export const args = { ...SyncJsonArgs } ;
5554
5655export const usage : Usage = ( ) => {
5756 return sprint `
@@ -80,6 +79,9 @@ export const usage: Usage = () => {
8079 Add a new model 'post' with 2 new 'string' type fields 'title' and 'body':
8180 {cyanBright $ ggt add model post title:string body:string}
8281
82+ Add a new 'boolean' type field 'published' to an existing model
83+ {cyanBright ggt add field post/published:boolean}
84+
8385 Add new action 'publish' to the 'post' model:
8486 {cyanBright ggt add action model/post/publish}
8587
@@ -89,8 +91,8 @@ export const usage: Usage = () => {
8991 Add a new route 'howdy'
9092 {cyanBright ggt add route GET howdy}
9193
92- Add a new 'boolean' type field 'published' to an existing model
93- {cyanBright ggt add field post/published:boolean }
94+ Clone the \`development\` environment into a new \`staging\` environment
95+ {cyanBright ggt add environment staging --environment development }
9496 ` ;
9597} ;
9698
@@ -107,12 +109,8 @@ export const run: Run<AddArgs> = async (ctx, args) => {
107109 if ( ! hashes . inSync ) {
108110 await filesync . merge ( ctx , {
109111 hashes,
110- printEnvironmentChangesOptions : {
111- limit : 5 ,
112- } ,
113- printLocalChangesOptions : {
114- limit : 5 ,
115- } ,
112+ printEnvironmentChangesOptions : { limit : 5 } ,
113+ printLocalChangesOptions : { limit : 5 } ,
116114 quietly : true ,
117115 } ) ;
118116 }
@@ -132,6 +130,10 @@ export const run: Run<AddArgs> = async (ctx, args) => {
132130 case "field" :
133131 await fieldSubCommand ( ctx , { args, filesync } ) ;
134132 break ;
133+ case "environment" :
134+ case "env" :
135+ await envSubCommand ( ctx , { args, filesync } ) ;
136+ break ;
135137 default :
136138 println ( usage ( ctx ) ) ;
137139 return ;
@@ -189,10 +191,7 @@ const modelSubCommand = async (ctx: Context, { args, filesync }: { args: AddArgs
189191 mutation : CREATE_MODEL_MUTATION ,
190192 variables : {
191193 path : modelApiIdentifier ,
192- fields : modelFieldsList . map ( ( fields ) => ( {
193- name : fields . name ,
194- fieldType : fields . fieldType ,
195- } ) ) ,
194+ fields : modelFieldsList . map ( ( fields ) => ( { name : fields . name , fieldType : fields . fieldType } ) ) ,
196195 } ,
197196 } )
198197 ) . createModel ;
@@ -206,11 +205,7 @@ const modelSubCommand = async (ctx: Context, { args, filesync }: { args: AddArgs
206205
207206 println ( { ensureEmptyLineAbove : true , content : chalk . gray ( "New model created in environment." ) } ) ;
208207
209- await filesync . writeToLocalFilesystem ( ctx , {
210- filesVersion : result . remoteFilesVersion ,
211- files : result . changed ,
212- delete : [ ] ,
213- } ) ;
208+ await filesync . writeToLocalFilesystem ( ctx , { filesVersion : result . remoteFilesVersion , files : result . changed , delete : [ ] } ) ;
214209
215210 const modelPrintout = terminalLink . isSupported
216211 ? terminalLink (
@@ -294,11 +289,7 @@ const actionSubCommand = async (ctx: Context, { args, filesync }: { args: AddArg
294289 } )
295290 ) . createAction ;
296291
297- await filesync . writeToLocalFilesystem ( ctx , {
298- filesVersion : result . remoteFilesVersion ,
299- files : result . changed ,
300- delete : [ ] ,
301- } ) ;
292+ await filesync . writeToLocalFilesystem ( ctx , { filesVersion : result . remoteFilesVersion , files : result . changed , delete : [ ] } ) ;
302293 } catch ( error ) {
303294 if ( error instanceof ClientError ) {
304295 throw new AddClientError ( error ) ;
@@ -307,10 +298,7 @@ const actionSubCommand = async (ctx: Context, { args, filesync }: { args: AddArg
307298 }
308299 }
309300
310- println ( {
311- ensureEmptyLineAbove : true ,
312- content : `Action ${ chalk . cyanBright ( path ) } added successfully.` ,
313- } ) ;
301+ println ( { ensureEmptyLineAbove : true , content : `Action ${ chalk . cyanBright ( path ) } added successfully.` } ) ;
314302} ;
315303
316304const routeSubCommand = async ( ctx : Context , { args, filesync } : { args : AddArgsResult ; filesync : FileSync } ) : Promise < void > => {
@@ -333,18 +321,10 @@ const routeSubCommand = async (ctx: Context, { args, filesync }: { args: AddArgs
333321 }
334322
335323 try {
336- const result = (
337- await syncJson . edit . mutate ( {
338- mutation : CREATE_ROUTE_MUTATION ,
339- variables : { method : routeMethod , path : routePath } ,
340- } )
341- ) . createRoute ;
324+ const result = ( await syncJson . edit . mutate ( { mutation : CREATE_ROUTE_MUTATION , variables : { method : routeMethod , path : routePath } } ) )
325+ . createRoute ;
342326
343- await filesync . writeToLocalFilesystem ( ctx , {
344- filesVersion : result . remoteFilesVersion ,
345- files : result . changed ,
346- delete : [ ] ,
347- } ) ;
327+ await filesync . writeToLocalFilesystem ( ctx , { filesVersion : result . remoteFilesVersion , files : result . changed , delete : [ ] } ) ;
348328 } catch ( error ) {
349329 if ( error instanceof ClientError ) {
350330 throw new AddClientError ( error ) ;
@@ -353,10 +333,7 @@ const routeSubCommand = async (ctx: Context, { args, filesync }: { args: AddArgs
353333 }
354334 }
355335
356- println ( {
357- ensureEmptyLineAbove : true ,
358- content : `Route ${ chalk . cyanBright ( routePath ) } added successfully.` ,
359- } ) ;
336+ println ( { ensureEmptyLineAbove : true , content : `Route ${ chalk . cyanBright ( routePath ) } added successfully.` } ) ;
360337} ;
361338
362339const fieldSubCommand = async ( ctx : Context , { args, filesync } : { args : AddArgsResult ; filesync : FileSync } ) : Promise < void > => {
@@ -400,18 +377,31 @@ const fieldSubCommand = async (ctx: Context, { args, filesync }: { args: AddArgs
400377 variables : {
401378 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
402379 path : splitPathAndField [ 0 ] ! ,
403- fields : modelFieldsList . map ( ( field ) => ( {
404- name : field . name ,
405- fieldType : field . fieldType ,
406- } ) ) ,
380+ fields : modelFieldsList . map ( ( field ) => ( { name : field . name , fieldType : field . fieldType } ) ) ,
407381 } ,
408382 } )
409383 ) . createModelFields ;
410384
411- await filesync . writeToLocalFilesystem ( ctx , {
412- filesVersion : result . remoteFilesVersion ,
413- files : result . changed ,
414- delete : [ ] ,
385+ await filesync . writeToLocalFilesystem ( ctx , { filesVersion : result . remoteFilesVersion , files : result . changed , delete : [ ] } ) ;
386+ } catch ( error ) {
387+ if ( error instanceof ClientError ) {
388+ throw new AddClientError ( error ) ;
389+ } else {
390+ throw error ;
391+ }
392+ }
393+
394+ println ( { ensureEmptyLineAbove : true , content : `Field ${ chalk . cyanBright ( modelFieldsList [ 0 ] ?. name ) } added successfully.` } ) ;
395+ } ;
396+
397+ const envSubCommand = async ( ctx : Context , { args, filesync } : { args : AddArgsResult ; filesync : FileSync } ) : Promise < void > => {
398+ const syncJson = filesync . syncJson ;
399+ const newEnvName = args . _ [ 1 ] ?? makeDefaultEnvName ( ) ;
400+
401+ try {
402+ await syncJson . edit . mutate ( {
403+ mutation : CREATE_ENVIRONMENT_MUTATION ,
404+ variables : { environment : { slug : newEnvName , sourceSlug : syncJson . environment . name } } ,
415405 } ) ;
416406 } catch ( error ) {
417407 if ( error instanceof ClientError ) {
@@ -421,8 +411,33 @@ const fieldSubCommand = async (ctx: Context, { args, filesync }: { args: AddArgs
421411 }
422412 }
423413
424- println ( {
425- ensureEmptyLineAbove : true ,
426- content : `Field ${ chalk . cyanBright ( modelFieldsList [ 0 ] ?. name ) } added successfully.` ,
414+ println ( { ensureEmptyLineAbove : true , content : `Environment ${ chalk . cyanBright ( newEnvName ) } added successfully.` } ) ;
415+
416+ // Try to switch to newly made env
417+ const pullFromNewEnvSyncJson = await SyncJson . load ( ctx , {
418+ command : "pull" ,
419+ args : { _ : [ ] , "--app" : undefined , "--allow-unknown-directory" : undefined , "--allow-different-app" : undefined , "--env" : newEnvName } ,
420+ directory : await loadSyncJsonDirectory ( process . cwd ( ) ) ,
427421 } ) ;
422+ if ( pullFromNewEnvSyncJson ) {
423+ const filesync = new FileSync ( syncJson ) ;
424+ const hashes = await filesync . hashes ( ctx ) ;
425+ if ( hashes . environmentChangesToPull . size === 0 ) {
426+ println ( { ensureEmptyLineAbove : true , content : "Nothing to pull." } ) ;
427+ return ;
428+ }
429+ if ( hashes . localChangesToPush . size > 0 ) {
430+ // show them the local changes they will discard
431+ await filesync . print ( ctx , { hashes } ) ;
432+ }
433+ await filesync . pull ( ctx , { hashes, force : true } ) ;
434+ }
435+ } ;
436+
437+ /**
438+ * Creates a default environment name based on the current date and time.
439+ */
440+ const makeDefaultEnvName = ( ) : string => {
441+ const currentDate = new Date ( ) ;
442+ return `env-${ currentDate . toISOString ( ) . slice ( 0 , 10 ) . replace ( / - / g, "" ) } -${ currentDate . toLocaleTimeString ( "en-US" , { hour12 : false } ) . replace ( / : / g, "" ) } ` ;
428443} ;
0 commit comments