diff --git a/cli/.gitignore b/cli/.gitignore
index affe73e..8468f41 100644
--- a/cli/.gitignore
+++ b/cli/.gitignore
@@ -39,4 +39,6 @@ coverage/
*.wasm
# wallets
-wallet*.yaml
\ No newline at end of file
+wallet*.yaml
+
+deweb_cli_config.json
\ No newline at end of file
diff --git a/cli/src/commands/config.ts b/cli/src/commands/config.ts
new file mode 100644
index 0000000..8ed0db2
--- /dev/null
+++ b/cli/src/commands/config.ts
@@ -0,0 +1,46 @@
+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}`)
+ }
+}
+
+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 || '',
+ }
+}
+
+export function setDefaultValues(commandOptions: OptionValues): OptionValues {
+ return {
+ node_url: commandOptions.node_url || DEFAULT_NODE_URL,
+ chunk_size: commandOptions.chunk_size || DEFAULT_CHUNK_SIZE,
+ }
+}
diff --git a/cli/src/commands/delete.ts b/cli/src/commands/delete.ts
index 920e074..29f468c 100644
--- a/cli/src/commands/delete.ts
+++ b/cli/src/commands/delete.ts
@@ -11,13 +11,7 @@ export const deleteCommand = new Command('delete')
.description('Delete the given website from Massa blockchain')
.argument('
', '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)
diff --git a/cli/src/commands/list.ts b/cli/src/commands/list.ts
index f8073f9..53f03c0 100644
--- a/cli/src/commands/list.ts
+++ b/cli/src/commands/list.ts
@@ -8,13 +8,7 @@ export const listFilesCommand = new Command('list')
.description('Lists files from the given website on Massa blockchain')
.option('-a, --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)
diff --git a/cli/src/commands/showFile.ts b/cli/src/commands/showFile.ts
index 57bd2b4..d927c77 100644
--- a/cli/src/commands/showFile.ts
+++ b/cli/src/commands/showFile.ts
@@ -8,13 +8,7 @@ export const showFileCommand = new Command('show')
.argument('', 'Path of the file to show')
.option('-a, --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)
diff --git a/cli/src/commands/upload.ts b/cli/src/commands/upload.ts
index 785d794..346299b 100644
--- a/cli/src/commands/upload.ts
+++ b/cli/src/commands/upload.ts
@@ -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('', 'Path to the website directory to upload')
.option('-a, --address ', 'Address of the website to edit')
+ .option('-s, --chunkSize ', 'Chunk size in bytes')
.option('-y, --yes', 'Skip confirmation prompt', false)
- .option(
- '-c, --chunk-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,
diff --git a/cli/src/commands/utils.ts b/cli/src/commands/utils.ts
index 0a21913..b8ce3f5 100644
--- a/cli/src/commands/utils.ts
+++ b/cli/src/commands/utils.ts
@@ -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 {
+ 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
@@ -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
}
/**
diff --git a/cli/src/index.ts b/cli/src/index.ts
index dcc115a..7c71514 100644
--- a/cli/src/index.ts
+++ b/cli/src/index.ts
@@ -1,14 +1,19 @@
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,
+ setDefaultValues,
+} from './commands/config'
+
const version = process.env.VERSION || 'dev'
const defaultConfigPath = 'deweb_cli_config.json'
-const defaultNodeUrl = PublicApiUrl.Buildnet
const program = new Command()
@@ -17,7 +22,7 @@ program
.description('CLI app for deploying websites')
.version(version)
.option('-c, --config ', 'Path to the config file', defaultConfigPath)
- .option('-n, --node_url ', 'Node URL', defaultNodeUrl)
+ .option('-n, --node_url ', 'Node URL')
.option('-w, --wallet ', 'Path to the wallet file')
.option('-p, --password ', 'Password for the wallet file')
@@ -26,4 +31,28 @@ 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)
+ }
+} else {
+ const defaultValues = setDefaultValues(commandOptions)
+
+ for (const [key, value] of Object.entries(defaultValues)) {
+ program.setOptionValue(key, value)
+ }
+}
+
+program.parse()