-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(kadena-cli): add kadena-cli devnet commands
- Loading branch information
1 parent
d12d320
commit 461be98
Showing
19 changed files
with
956 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@kadena/kadena-cli': patch | ||
--- | ||
|
||
add devnet commands to kadena cli |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import type { TDevnetsCreateOptions } from '../devnet/devnetsCreateQuestions.js'; | ||
|
||
export interface IDefaultDevnetOptions { | ||
[key: string]: TDevnetsCreateOptions; | ||
} | ||
|
||
/** | ||
* @const devnetDefaults | ||
* Provides the default devnet configurations. | ||
*/ | ||
export const devnetDefaults: IDefaultDevnetOptions = { | ||
devnet: { | ||
name: 'devnet', | ||
port: 8080, | ||
useVolume: false, | ||
mountPactFolder: '', | ||
version: 'latest', | ||
}, | ||
other: { | ||
name: '', | ||
port: 8080, | ||
useVolume: false, | ||
mountPactFolder: '', | ||
version: '', | ||
}, | ||
}; | ||
|
||
export const defaultDevnetsPath: string = `${process.cwd()}/.kadena/devnets`; | ||
export const standardDevnets: string[] = ['devnet']; | ||
export const defaultDevnet: string = 'devnet'; |
107 changes: 107 additions & 0 deletions
107
packages/tools/kadena-cli/src/devnet/createDevnetsCommand.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { defaultDevnetsPath } from '../constants/devnets.js'; | ||
import { ensureFileExists } from '../utils/filesystem.js'; | ||
import { clearCLI, collectResponses } from '../utils/helpers.js'; | ||
import { processZodErrors } from '../utils/processZodErrors.js'; | ||
|
||
import type { TDevnetsCreateOptions } from './devnetsCreateQuestions.js'; | ||
import { | ||
DevnetsCreateOptions, | ||
devnetsCreateQuestions, | ||
} from './devnetsCreateQuestions.js'; | ||
import { displayDevnetConfig, writeDevnets } from './devnetsHelpers.js'; | ||
|
||
import { select } from '@inquirer/prompts'; | ||
import chalk from 'chalk'; | ||
import { Option, type Command } from 'commander'; | ||
import debug from 'debug'; | ||
import path from 'path'; | ||
|
||
async function shouldProceedWithDevnetCreate(devnet: string): Promise<boolean> { | ||
const filePath = path.join(defaultDevnetsPath, `${devnet}.yaml`); | ||
if (ensureFileExists(filePath)) { | ||
const overwrite = await select({ | ||
message: `Your devnet (config) already exists. Do you want to update it?`, | ||
choices: [ | ||
{ value: 'yes', name: 'Yes' }, | ||
{ value: 'no', name: 'No' }, | ||
], | ||
}); | ||
return overwrite === 'yes'; | ||
} | ||
return true; | ||
} | ||
|
||
export async function runDevnetsCreate( | ||
program: Command, | ||
version: string, | ||
args: TDevnetsCreateOptions, | ||
): Promise<void> { | ||
try { | ||
const responses = await collectResponses(args, devnetsCreateQuestions); | ||
|
||
const devnetConfig = { ...args, ...responses }; | ||
|
||
DevnetsCreateOptions.parse(devnetConfig); | ||
|
||
writeDevnets(devnetConfig); | ||
|
||
displayDevnetConfig(devnetConfig); | ||
|
||
const proceed = await select({ | ||
message: 'Is the above devnet configuration correct?', | ||
choices: [ | ||
{ value: 'yes', name: 'Yes' }, | ||
{ value: 'no', name: 'No' }, | ||
], | ||
}); | ||
|
||
if (proceed === 'no') { | ||
clearCLI(true); | ||
console.log(chalk.yellow("Let's restart the configuration process.")); | ||
await runDevnetsCreate(program, version, args); | ||
} else { | ||
console.log(chalk.green('Configuration complete. Goodbye!')); | ||
} | ||
} catch (e) { | ||
console.error(e); | ||
processZodErrors(program, e, args); | ||
} | ||
} | ||
|
||
export function createDevnetsCommand(program: Command, version: string): void { | ||
program | ||
.command('create') | ||
.description('Create new devnet') | ||
.option('-n, --name <name>', 'Container name (e.g. "devnet")') | ||
.addOption( | ||
new Option( | ||
'-p, --port <port>', | ||
'Port to forward to the Chainweb node API (e.g. 8080)', | ||
).argParser((value) => parseInt(value, 10)), | ||
) | ||
.option( | ||
'-u, --useVolume', | ||
'Create a persistent volume to mount to the container', | ||
) | ||
.option( | ||
'-m, --mountPactFolder <mountPactFolder>', | ||
'Mount a folder containing Pact files to the container (e.g. "./pact")', | ||
) | ||
.option( | ||
'-v, --version <version>', | ||
'Version of the kadena/devnet Docker image to use (e.g. "latest")', | ||
) | ||
.action(async (args: TDevnetsCreateOptions) => { | ||
debug('devnet-create:action')({ args }); | ||
|
||
if ( | ||
args.name && | ||
!(await shouldProceedWithDevnetCreate(args.name.toLowerCase())) | ||
) { | ||
console.log(chalk.red('Devnet creation aborted.')); | ||
return; | ||
} | ||
|
||
await runDevnetsCreate(program, version, args); | ||
}); | ||
} |
127 changes: 127 additions & 0 deletions
127
packages/tools/kadena-cli/src/devnet/devnetsCreateQuestions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import type { IQuestion } from '../utils/helpers.js'; | ||
import { | ||
capitalizeFirstLetter, | ||
getExistingDevnets, | ||
isAlphabetic, | ||
} from '../utils/helpers.js'; | ||
|
||
import { input, select } from '@inquirer/prompts'; | ||
import { z } from 'zod'; | ||
|
||
// eslint-disable-next-line @rushstack/typedef-var | ||
export const DevnetsCreateOptions = z.object({ | ||
name: z.string(), | ||
port: z.number().optional(), | ||
useVolume: z.boolean().optional(), | ||
mountPactFolder: z.string().optional(), | ||
version: z.string().optional(), | ||
}); | ||
|
||
export type TDevnetsCreateOptions = z.infer<typeof DevnetsCreateOptions>; | ||
|
||
interface IDevnetManageQuestionsQuestions | ||
extends Pick<IQuestion<TDevnetsCreateOptions>, 'key' | 'prompt'> {} | ||
|
||
interface ICustomChoice { | ||
value: string; | ||
name?: string; | ||
description?: string; | ||
disabled?: boolean | string; | ||
} | ||
|
||
export async function askForDevnet(): Promise<string> { | ||
const existingDevnets: ICustomChoice[] = getExistingDevnets(); | ||
|
||
const allDevnetChoices: ICustomChoice[] = [...existingDevnets] | ||
.filter((v, i, a) => a.findIndex((v2) => v2.name === v.name) === i) | ||
.map((devnet) => { | ||
return { | ||
value: devnet.value, | ||
name: capitalizeFirstLetter(devnet.value), | ||
}; | ||
}); | ||
|
||
const devnetChoice = await select({ | ||
message: | ||
'Select an (default) existing devnet configuration or create a new one:', | ||
choices: [ | ||
...allDevnetChoices, | ||
{ value: 'CREATE_NEW', name: 'Create a New Devnet' } as ICustomChoice, | ||
], | ||
}); | ||
|
||
if (devnetChoice === 'CREATE_NEW') { | ||
const newDevnetName = await input({ | ||
validate: function (input) { | ||
if (input === '') { | ||
return 'Devnet name cannot be empty! Please enter something.'; | ||
} | ||
if (!isAlphabetic(input)) { | ||
return 'Devnet name must be alphabetic! Please enter a valid name.'; | ||
} | ||
return true; | ||
}, | ||
message: 'Enter the name for your new devnet container:', | ||
}); | ||
return newDevnetName.toLowerCase(); | ||
} | ||
|
||
return devnetChoice.toLowerCase(); | ||
} | ||
|
||
export const devnetsCreateQuestions: IQuestion<TDevnetsCreateOptions>[] = [ | ||
{ | ||
key: 'name', | ||
prompt: async () => await askForDevnet(), | ||
}, | ||
{ | ||
key: 'port', | ||
prompt: async () => { | ||
const port = await input({ | ||
default: '8080', | ||
message: 'Enter a port number to forward to the Chainweb node API', | ||
validate: function (input) { | ||
const port = parseInt(input); | ||
if (isNaN(port)) { | ||
return 'Port must be a number! Please enter a valid port number.'; | ||
} | ||
return true; | ||
}, | ||
}); | ||
return parseInt(port); | ||
}, | ||
}, | ||
{ | ||
key: 'useVolume', | ||
prompt: async () => | ||
await select({ | ||
message: 'Would you like to create a persistent volume?', | ||
choices: [ | ||
{ value: false, name: 'No' }, | ||
{ value: true, name: 'Yes' }, | ||
], | ||
}), | ||
}, | ||
{ | ||
key: 'mountPactFolder', | ||
prompt: async () => | ||
await input({ | ||
default: '', | ||
message: | ||
'Enter the relative path to a folder containing your Pact files to mount (e.g. ./pact) or leave empty to skip.', | ||
}), | ||
}, | ||
{ | ||
key: 'version', | ||
prompt: async () => | ||
await input({ | ||
default: 'latest', | ||
message: | ||
'Enter the version of the kadena/devnet image you would like to use.', | ||
}), | ||
}, | ||
]; | ||
|
||
export const devnetManageQuestions: IDevnetManageQuestionsQuestions[] = [ | ||
...devnetsCreateQuestions.filter((question) => question.key !== 'name'), | ||
]; |
Oops, something went wrong.