Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configFile support in cli #131

Merged
merged 17 commits into from
Oct 4, 2024
Merged
4 changes: 3 additions & 1 deletion cli/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ coverage/
*.wasm

# wallets
wallet*.yaml
wallet*.yaml

deweb_cli_config.json
39 changes: 39 additions & 0 deletions cli/src/commands/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { PublicApiUrl } from '@massalabs/massa-web3'
import { OptionValues } from 'commander'
import { readFileSync } from 'fs'

export const DEFAULT_CHUNK_SIZE = 64000
const DEFAULT_NODE_URL = PublicApiUrl.Buildnet

interface Config {
wallet_password: string
wallet_path: string
node_url: string
chunk_size: number
secret_key: string
}

export function parseConfigFile(filePath: string): Config {
const fileContent = readFileSync(filePath, 'utf-8')
try {
return JSON.parse(fileContent)
} catch (error) {
throw new Error(`Failed to parse file: ${error}`)
}
}
thomas-senechal marked this conversation as resolved.
Show resolved Hide resolved

export function mergeConfigAndOptions(
commandOptions: OptionValues,
configOptions: Config
): OptionValues {
if (!configOptions) return commandOptions

return {
wallet: commandOptions.wallet || configOptions.wallet_path,
password: commandOptions.password || configOptions.wallet_password,
node_url:
commandOptions.node_url || configOptions.node_url || DEFAULT_NODE_URL,
chunk_size: configOptions.chunk_size || DEFAULT_CHUNK_SIZE,
secret_key: configOptions.secret_key || '',
}
}
8 changes: 1 addition & 7 deletions cli/src/commands/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ export const deleteCommand = new Command('delete')
.description('Delete the given website from Massa blockchain')
.argument('<address>', 'Address of the website to delete')
.action(async (address, _, command) => {
const globalOptions = command.parent?.opts()

if (!globalOptions) {
throw new Error(
'Global options are not defined. This should never happen.'
)
}
const globalOptions = command.optsWithGlobals()

const provider = await makeProviderFromNodeURLAndSecret(globalOptions)

Expand Down
8 changes: 1 addition & 7 deletions cli/src/commands/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,7 @@ export const listFilesCommand = new Command('list')
.description('Lists files from the given website on Massa blockchain')
.option('-a, --address <address>', 'Address of the website to list')
.action(async (options, command) => {
const globalOptions = command.parent?.opts()

if (!globalOptions) {
throw new Error(
'Global options are not defined. This should never happen.'
)
}
const globalOptions = command.optsWithGlobals()

const provider = await makeProviderFromNodeURLAndSecret(globalOptions)

Expand Down
8 changes: 1 addition & 7 deletions cli/src/commands/showFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,7 @@ export const showFileCommand = new Command('show')
.argument('<file_path>', 'Path of the file to show')
.option('-a, --address <address>', 'Address of the website to edit')
.action(async (filePath, options, command) => {
const globalOptions = command.parent?.opts()

if (!globalOptions) {
throw new Error(
'Global options are not defined. This should never happen.'
)
}
const globalOptions = command.optsWithGlobals()

const provider = await makeProviderFromNodeURLAndSecret(globalOptions)

Expand Down
21 changes: 6 additions & 15 deletions cli/src/commands/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,22 @@ import { confirmUploadTask, uploadBatchesTask } from '../tasks/upload'

import { makeProviderFromNodeURLAndSecret, validateAddress } from './utils'

const DEFAULT_CHUNK_SIZE = 64000n

export const uploadCommand = new Command('upload')
.alias('u')
.description('Uploads the given website on Massa blockchain')
.argument('<website_path>', 'Path to the website directory to upload')
.option('-a, --address <address>', 'Address of the website to edit')
.option('-s, --chunkSize <size>', 'Chunk size in bytes')
.option('-y, --yes', 'Skip confirmation prompt', false)
.option(
'-c, --chunk-size <size>',
'Chunk size in bytes',
DEFAULT_CHUNK_SIZE.toString()
)
.action(async (websiteDirPath, options, command) => {
const globalOptions = command.parent?.opts()

if (!globalOptions) {
throw new Error(
'Global options are not defined. This should never happen.'
)
}
const globalOptions = command.optsWithGlobals()

const provider = await makeProviderFromNodeURLAndSecret(globalOptions)

const chunkSize = parseInt(options.chunkSize)
// set chunksize from options or config
const chunkSize =
parseInt(options.chunkSize as string) ||
(globalOptions.chunk_size as number)

const ctx: UploadCtx = {
provider: provider,
Expand Down
66 changes: 41 additions & 25 deletions cli/src/commands/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,41 @@ import { parse as yamlParse } from 'yaml'

const KEY_ENV_NAME = 'SECRET_KEY'

/**
* Load the keypair using environment variables, secret_key, or wallet file
* @param globalOptions - the global options
* @returns the keypair
*/
async function loadKeyPair(globalOptions: OptionValues): Promise<KeyPair> {
try {
const envSecretKey = process.env[KEY_ENV_NAME]

if (envSecretKey) {
return await KeyPair.fromEnv(KEY_ENV_NAME)
}

if (globalOptions.secret_key) {
return await KeyPair.fromPrivateKey(globalOptions.secret_key)
}

if (
globalOptions.wallet &&
globalOptions.password &&
globalOptions.wallet.endsWith('.yaml')
) {
return await importFromYamlKeyStore(
globalOptions.wallet,
globalOptions.password
)
}

throw new Error('No valid method to load keypair.')
} catch (error) {
console.warn(`Failed to initialize keyPair: ${error}`)
throw error
}
}

/**
* Make a provider from the node URL and secret key
* @param globalOptions - the global options
Expand All @@ -23,33 +58,14 @@ export async function makeProviderFromNodeURLAndSecret(
throw new Error('node_url is not defined. Please use --node_url to set one')
}

var keyPair: KeyPair
if (
(globalOptions.wallet && !globalOptions.password) ||
(!globalOptions.wallet && globalOptions.password)
) {
throw new Error('Both wallet and password must be provided together.')
}

if (globalOptions.wallet && globalOptions.password) {
if (!globalOptions.wallet.endsWith('.yaml')) {
throw new Error('Wallet file must be a YAML file')
}
try {
const keyPair = await loadKeyPair(globalOptions)

keyPair = await importFromYamlKeyStore(
globalOptions.wallet,
globalOptions.password
)
} else {
keyPair = await KeyPair.fromEnv(KEY_ENV_NAME)
return Web3Provider.fromRPCUrl(globalOptions.node_url as string, keyPair)
} catch (error) {
console.error(`Failed to initialize provider: ${error}`)
throw new Error('Failed to initialize provider with any available method')
}

const provider = Web3Provider.fromRPCUrl(
globalOptions.node_url as string,
keyPair
)

return provider
}

/**
Expand Down
27 changes: 23 additions & 4 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Command } from '@commander-js/extra-typings'
import { PublicApiUrl } from '@massalabs/massa-web3'

import { deleteCommand } from './commands/delete'
import { listFilesCommand } from './commands/list'
import { showFileCommand } from './commands/showFile'
import { uploadCommand } from './commands/upload'

import { existsSync } from 'fs'
import { mergeConfigAndOptions, parseConfigFile } from './commands/config'

const version = process.env.VERSION || 'dev'
const defaultConfigPath = 'deweb_cli_config.json'
const defaultNodeUrl = PublicApiUrl.Buildnet

const program = new Command()

Expand All @@ -17,7 +18,7 @@ program
.description('CLI app for deploying websites')
.version(version)
.option('-c, --config <path>', 'Path to the config file', defaultConfigPath)
.option('-n, --node_url <url>', 'Node URL', defaultNodeUrl)
.option('-n, --node_url <url>', 'Node URL')
.option('-w, --wallet <path>', 'Path to the wallet file')
.option('-p, --password <password>', 'Password for the wallet file')

Expand All @@ -26,4 +27,22 @@ program.addCommand(deleteCommand)
program.addCommand(listFilesCommand)
program.addCommand(showFileCommand)

program.parse(process.argv)
interface OptionValues {
config: string
node_url: string
wallet: string
password: string
}

const commandOptions: OptionValues = program.opts() as OptionValues

if (existsSync(commandOptions.config)) {
const configOptions = parseConfigFile(commandOptions.config)
// commandOptions get priority over configOptions
const programOptions = mergeConfigAndOptions(commandOptions, configOptions)
for (const [key, value] of Object.entries(programOptions)) {
program.setOptionValue(key, value)
}
}

program.parse()
Loading