From 5e52e1f1cbcdfc5fea329d57f340d22604727c78 Mon Sep 17 00:00:00 2001 From: Peter van der Zee Date: Mon, 24 Mar 2025 15:38:25 +0100 Subject: [PATCH 1/3] [socket config] Add configuration commands --- src/cli.ts | 2 + src/commands/config/cmd-config-get.test.ts | 86 ++++++++++++++ src/commands/config/cmd-config-get.ts | 79 +++++++++++++ src/commands/config/cmd-config-list.test.ts | 65 +++++++++++ src/commands/config/cmd-config-list.ts | 67 +++++++++++ src/commands/config/cmd-config-set.test.ts | 100 +++++++++++++++++ src/commands/config/cmd-config-set.ts | 96 ++++++++++++++++ src/commands/config/cmd-config-unset.test.ts | 86 ++++++++++++++ src/commands/config/cmd-config-unset.ts | 79 +++++++++++++ src/commands/config/cmd-config.test.ts | 70 ++++++++++++ src/commands/config/cmd-config.ts | 30 +++++ src/commands/config/handle-config-get.ts | 16 +++ src/commands/config/handle-config-set.ts | 18 +++ src/commands/config/handle-config-unset.ts | 16 +++ src/commands/config/output-config-get.ts | 19 ++++ src/commands/config/output-config-list.ts | 60 ++++++++++ src/commands/config/output-config-set.ts | 24 ++++ src/commands/config/output-config-unset.ts | 23 ++++ src/commands/login/apply-login.ts | 10 +- src/commands/login/attempt-login.ts | 8 +- src/commands/logout/apply-logout.ts | 10 +- src/utils/alert/rules.ts | 4 +- src/utils/api.ts | 4 +- src/utils/{settings.ts => config.ts} | 112 ++++++++++--------- src/utils/meow-with-subcommands.ts | 4 +- src/utils/sdk.ts | 8 +- 26 files changed, 1022 insertions(+), 74 deletions(-) create mode 100644 src/commands/config/cmd-config-get.test.ts create mode 100644 src/commands/config/cmd-config-get.ts create mode 100644 src/commands/config/cmd-config-list.test.ts create mode 100644 src/commands/config/cmd-config-list.ts create mode 100644 src/commands/config/cmd-config-set.test.ts create mode 100644 src/commands/config/cmd-config-set.ts create mode 100644 src/commands/config/cmd-config-unset.test.ts create mode 100644 src/commands/config/cmd-config-unset.ts create mode 100644 src/commands/config/cmd-config.test.ts create mode 100644 src/commands/config/cmd-config.ts create mode 100644 src/commands/config/handle-config-get.ts create mode 100644 src/commands/config/handle-config-set.ts create mode 100644 src/commands/config/handle-config-unset.ts create mode 100644 src/commands/config/output-config-get.ts create mode 100644 src/commands/config/output-config-list.ts create mode 100644 src/commands/config/output-config-set.ts create mode 100644 src/commands/config/output-config-unset.ts rename src/utils/{settings.ts => config.ts} (56%) diff --git a/src/cli.ts b/src/cli.ts index d2b7f3648..a64409ebc 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -12,6 +12,7 @@ import { logger } from '@socketsecurity/registry/lib/logger' import { cmdAnalytics } from './commands/analytics/cmd-analytics' import { cmdAuditLog } from './commands/audit-log/cmd-audit-log' import { cmdCdxgen } from './commands/cdxgen/cmd-cdxgen' +import { cmdConfig } from './commands/config/cmd-config' import { cmdScanCreate } from './commands/dependencies/cmd-dependencies' import { cmdDiffScan } from './commands/diff-scan/cmd-diff-scan' import { cmdFix } from './commands/fix/cmd-fix' @@ -51,6 +52,7 @@ void (async () => { await meowWithSubcommands( { cdxgen: cmdCdxgen, + config: cmdConfig, fix: cmdFix, info: cmdInfo, login: cmdLogin, diff --git a/src/commands/config/cmd-config-get.test.ts b/src/commands/config/cmd-config-get.test.ts new file mode 100644 index 000000000..db7a7c071 --- /dev/null +++ b/src/commands/config/cmd-config-get.test.ts @@ -0,0 +1,86 @@ +import path from 'node:path' + +import { describe, expect } from 'vitest' + +import constants from '../../../dist/constants.js' +import { cmdit, invokeNpm } from '../../../test/utils' + +const { CLI } = constants + +describe('socket config get', async () => { + // Lazily access constants.rootBinPath. + const entryPath = path.join(constants.rootBinPath, `${CLI}.js`) + + cmdit(['config', 'get', '--help'], 'should support --help', async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot( + ` + "Get the value of a local CLI config item + + Usage + $ socket config get + + Options + --dryRun Do input validation for a command and exit 0 when input is ok + --help Print this help. + --json Output result as json + --markdown Output result as markdown + + Examples + $ socket config get FakeOrg --repoName=test-repo" + ` + ) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: " + `) + + expect(code, 'help should exit with code 2').toBe(2) + expect(stderr, 'header should include command (without params)').toContain( + '`socket config get`' + ) + }) + + cmdit( + ['config', 'get', '--dry-run'], + 'should require args with just dry-run', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot(`""`) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: + + \\x1b[31m\\xd7\\x1b[39m \\x1b[41m\\x1b[37mInput error\\x1b[39m\\x1b[49m: Please provide the required fields: + + - Config key should be the first arg \\x1b[31m(missing!)\\x1b[39m" + `) + + expect(code, 'dry-run should exit with code 2 if missing input').toBe(2) + } + ) + + cmdit( + ['config', 'get', 'test', '--dry-run'], + 'should require args with just dry-run', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: " + `) + + expect(code, 'dry-run should exit with code 0 if input ok').toBe(0) + } + ) +}) diff --git a/src/commands/config/cmd-config-get.ts b/src/commands/config/cmd-config-get.ts new file mode 100644 index 000000000..15cf4b4ae --- /dev/null +++ b/src/commands/config/cmd-config-get.ts @@ -0,0 +1,79 @@ +import { stripIndents } from 'common-tags' +import colors from 'yoctocolors-cjs' + +import { logger } from '@socketsecurity/registry/lib/logger' + +import { handleConfigGet } from './handle-config-get' +import constants from '../../constants' +import { commonFlags, outputFlags } from '../../flags' +import { LocalConfig, supportedConfigKeys } from '../../utils/config' +import { meowOrExit } from '../../utils/meow-with-subcommands' +import { getFlagListOutput } from '../../utils/output-formatting' + +import type { CliCommandConfig } from '../../utils/meow-with-subcommands' + +const { DRY_RUN_BAIL_TEXT } = constants + +const config: CliCommandConfig = { + commandName: 'get', + description: 'Get the value of a local CLI config item', + hidden: false, + flags: { + ...commonFlags, + ...outputFlags + }, + help: (command, config) => ` + Usage + $ ${command} + + Options + ${getFlagListOutput(config.flags, 6)} + + Examples + $ ${command} FakeOrg --repoName=test-repo + ` +} + +export const cmdConfigGet = { + description: config.description, + hidden: config.hidden, + run +} + +async function run( + argv: string[] | readonly string[], + importMeta: ImportMeta, + { parentName }: { parentName: string } +): Promise { + const cli = meowOrExit({ + argv, + config, + importMeta, + parentName + }) + + const { json, markdown } = cli.flags + const [key = ''] = cli.input + + if (!supportedConfigKeys.has(key as keyof LocalConfig) && key !== 'test') { + // Use exit status of 2 to indicate incorrect usage, generally invalid + // options or missing arguments. + // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html + process.exitCode = 2 + logger.fail(stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields: + + - Config key should be the first arg ${!key ? colors.red('(missing!)') : !supportedConfigKeys.has(key as any) ? colors.red('(invalid config key!)') : colors.green('(ok)')} + `) + return + } + + if (cli.flags['dryRun']) { + logger.log(DRY_RUN_BAIL_TEXT) + return + } + + await handleConfigGet({ + key: key as keyof LocalConfig, + outputKind: json ? 'json' : markdown ? 'markdown' : 'text' + }) +} diff --git a/src/commands/config/cmd-config-list.test.ts b/src/commands/config/cmd-config-list.test.ts new file mode 100644 index 000000000..5d8688b0a --- /dev/null +++ b/src/commands/config/cmd-config-list.test.ts @@ -0,0 +1,65 @@ +import path from 'node:path' + +import { describe, expect } from 'vitest' + +import constants from '../../../dist/constants.js' +import { cmdit, invokeNpm } from '../../../test/utils' + +const { CLI } = constants + +describe('socket config get', async () => { + // Lazily access constants.rootBinPath. + const entryPath = path.join(constants.rootBinPath, `${CLI}.js`) + + cmdit(['config', 'list', '--help'], 'should support --help', async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot( + ` + "Show all local CLI config items and their values + + Usage + $ socket config list + + Options + --dryRun Do input validation for a command and exit 0 when input is ok + --full Show full tokens in plaintext (unsafe) + --help Print this help. + --json Output result as json + --markdown Output result as markdown + + Examples + $ socket config list FakeOrg --repoName=test-repo" + ` + ) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config list\`, cwd: " + `) + + expect(code, 'help should exit with code 2').toBe(2) + expect(stderr, 'header should include command (without params)').toContain( + '`socket config list`' + ) + }) + + cmdit( + ['config', 'list', '--dry-run'], + 'should require args with just dry-run', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config list\`, cwd: " + `) + + expect(code, 'dry-run should exit with code 0 if input ok').toBe(0) + } + ) +}) diff --git a/src/commands/config/cmd-config-list.ts b/src/commands/config/cmd-config-list.ts new file mode 100644 index 000000000..21d125273 --- /dev/null +++ b/src/commands/config/cmd-config-list.ts @@ -0,0 +1,67 @@ +import { logger } from '@socketsecurity/registry/lib/logger' + +import { outputConfigList } from './output-config-list' +import constants from '../../constants' +import { commonFlags, outputFlags } from '../../flags' +import { meowOrExit } from '../../utils/meow-with-subcommands' +import { getFlagListOutput } from '../../utils/output-formatting' + +import type { CliCommandConfig } from '../../utils/meow-with-subcommands' + +const { DRY_RUN_BAIL_TEXT } = constants + +const config: CliCommandConfig = { + commandName: 'list', + description: 'Show all local CLI config items and their values', + hidden: false, + flags: { + ...commonFlags, + ...outputFlags, + full: { + type: 'boolean', + default: false, + description: 'Show full tokens in plaintext (unsafe)' + } + }, + help: (command, config) => ` + Usage + $ ${command} + + Options + ${getFlagListOutput(config.flags, 6)} + + Examples + $ ${command} FakeOrg --repoName=test-repo + ` +} + +export const cmdConfigList = { + description: config.description, + hidden: config.hidden, + run +} + +async function run( + argv: string[] | readonly string[], + importMeta: ImportMeta, + { parentName }: { parentName: string } +): Promise { + const cli = meowOrExit({ + argv, + config, + importMeta, + parentName + }) + + const { full, json, markdown } = cli.flags + + if (cli.flags['dryRun']) { + logger.log(DRY_RUN_BAIL_TEXT) + return + } + + await outputConfigList({ + full: !!full, + outputKind: json ? 'json' : markdown ? 'markdown' : 'text' + }) +} diff --git a/src/commands/config/cmd-config-set.test.ts b/src/commands/config/cmd-config-set.test.ts new file mode 100644 index 000000000..e6764039a --- /dev/null +++ b/src/commands/config/cmd-config-set.test.ts @@ -0,0 +1,100 @@ +import path from 'node:path' + +import { describe, expect } from 'vitest' + +import constants from '../../../dist/constants.js' +import { cmdit, invokeNpm } from '../../../test/utils' + +const { CLI } = constants + +describe('socket config get', async () => { + // Lazily access constants.rootBinPath. + const entryPath = path.join(constants.rootBinPath, `${CLI}.js`) + + cmdit(['config', 'set', '--help'], 'should support --help', async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot( + ` + "Update the value of a local CLI config item + + Usage + $ socket config set + + Options + --dryRun Do input validation for a command and exit 0 when input is ok + --help Print this help. + --json Output result as json + --markdown Output result as markdown + + This is a crude way of updating the local configuration for this CLI tool. + + Note that updating a value here is nothing more than updating a key/value + store entry. No validation is happening. The server may reject your config. + + Keys: + + - apiBaseUrl -- Base URL of the API endpoint + - apiToken -- The API token required to access most API endpoints + - apiProxy -- A proxy through which to access the API + - enforcedOrgs -- Orgs in this list have their security policies enforced on this machine + + Examples + $ socket config set apiProxy https://example.com" + ` + ) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config set\`, cwd: " + `) + + expect(code, 'help should exit with code 2').toBe(2) + expect(stderr, 'header should include command (without params)').toContain( + '`socket config set`' + ) + }) + + cmdit( + ['config', 'set', '--dry-run'], + 'should require args with just dry-run', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot(`""`) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config set\`, cwd: + + \\x1b[31m\\xd7\\x1b[39m \\x1b[41m\\x1b[37mInput error\\x1b[39m\\x1b[49m: Please provide the required fields: + + - Config key should be the first arg \\x1b[31m(missing!)\\x1b[39m + + - Key value should be the remaining args (use \`del\` to unset a value) \\x1b[31m(missing!)\\x1b[39m" + `) + + expect(code, 'dry-run should exit with code 2 if missing input').toBe(2) + } + ) + + cmdit( + ['config', 'set', 'test', 'xyz', '--dry-run'], + 'should require args with just dry-run', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config set\`, cwd: " + `) + + expect(code, 'dry-run should exit with code 0 if input ok').toBe(0) + } + ) +}) diff --git a/src/commands/config/cmd-config-set.ts b/src/commands/config/cmd-config-set.ts new file mode 100644 index 000000000..e836e7719 --- /dev/null +++ b/src/commands/config/cmd-config-set.ts @@ -0,0 +1,96 @@ +import { stripIndents } from 'common-tags' +import colors from 'yoctocolors-cjs' + +import { logger } from '@socketsecurity/registry/lib/logger' + +import { handleConfigSet } from './handle-config-set' +import constants from '../../constants' +import { commonFlags, outputFlags } from '../../flags' +import { LocalConfig, supportedConfigKeys } from '../../utils/config' +import { meowOrExit } from '../../utils/meow-with-subcommands' +import { getFlagListOutput } from '../../utils/output-formatting' + +import type { CliCommandConfig } from '../../utils/meow-with-subcommands' + +const { DRY_RUN_BAIL_TEXT } = constants + +const config: CliCommandConfig = { + commandName: 'set', + description: 'Update the value of a local CLI config item', + hidden: false, + flags: { + ...commonFlags, + ...outputFlags + }, + help: (command, config) => ` + Usage + $ ${command} + + Options + ${getFlagListOutput(config.flags, 6)} + + This is a crude way of updating the local configuration for this CLI tool. + + Note that updating a value here is nothing more than updating a key/value + store entry. No validation is happening. The server may reject your config. + + Keys: + +${Array.from(supportedConfigKeys.entries()) + .map(([key, desc]) => ` - ${key} -- ${desc}`) + .join('\n')} + + Examples + $ ${command} apiProxy https://example.com + ` +} + +export const cmdConfigSet = { + description: config.description, + hidden: config.hidden, + run +} + +async function run( + argv: string[] | readonly string[], + importMeta: ImportMeta, + { parentName }: { parentName: string } +): Promise { + const cli = meowOrExit({ + argv, + config, + importMeta, + parentName + }) + + const { json, markdown } = cli.flags + const [key = '', ...rest] = cli.input + const value = rest.join(' ') + + if ( + (!supportedConfigKeys.has(key as keyof LocalConfig) && key !== 'test') || + !value + ) { + // Use exit status of 2 to indicate incorrect usage, generally invalid + // options or missing arguments. + // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html + process.exitCode = 2 + logger.fail(stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields: + + - Config key should be the first arg ${!key ? colors.red('(missing!)') : !supportedConfigKeys.has(key as any) ? colors.red('(invalid config key!)') : colors.green('(ok)')} + + - Key value should be the remaining args (use \`del\` to unset a value) ${!value ? colors.red('(missing!)') : colors.green('(ok)')}`) + return + } + + if (cli.flags['dryRun']) { + logger.log(DRY_RUN_BAIL_TEXT) + return + } + + await handleConfigSet({ + key: key as keyof LocalConfig, + outputKind: json ? 'json' : markdown ? 'markdown' : 'text', + value + }) +} diff --git a/src/commands/config/cmd-config-unset.test.ts b/src/commands/config/cmd-config-unset.test.ts new file mode 100644 index 000000000..207479bc1 --- /dev/null +++ b/src/commands/config/cmd-config-unset.test.ts @@ -0,0 +1,86 @@ +import path from 'node:path' + +import { describe, expect } from 'vitest' + +import constants from '../../../dist/constants.js' +import { cmdit, invokeNpm } from '../../../test/utils' + +const { CLI } = constants + +describe('socket config unset', async () => { + // Lazily access constants.rootBinPath. + const entryPath = path.join(constants.rootBinPath, `${CLI}.js`) + + cmdit(['config', 'unset', '--help'], 'should support --help', async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot( + ` + "Clear the value of a local CLI config item + + Usage + $ socket config unset + + Options + --dryRun Do input validation for a command and exit 0 when input is ok + --help Print this help. + --json Output result as json + --markdown Output result as markdown + + Examples + $ socket config unset FakeOrg --repoName=test-repo" + ` + ) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config unset\`, cwd: " + `) + + expect(code, 'help should exit with code 2').toBe(2) + expect(stderr, 'header should include command (without params)').toContain( + '`socket config unset`' + ) + }) + + cmdit( + ['config', 'unset', '--dry-run'], + 'should require args with just dry-run', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot(`""`) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config unset\`, cwd: + + \\x1b[31m\\xd7\\x1b[39m \\x1b[41m\\x1b[37mInput error\\x1b[39m\\x1b[49m: Please provide the required fields: + + - Config key should be the first arg \\x1b[31m(missing!)\\x1b[39m" + `) + + expect(code, 'dry-run should exit with code 2 if missing input').toBe(2) + } + ) + + cmdit( + ['config', 'unset', 'test', '--dry-run'], + 'should require args with just dry-run', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config unset\`, cwd: " + `) + + expect(code, 'dry-run should exit with code 0 if input ok').toBe(0) + } + ) +}) diff --git a/src/commands/config/cmd-config-unset.ts b/src/commands/config/cmd-config-unset.ts new file mode 100644 index 000000000..dd1d3dbe4 --- /dev/null +++ b/src/commands/config/cmd-config-unset.ts @@ -0,0 +1,79 @@ +import { stripIndents } from 'common-tags' +import colors from 'yoctocolors-cjs' + +import { logger } from '@socketsecurity/registry/lib/logger' + +import { handleConfigUnset } from './handle-config-unset' +import constants from '../../constants' +import { commonFlags, outputFlags } from '../../flags' +import { LocalConfig, supportedConfigKeys } from '../../utils/config' +import { meowOrExit } from '../../utils/meow-with-subcommands' +import { getFlagListOutput } from '../../utils/output-formatting' + +import type { CliCommandConfig } from '../../utils/meow-with-subcommands' + +const { DRY_RUN_BAIL_TEXT } = constants + +const config: CliCommandConfig = { + commandName: 'unset', + description: 'Clear the value of a local CLI config item', + hidden: false, + flags: { + ...commonFlags, + ...outputFlags + }, + help: (command, config) => ` + Usage + $ ${command} + + Options + ${getFlagListOutput(config.flags, 6)} + + Examples + $ ${command} FakeOrg --repoName=test-repo + ` +} + +export const cmdConfigUnset = { + description: config.description, + hidden: config.hidden, + run +} + +async function run( + argv: string[] | readonly string[], + importMeta: ImportMeta, + { parentName }: { parentName: string } +): Promise { + const cli = meowOrExit({ + argv, + config, + importMeta, + parentName + }) + + const { json, markdown } = cli.flags + const [key = ''] = cli.input + + if (!supportedConfigKeys.has(key as keyof LocalConfig) && key !== 'test') { + // Use exit status of 2 to indicate incorrect usage, generally invalid + // options or missing arguments. + // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html + process.exitCode = 2 + logger.fail(stripIndents`${colors.bgRed(colors.white('Input error'))}: Please provide the required fields: + + - Config key should be the first arg ${!key ? colors.red('(missing!)') : !supportedConfigKeys.has(key as any) ? colors.red('(invalid config key!)') : colors.green('(ok)')} + `) + return + } + + if (cli.flags['dryRun']) { + logger.log(DRY_RUN_BAIL_TEXT) + return + } + + await handleConfigUnset({ + key: key as keyof LocalConfig, + outputKind: json ? 'json' : markdown ? 'markdown' : 'text' + }) +} diff --git a/src/commands/config/cmd-config.test.ts b/src/commands/config/cmd-config.test.ts new file mode 100644 index 000000000..cdd00227b --- /dev/null +++ b/src/commands/config/cmd-config.test.ts @@ -0,0 +1,70 @@ +import path from 'node:path' + +import { describe, expect } from 'vitest' + +import constants from '../../../dist/constants.js' +import { cmdit, invokeNpm } from '../../../test/utils' + +const { CLI } = constants + +describe('socket config', async () => { + // Lazily access constants.rootBinPath. + const entryPath = path.join(constants.rootBinPath, `${CLI}.js`) + + cmdit(['config', '--help'], 'should support --help', async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot( + ` + "Commands related to the local CLI configuration + + Usage + $ socket config + + Commands + get Get the value of a local CLI config item + list Show all local CLI config items and their values + set Update the value of a local CLI config item + unset Clear the value of a local CLI config item + + Options + --dryRun Do input validation for a command and exit 0 when input is ok + --help Print this help. + + Examples + $ socket config --help" + ` + ) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config\`, cwd: " + `) + + expect(code, 'help should exit with code 2').toBe(2) + expect(stderr, 'header should include command (without params)').toContain( + '`socket config`' + ) + }) + + cmdit( + ['config', '--dry-run'], + 'should require args with just dry-run', + async cmd => { + const { code, stderr, stdout } = await invokeNpm(entryPath, cmd) + expect(stdout).toMatchInlineSnapshot( + `"[DryRun]: No-op, call a sub-command; ok"` + ) + expect(`\n ${stderr}`).toMatchInlineSnapshot(` + " + _____ _ _ /--------------- + | __|___ ___| |_ ___| |_ | Socket.dev CLI ver + |__ | . | _| '_| -_| _| | Node: , API token set: + |_____|___|___|_,_|___|_|.dev | Command: \`socket config\`, cwd: " + `) + + expect(code, 'dry-run should exit with code 0 if input ok').toBe(0) + } + ) +}) diff --git a/src/commands/config/cmd-config.ts b/src/commands/config/cmd-config.ts new file mode 100644 index 000000000..61b607279 --- /dev/null +++ b/src/commands/config/cmd-config.ts @@ -0,0 +1,30 @@ +import { cmdConfigGet } from './cmd-config-get' +import { cmdConfigList } from './cmd-config-list' +import { cmdConfigSet } from './cmd-config-set' +import { cmdConfigUnset } from './cmd-config-unset' +import { meowWithSubcommands } from '../../utils/meow-with-subcommands' + +import type { CliSubcommand } from '../../utils/meow-with-subcommands' + +const description = 'Commands related to the local CLI configuration' + +export const cmdConfig: CliSubcommand = { + description, + hidden: true, // [beta] + async run(argv, importMeta, { parentName }) { + await meowWithSubcommands( + { + unset: cmdConfigUnset, + get: cmdConfigGet, + list: cmdConfigList, + set: cmdConfigSet + }, + { + argv, + description, + importMeta, + name: `${parentName} config` + } + ) + } +} diff --git a/src/commands/config/handle-config-get.ts b/src/commands/config/handle-config-get.ts new file mode 100644 index 000000000..c469524bc --- /dev/null +++ b/src/commands/config/handle-config-get.ts @@ -0,0 +1,16 @@ +import { outputConfigGet } from './output-config-get' +import { getConfigValue } from '../../utils/config' + +import type { LocalConfig } from '../../utils/config' + +export async function handleConfigGet({ + key, + outputKind +}: { + key: keyof LocalConfig + outputKind: 'json' | 'markdown' | 'text' +}) { + const value = getConfigValue(key) + + await outputConfigGet(key, value, outputKind) +} diff --git a/src/commands/config/handle-config-set.ts b/src/commands/config/handle-config-set.ts new file mode 100644 index 000000000..050e16dae --- /dev/null +++ b/src/commands/config/handle-config-set.ts @@ -0,0 +1,18 @@ +import { outputConfigSet } from './output-config-set' +import { updateConfigValue } from '../../utils/config' + +import type { LocalConfig } from '../../utils/config' + +export async function handleConfigSet({ + key, + outputKind, + value +}: { + key: keyof LocalConfig + outputKind: 'json' | 'markdown' | 'text' + value: string +}) { + updateConfigValue(key, value) + + await outputConfigSet(key, value, outputKind) +} diff --git a/src/commands/config/handle-config-unset.ts b/src/commands/config/handle-config-unset.ts new file mode 100644 index 000000000..083d36d09 --- /dev/null +++ b/src/commands/config/handle-config-unset.ts @@ -0,0 +1,16 @@ +import { outputConfigUnset } from './output-config-unset' +import { updateConfigValue } from '../../utils/config' + +import type { LocalConfig } from '../../utils/config' + +export async function handleConfigUnset({ + key, + outputKind +}: { + key: keyof LocalConfig + outputKind: 'json' | 'markdown' | 'text' +}) { + updateConfigValue(key, undefined) + + await outputConfigUnset(key, outputKind) +} diff --git a/src/commands/config/output-config-get.ts b/src/commands/config/output-config-get.ts new file mode 100644 index 000000000..7df89134c --- /dev/null +++ b/src/commands/config/output-config-get.ts @@ -0,0 +1,19 @@ +import { logger } from '@socketsecurity/registry/lib/logger' + +import { LocalConfig } from '../../utils/config' + +export async function outputConfigGet( + key: keyof LocalConfig, + value: unknown, + outputKind: 'json' | 'markdown' | 'text' +) { + if (outputKind === 'json') { + logger.log(JSON.stringify({ success: true, result: { key, value } })) + } else if (outputKind === 'markdown') { + logger.log(`# Config Value`) + logger.log('') + logger.log(`Config key '${key}' has value '${value}`) + } else { + logger.log(`${key}: ${value}`) + } +} diff --git a/src/commands/config/output-config-list.ts b/src/commands/config/output-config-list.ts new file mode 100644 index 000000000..ccda6428e --- /dev/null +++ b/src/commands/config/output-config-list.ts @@ -0,0 +1,60 @@ +import { logger } from '@socketsecurity/registry/lib/logger' + +import { + getConfigValue, + sensitiveConfigKeys, + supportedConfigKeys +} from '../../utils/config' + +export async function outputConfigList({ + full, + outputKind +}: { + full: boolean + outputKind: 'json' | 'markdown' | 'text' +}) { + if (outputKind === 'json') { + const obj: Record = {} + for (const key of supportedConfigKeys.keys()) { + let value = getConfigValue(key) + if (!full && sensitiveConfigKeys.has(key)) { + value = '********' + } + if (full || value !== undefined) { + obj[key as any] = value ?? '' + } + } + logger.log( + JSON.stringify( + { + success: true, + full, + config: obj + }, + null, + 2 + ) + ) + } else { + const maxWidth = Array.from(supportedConfigKeys.keys()).reduce( + (a, b) => Math.max(a, b.length), + 0 + ) + + logger.log('# Local CLI Config') + logger.log('') + logger.log(`This is the local CLI config (full=${!!full}):`) + logger.log('') + for (const key of supportedConfigKeys.keys()) { + let value = getConfigValue(key) + if (!full && sensitiveConfigKeys.has(key)) { + value = '********' + } + if (full || value !== undefined) { + logger.log( + `- ${key}:${' '.repeat(Math.max(0, maxWidth - key.length + 3))} ${Array.isArray(value) ? value.join(', ') || '' : (value ?? '')}` + ) + } + } + } +} diff --git a/src/commands/config/output-config-set.ts b/src/commands/config/output-config-set.ts new file mode 100644 index 000000000..84df41f45 --- /dev/null +++ b/src/commands/config/output-config-set.ts @@ -0,0 +1,24 @@ +import { logger } from '@socketsecurity/registry/lib/logger' + +import type { LocalConfig } from '../../utils/config' + +export async function outputConfigSet( + key: keyof LocalConfig, + _value: string, + outputKind: 'json' | 'markdown' | 'text' +) { + if (outputKind === 'json') { + logger.log( + JSON.stringify({ + success: true, + message: `Config key '${key}' was updated` + }) + ) + } else if (outputKind === 'markdown') { + logger.log(`# Update config`) + logger.log('') + logger.log(`Config key '${key}' was updated`) + } else { + logger.log(`OK`) + } +} diff --git a/src/commands/config/output-config-unset.ts b/src/commands/config/output-config-unset.ts new file mode 100644 index 000000000..19ca06dc2 --- /dev/null +++ b/src/commands/config/output-config-unset.ts @@ -0,0 +1,23 @@ +import { logger } from '@socketsecurity/registry/lib/logger' + +import type { LocalConfig } from '../../utils/config' + +export async function outputConfigUnset( + key: keyof LocalConfig, + outputKind: 'json' | 'markdown' | 'text' +) { + if (outputKind === 'json') { + logger.log( + JSON.stringify({ + success: true, + message: `Config key '${key}' was unset` + }) + ) + } else if (outputKind === 'markdown') { + logger.log(`# Update config`) + logger.log('') + logger.log(`Config key '${key}' was unset`) + } else { + logger.log(`OK`) + } +} diff --git a/src/commands/login/apply-login.ts b/src/commands/login/apply-login.ts index 101097208..09e0ec75c 100644 --- a/src/commands/login/apply-login.ts +++ b/src/commands/login/apply-login.ts @@ -1,4 +1,4 @@ -import { updateSetting } from '../../utils/settings' +import { updateConfigValue } from '../../utils/config' export function applyLogin( apiToken: string, @@ -6,8 +6,8 @@ export function applyLogin( apiBaseUrl: string | undefined, apiProxy: string | undefined ) { - updateSetting('enforcedOrgs', enforcedOrgs) - updateSetting('apiToken', apiToken) - updateSetting('apiBaseUrl', apiBaseUrl) - updateSetting('apiProxy', apiProxy) + updateConfigValue('enforcedOrgs', enforcedOrgs) + updateConfigValue('apiToken', apiToken) + updateConfigValue('apiBaseUrl', apiBaseUrl) + updateConfigValue('apiProxy', apiProxy) } diff --git a/src/commands/login/attempt-login.ts b/src/commands/login/attempt-login.ts index ad303fa46..65efd195b 100644 --- a/src/commands/login/attempt-login.ts +++ b/src/commands/login/attempt-login.ts @@ -5,9 +5,9 @@ import { confirm, password, select } from '@socketsecurity/registry/lib/prompts' import { applyLogin } from './apply-login' import constants from '../../constants' +import { getConfigValue } from '../../utils/config' import { AuthError } from '../../utils/errors' import { setupSdk } from '../../utils/sdk' -import { getSetting } from '../../utils/settings' import type { Choice, Separator } from '@socketsecurity/registry/lib/prompts' import type { SocketSdkReturnType } from '@socketsecurity/sdk' @@ -20,8 +20,8 @@ export async function attemptLogin( apiBaseUrl: string | undefined, apiProxy: string | undefined ) { - apiBaseUrl ??= getSetting('apiBaseUrl') ?? undefined - apiProxy ??= getSetting('apiProxy') ?? undefined + apiBaseUrl ??= getConfigValue('apiBaseUrl') ?? undefined + apiProxy ??= getConfigValue('apiProxy') ?? undefined const apiToken = (await password({ message: `Enter your ${terminalLink( @@ -94,7 +94,7 @@ export async function attemptLogin( spinner.stop() - const oldToken = getSetting('apiToken') + const oldToken = getConfigValue('apiToken') try { applyLogin(apiToken, enforcedOrgs, apiBaseUrl, apiProxy) logger.success(`API credentials ${oldToken ? 'updated' : 'set'}`) diff --git a/src/commands/logout/apply-logout.ts b/src/commands/logout/apply-logout.ts index a35866a4f..b88caa536 100644 --- a/src/commands/logout/apply-logout.ts +++ b/src/commands/logout/apply-logout.ts @@ -1,8 +1,8 @@ -import { updateSetting } from '../../utils/settings' +import { updateConfigValue } from '../../utils/config' export function applyLogout() { - updateSetting('apiToken', null) - updateSetting('apiBaseUrl', null) - updateSetting('apiProxy', null) - updateSetting('enforcedOrgs', null) + updateConfigValue('apiToken', null) + updateConfigValue('apiBaseUrl', null) + updateConfigValue('apiProxy', null) + updateConfigValue('enforcedOrgs', null) } diff --git a/src/utils/alert/rules.ts b/src/utils/alert/rules.ts index 552e2990b..85d6cb8bc 100644 --- a/src/utils/alert/rules.ts +++ b/src/utils/alert/rules.ts @@ -1,8 +1,8 @@ import { isObject } from '@socketsecurity/registry/lib/objects' +import { findSocketYmlSync, getConfigValue } from '../config' import { isErrnoException } from '../errors' import { getPublicToken, setupSdk } from '../sdk' -import { findSocketYmlSync, getSetting } from '../settings' import type { SocketSdkResultType } from '@socketsecurity/sdk' @@ -229,7 +229,7 @@ export async function uxLookup( } })() // Remove any organizations not being enforced. - const enforcedOrgs = getSetting('enforcedOrgs') ?? [] + const enforcedOrgs = getConfigValue('enforcedOrgs') ?? [] for (const { 0: i, 1: org } of orgs.entries()) { if (!enforcedOrgs.includes(org.id)) { settings.entries.splice(i, 1) diff --git a/src/utils/api.ts b/src/utils/api.ts index 01109dff1..46b0f9a41 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -5,8 +5,8 @@ import colors from 'yoctocolors-cjs' import { logger } from '@socketsecurity/registry/lib/logger' import { isNonEmptyString } from '@socketsecurity/registry/lib/strings' +import { getConfigValue } from './config' import { AuthError } from './errors' -import { getSetting } from './settings' import constants from '../constants' import type { @@ -65,7 +65,7 @@ export function getLastFiveOfApiToken(token: string): string { // The API server that should be used for operations. export function getDefaultApiBaseUrl(): string | undefined { const baseUrl = - process.env['SOCKET_SECURITY_API_BASE_URL'] || getSetting('apiBaseUrl') + process.env['SOCKET_SECURITY_API_BASE_URL'] || getConfigValue('apiBaseUrl') return isNonEmptyString(baseUrl) ? baseUrl : undefined } diff --git a/src/utils/settings.ts b/src/utils/config.ts similarity index 56% rename from src/utils/settings.ts rename to src/utils/config.ts index df8e3ca9c..e5a67e66e 100644 --- a/src/utils/settings.ts +++ b/src/utils/config.ts @@ -9,58 +9,66 @@ import { logger } from '@socketsecurity/registry/lib/logger' import { safeReadFileSync } from './fs' import constants from '../constants' +export interface LocalConfig { + apiBaseUrl?: string | null | undefined + // @deprecated ; use apiToken + apiKey?: string | null | undefined + apiProxy?: string | null | undefined + // apiToken replaced apiKey. + apiToken?: string | null | undefined + defaultOrg?: string + enforcedOrgs?: string[] | readonly string[] | null | undefined + // Ignore. + test?: unknown +} + // Default app data folder env var on Win const LOCALAPPDATA = 'LOCALAPPDATA' // Default app data folder env var on Mac/Linux const XDG_DATA_HOME = 'XDG_DATA_HOME' -const SOCKET_APP_DIR = 'socket/settings' +const SOCKET_APP_DIR = 'socket/settings' // It used to be settings... -const supportedApiKeys: Set = new Set([ - 'apiBaseUrl', - 'apiKey', - 'apiProxy', - 'enforcedOrgs' +export const supportedConfigKeys: Map = new Map([ + ['apiBaseUrl', 'Base URL of the API endpoint'], + ['apiToken', 'The API token required to access most API endpoints'], + ['apiProxy', 'A proxy through which to access the API'], + [ + 'enforcedOrgs', + 'Orgs in this list have their security policies enforced on this machine' + ] ]) -interface Settings { - apiBaseUrl?: string | null | undefined - // @deprecated - apiKey?: string | null | undefined - apiProxy?: string | null | undefined - enforcedOrgs?: string[] | readonly string[] | null | undefined - // apiToken is an alias for apiKey. - apiToken?: string | null | undefined -} +export const sensitiveConfigKeys: Set = new Set(['apiToken']) -let settings: Settings | undefined -let settingsPath: string | undefined -let warnedSettingPathWin32Missing = false +let cachedConfig: LocalConfig | undefined +let configPath: string | undefined +let warnedConfigPathWin32Missing = false let pendingSave = false -function getSettings(): Settings { - if (settings === undefined) { - settings = {} as Settings - const settingsPath = getSettingsPath() - if (settingsPath) { - const raw = safeReadFileSync(settingsPath) +function getConfigValues(): LocalConfig { + if (cachedConfig === undefined) { + cachedConfig = {} as LocalConfig + const configPath = getConfigPath() + if (configPath) { + const raw = safeReadFileSync(configPath) if (raw) { try { Object.assign( - settings, + cachedConfig, JSON.parse(Buffer.from(raw, 'base64').toString()) ) } catch { - logger.warn(`Failed to parse settings at ${settingsPath}`) + logger.warn(`Failed to parse config at ${configPath}`) } } else { - fs.mkdirSync(path.dirname(settingsPath), { recursive: true }) + fs.mkdirSync(path.dirname(configPath), { recursive: true }) } } } - return settings + return cachedConfig } -function getSettingsPath(): string | undefined { +function getConfigPath(): string | undefined { // Get the OS app data folder: // - Win: %LOCALAPPDATA% or fail? // - Mac: %XDG_DATA_HOME% or fallback to "~/Library/Application Support/" @@ -73,7 +81,7 @@ function getSettingsPath(): string | undefined { // - Mac: %XDG_DATA_HOME%/socket/settings or "~/Library/Application Support/socket/settings" // - Linux: %XDG_DATA_HOME%/socket/settings or "~/.local/share/socket/settings" - if (settingsPath === undefined) { + if (configPath === undefined) { // Lazily access constants.WIN32. const { WIN32 } = constants let dataHome: string | undefined = WIN32 @@ -81,8 +89,8 @@ function getSettingsPath(): string | undefined { : process.env[XDG_DATA_HOME] if (!dataHome) { if (WIN32) { - if (!warnedSettingPathWin32Missing) { - warnedSettingPathWin32Missing = true + if (!warnedConfigPathWin32Missing) { + warnedConfigPathWin32Missing = true logger.warn(`Missing %${LOCALAPPDATA}%`) } } else { @@ -94,17 +102,21 @@ function getSettingsPath(): string | undefined { ) } } - settingsPath = dataHome ? path.join(dataHome, SOCKET_APP_DIR) : undefined + configPath = dataHome ? path.join(dataHome, SOCKET_APP_DIR) : undefined } - return settingsPath + return configPath } -function normalizeSettingsKey(key: keyof Settings): keyof Settings { +function normalizeConfigKey(key: keyof LocalConfig): keyof LocalConfig { const normalizedKey = key === 'apiToken' ? 'apiKey' : key - if (!supportedApiKeys.has(normalizedKey as keyof Settings)) { - throw new Error(`Invalid settings key: ${normalizedKey}`) + if ( + normalizedKey !== 'apiKey' && + normalizedKey !== 'test' && + !supportedConfigKeys.has(normalizedKey as keyof LocalConfig) + ) { + throw new Error(`Invalid config key: ${normalizedKey}`) } - return normalizedKey as keyof Settings + return normalizedKey as keyof LocalConfig } export function findSocketYmlSync() { @@ -133,27 +145,27 @@ export function findSocketYmlSync() { return null } -export function getSetting( +export function getConfigValue( key: Key -): Settings[Key] { - return getSettings()[normalizeSettingsKey(key) as Key] +): LocalConfig[Key] { + return getConfigValues()[normalizeConfigKey(key) as Key] } -export function updateSetting( - key: Key, - value: Settings[Key] +export function updateConfigValue( + key: keyof LocalConfig, + value: LocalConfig[Key] ): void { - const settings = getSettings() - settings[normalizeSettingsKey(key) as Key] = value + const localConfig = getConfigValues() + localConfig[normalizeConfigKey(key) as Key] = value if (!pendingSave) { pendingSave = true process.nextTick(() => { pendingSave = false - const settingsPath = getSettingsPath() - if (settingsPath) { + const configPath = getConfigPath() + if (configPath) { fs.writeFileSync( - settingsPath, - Buffer.from(JSON.stringify(settings)).toString('base64') + configPath, + Buffer.from(JSON.stringify(localConfig)).toString('base64') ) } }) diff --git a/src/utils/meow-with-subcommands.ts b/src/utils/meow-with-subcommands.ts index 929325f25..e208196b9 100644 --- a/src/utils/meow-with-subcommands.ts +++ b/src/utils/meow-with-subcommands.ts @@ -8,8 +8,8 @@ import { normalizePath } from '@socketsecurity/registry/lib/path' import { escapeRegExp } from '@socketsecurity/registry/lib/regexps' import { getLastFiveOfApiToken } from './api' +import { getConfigValue } from './config' import { getFlagListOutput, getHelpListOutput } from './output-formatting' -import { getSetting } from './settings' import constants from '../constants' import { MeowFlags, commonFlags } from '../flags' @@ -214,7 +214,7 @@ function getAsciiHeader(command: string) { : // The '@rollup/plugin-replace' will replace "process.env['INLINED_SOCKET_CLI_VERSION_HASH']". process.env['INLINED_SOCKET_CLI_VERSION_HASH'] const nodeVersion = redacting ? REDACTED : process.version - const apiToken = getSetting('apiToken') + const apiToken = getConfigValue('apiToken') const shownToken = redacting ? REDACTED : apiToken diff --git a/src/utils/sdk.ts b/src/utils/sdk.ts index 21f5894bb..dcd515079 100644 --- a/src/utils/sdk.ts +++ b/src/utils/sdk.ts @@ -8,8 +8,8 @@ import { password } from '@socketsecurity/registry/lib/prompts' import { isNonEmptyString } from '@socketsecurity/registry/lib/strings' import { SocketSdk, createUserAgentFromPkgJson } from '@socketsecurity/sdk' +import { getConfigValue } from './config' import { AuthError } from './errors' -import { getSetting } from './settings' import constants from '../constants' const { SOCKET_CLI_NO_API_TOKEN } = constants @@ -17,14 +17,14 @@ const { SOCKET_CLI_NO_API_TOKEN } = constants // The API server that should be used for operations. function getDefaultApiBaseUrl(): string | undefined { const baseUrl = - process.env['SOCKET_SECURITY_API_BASE_URL'] || getSetting('apiBaseUrl') + process.env['SOCKET_SECURITY_API_BASE_URL'] || getConfigValue('apiBaseUrl') return isNonEmptyString(baseUrl) ? baseUrl : undefined } // The API server that should be used for operations. function getDefaultHttpProxy(): string | undefined { const apiProxy = - process.env['SOCKET_SECURITY_API_PROXY'] || getSetting('apiProxy') + process.env['SOCKET_SECURITY_API_PROXY'] || getConfigValue('apiProxy') return isNonEmptyString(apiProxy) ? apiProxy : undefined } @@ -40,7 +40,7 @@ export function getDefaultToken(): string | undefined { // Keep 'SOCKET_SECURITY_API_KEY' as an alias of 'SOCKET_SECURITY_API_TOKEN'. // TODO: Remove 'SOCKET_SECURITY_API_KEY' alias. process.env['SOCKET_SECURITY_API_KEY'] || - getSetting('apiToken') || + getConfigValue('apiToken') || _defaultToken _defaultToken = isNonEmptyString(key) ? key : undefined } From 71d3bbc412ffcb5dffcdee603d42e34961209216 Mon Sep 17 00:00:00 2001 From: Peter van der Zee Date: Mon, 24 Mar 2025 17:14:47 +0100 Subject: [PATCH 2/3] Add the valid keys to all the config commands --- src/commands/config/cmd-config-get.ts | 9 ++++++++- src/commands/config/cmd-config-list.ts | 7 +++++++ src/commands/config/cmd-config-set.ts | 3 ++- src/commands/config/cmd-config-unset.ts | 9 ++++++++- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/commands/config/cmd-config-get.ts b/src/commands/config/cmd-config-get.ts index 15cf4b4ae..42befdbc2 100644 --- a/src/commands/config/cmd-config-get.ts +++ b/src/commands/config/cmd-config-get.ts @@ -6,10 +6,11 @@ import { logger } from '@socketsecurity/registry/lib/logger' import { handleConfigGet } from './handle-config-get' import constants from '../../constants' import { commonFlags, outputFlags } from '../../flags' -import { LocalConfig, supportedConfigKeys } from '../../utils/config' +import { supportedConfigKeys } from '../../utils/config' import { meowOrExit } from '../../utils/meow-with-subcommands' import { getFlagListOutput } from '../../utils/output-formatting' +import type { LocalConfig } from '../../utils/config' import type { CliCommandConfig } from '../../utils/meow-with-subcommands' const { DRY_RUN_BAIL_TEXT } = constants @@ -29,6 +30,12 @@ const config: CliCommandConfig = { Options ${getFlagListOutput(config.flags, 6)} + Keys: + +${Array.from(supportedConfigKeys.entries()) + .map(([key, desc]) => ` - ${key} -- ${desc}`) + .join('\n')} + Examples $ ${command} FakeOrg --repoName=test-repo ` diff --git a/src/commands/config/cmd-config-list.ts b/src/commands/config/cmd-config-list.ts index 21d125273..01dbb442f 100644 --- a/src/commands/config/cmd-config-list.ts +++ b/src/commands/config/cmd-config-list.ts @@ -3,6 +3,7 @@ import { logger } from '@socketsecurity/registry/lib/logger' import { outputConfigList } from './output-config-list' import constants from '../../constants' import { commonFlags, outputFlags } from '../../flags' +import { supportedConfigKeys } from '../../utils/config' import { meowOrExit } from '../../utils/meow-with-subcommands' import { getFlagListOutput } from '../../utils/output-formatting' @@ -30,6 +31,12 @@ const config: CliCommandConfig = { Options ${getFlagListOutput(config.flags, 6)} + Keys: + +${Array.from(supportedConfigKeys.entries()) + .map(([key, desc]) => ` - ${key} -- ${desc}`) + .join('\n')} + Examples $ ${command} FakeOrg --repoName=test-repo ` diff --git a/src/commands/config/cmd-config-set.ts b/src/commands/config/cmd-config-set.ts index e836e7719..29f7b3ab9 100644 --- a/src/commands/config/cmd-config-set.ts +++ b/src/commands/config/cmd-config-set.ts @@ -6,10 +6,11 @@ import { logger } from '@socketsecurity/registry/lib/logger' import { handleConfigSet } from './handle-config-set' import constants from '../../constants' import { commonFlags, outputFlags } from '../../flags' -import { LocalConfig, supportedConfigKeys } from '../../utils/config' +import { supportedConfigKeys } from '../../utils/config' import { meowOrExit } from '../../utils/meow-with-subcommands' import { getFlagListOutput } from '../../utils/output-formatting' +import type { LocalConfig } from '../../utils/config' import type { CliCommandConfig } from '../../utils/meow-with-subcommands' const { DRY_RUN_BAIL_TEXT } = constants diff --git a/src/commands/config/cmd-config-unset.ts b/src/commands/config/cmd-config-unset.ts index dd1d3dbe4..2997167ec 100644 --- a/src/commands/config/cmd-config-unset.ts +++ b/src/commands/config/cmd-config-unset.ts @@ -6,10 +6,11 @@ import { logger } from '@socketsecurity/registry/lib/logger' import { handleConfigUnset } from './handle-config-unset' import constants from '../../constants' import { commonFlags, outputFlags } from '../../flags' -import { LocalConfig, supportedConfigKeys } from '../../utils/config' +import { supportedConfigKeys } from '../../utils/config' import { meowOrExit } from '../../utils/meow-with-subcommands' import { getFlagListOutput } from '../../utils/output-formatting' +import type { LocalConfig } from '../../utils/config' import type { CliCommandConfig } from '../../utils/meow-with-subcommands' const { DRY_RUN_BAIL_TEXT } = constants @@ -29,6 +30,12 @@ const config: CliCommandConfig = { Options ${getFlagListOutput(config.flags, 6)} + Keys: + +${Array.from(supportedConfigKeys.entries()) + .map(([key, desc]) => ` - ${key} -- ${desc}`) + .join('\n')} + Examples $ ${command} FakeOrg --repoName=test-repo ` From b2f00c2568986f5c118920fec8b0cbb1042844cf Mon Sep 17 00:00:00 2001 From: Peter van der Zee Date: Mon, 24 Mar 2025 17:21:51 +0100 Subject: [PATCH 3/3] snapshots --- src/commands/config/cmd-config-get.test.ts | 7 +++++++ src/commands/config/cmd-config-list.test.ts | 7 +++++++ src/commands/config/cmd-config-unset.test.ts | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/src/commands/config/cmd-config-get.test.ts b/src/commands/config/cmd-config-get.test.ts index db7a7c071..5924f8960 100644 --- a/src/commands/config/cmd-config-get.test.ts +++ b/src/commands/config/cmd-config-get.test.ts @@ -26,6 +26,13 @@ describe('socket config get', async () => { --json Output result as json --markdown Output result as markdown + Keys: + + - apiBaseUrl -- Base URL of the API endpoint + - apiToken -- The API token required to access most API endpoints + - apiProxy -- A proxy through which to access the API + - enforcedOrgs -- Orgs in this list have their security policies enforced on this machine + Examples $ socket config get FakeOrg --repoName=test-repo" ` diff --git a/src/commands/config/cmd-config-list.test.ts b/src/commands/config/cmd-config-list.test.ts index 5d8688b0a..c42a1a7d6 100644 --- a/src/commands/config/cmd-config-list.test.ts +++ b/src/commands/config/cmd-config-list.test.ts @@ -27,6 +27,13 @@ describe('socket config get', async () => { --json Output result as json --markdown Output result as markdown + Keys: + + - apiBaseUrl -- Base URL of the API endpoint + - apiToken -- The API token required to access most API endpoints + - apiProxy -- A proxy through which to access the API + - enforcedOrgs -- Orgs in this list have their security policies enforced on this machine + Examples $ socket config list FakeOrg --repoName=test-repo" ` diff --git a/src/commands/config/cmd-config-unset.test.ts b/src/commands/config/cmd-config-unset.test.ts index 207479bc1..6cc259859 100644 --- a/src/commands/config/cmd-config-unset.test.ts +++ b/src/commands/config/cmd-config-unset.test.ts @@ -26,6 +26,13 @@ describe('socket config unset', async () => { --json Output result as json --markdown Output result as markdown + Keys: + + - apiBaseUrl -- Base URL of the API endpoint + - apiToken -- The API token required to access most API endpoints + - apiProxy -- A proxy through which to access the API + - enforcedOrgs -- Orgs in this list have their security policies enforced on this machine + Examples $ socket config unset FakeOrg --repoName=test-repo" `