@socketsecurity/cli 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,13 +7,13 @@ import ora from 'ora'
7
7
  import { outputFlags, validationFlags } from '../../flags/index.js'
8
8
  import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
9
9
  import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js'
10
- import { InputError } from '../../utils/errors.js'
11
- import { getSeverityCount, formatSeverityCount } from '../../utils/format-issues.js'
10
+ import { prepareFlags } from '../../utils/flags.js'
11
+ import { formatSeverityCount, getCountSeverity } from '../../utils/format-issues.js'
12
12
  import { printFlagList } from '../../utils/formatting.js'
13
13
  import { objectSome } from '../../utils/misc.js'
14
14
  import { FREE_API_KEY, getDefaultKey, setupSdk } from '../../utils/sdk.js'
15
15
 
16
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
16
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
17
17
  export const info = {
18
18
  description: 'Look up info regarding a package',
19
19
  async run (argv, importMeta, { parentName }) {
@@ -21,9 +21,9 @@ export const info = {
21
21
 
22
22
  const input = setupCommand(name, info.description, argv, importMeta)
23
23
  if (input) {
24
- const spinnerText = input.pkgVersion === 'latest' ? `Looking up data for the latest version of ${input.pkgName}\n` : `Looking up data for version ${input.pkgVersion} of ${input.pkgName}\n`
24
+ const spinnerText = `Looking up data for packages: ${input.packages.join(', ')}\n`
25
25
  const spinner = ora(spinnerText).start()
26
- const packageData = await fetchPackageData(input.pkgName, input.pkgVersion, input, spinner)
26
+ const packageData = await fetchPackageData(input.packages, input.includeAlerts, spinner)
27
27
  if (packageData) {
28
28
  formatPackageDataOutput(packageData, { name, ...input }, spinner)
29
29
  }
@@ -31,15 +31,31 @@ export const info = {
31
31
  }
32
32
  }
33
33
 
34
+ const infoFlags = prepareFlags({
35
+ // At the moment in API v0, alerts and license do the same thing.
36
+ // The license parameter will be implemented later.
37
+ // license: {
38
+ // type: 'boolean',
39
+ // shortFlag: 'l',
40
+ // default: false,
41
+ // description: 'Include license - Default is false',
42
+ // },
43
+ alerts: {
44
+ type: 'boolean',
45
+ shortFlag: 'a',
46
+ default: false,
47
+ description: 'Include alerts - Default is false',
48
+ }
49
+ })
50
+
34
51
  // Internal functions
35
52
 
36
53
  /**
37
54
  * @typedef CommandContext
38
- * @property {boolean} includeAllIssues
55
+ * @property {boolean} includeAlerts
39
56
  * @property {boolean} outputJson
40
57
  * @property {boolean} outputMarkdown
41
- * @property {string} pkgName
42
- * @property {string} pkgVersion
58
+ * @property {string[]} packages
43
59
  * @property {boolean} strict
44
60
  */
45
61
 
@@ -54,18 +70,19 @@ function setupCommand (name, description, argv, importMeta) {
54
70
  const flags = {
55
71
  ...outputFlags,
56
72
  ...validationFlags,
73
+ ...infoFlags
57
74
  }
58
75
 
59
76
  const cli = meow(`
60
77
  Usage
61
- $ ${name} <name>
78
+ $ ${name} <ecosystem>:<name>@<version>
62
79
 
63
80
  Options
64
81
  ${printFlagList(flags, 6)}
65
82
 
66
83
  Examples
67
- $ ${name} webtorrent
68
- $ ${name} webtorrent@1.9.1
84
+ $ ${name} npm:webtorrent
85
+ $ ${name} npm:webtorrent@1.9.1
69
86
  `, {
70
87
  argv,
71
88
  description,
@@ -74,138 +91,162 @@ function setupCommand (name, description, argv, importMeta) {
74
91
  })
75
92
 
76
93
  const {
77
- all: includeAllIssues,
94
+ alerts: includeAlerts,
78
95
  json: outputJson,
79
96
  markdown: outputMarkdown,
80
97
  strict,
81
98
  } = cli.flags
82
99
 
83
- if (cli.input.length > 1) {
84
- throw new InputError('Only one package lookup supported at once')
85
- }
86
-
87
100
  const [rawPkgName = ''] = cli.input
88
101
 
89
102
  if (!rawPkgName) {
103
+ console.error(`${chalk.bgRed('Input error')}: Please provide an ecosystem and package name`)
90
104
  cli.showHelp()
91
105
  return
92
106
  }
93
107
 
94
- const versionSeparator = rawPkgName.lastIndexOf('@')
108
+ const /** @type {string[]} */inputPkgs = []
95
109
 
96
- const pkgName = versionSeparator < 1 ? rawPkgName : rawPkgName.slice(0, versionSeparator)
97
- const pkgVersion = versionSeparator < 1 ? 'latest' : rawPkgName.slice(versionSeparator + 1)
110
+ cli.input.map(pkg => {
111
+ const ecosystem = pkg.split(':')[0]
112
+ if (!ecosystem) {
113
+ console.error(`Package name ${pkg} formatted incorrectly.`)
114
+ return cli.showHelp()
115
+ } else {
116
+ const versionSeparator = pkg.lastIndexOf('@')
117
+ const ecosystemSeparator = pkg.lastIndexOf(ecosystem)
118
+ const pkgName = versionSeparator < 1 ? pkg.slice(ecosystemSeparator + ecosystem.length + 1) : pkg.slice(ecosystemSeparator + ecosystem.length + 1, versionSeparator)
119
+ const pkgVersion = versionSeparator < 1 ? 'latest' : pkg.slice(versionSeparator + 1)
120
+ inputPkgs.push(`${ecosystem}/${pkgName}@${pkgVersion}`)
121
+ }
122
+ return inputPkgs
123
+ })
98
124
 
99
125
  return {
100
- includeAllIssues,
126
+ includeAlerts,
101
127
  outputJson,
102
128
  outputMarkdown,
103
- pkgName,
104
- pkgVersion,
129
+ packages: inputPkgs,
105
130
  strict,
106
131
  }
107
132
  }
108
133
 
109
134
  /**
110
135
  * @typedef PackageData
111
- * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>["data"]} data
112
- * @property {Record<import('../../utils/format-issues').SocketIssue['severity'], number>} severityCount
113
- * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'getScoreByNPMPackage'>["data"]} score
136
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'batchPackageFetch'>["data"]} data
114
137
  */
115
138
 
116
139
  /**
117
- * @param {string} pkgName
118
- * @param {string} pkgVersion
119
- * @param {Pick<CommandContext, 'includeAllIssues'>} context
140
+ * @param {string[]} packages
141
+ * @param {boolean} includeAlerts
120
142
  * @param {import('ora').Ora} spinner
121
143
  * @returns {Promise<void|PackageData>}
122
144
  */
123
- async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues }, spinner) {
145
+ async function fetchPackageData (packages, includeAlerts, spinner) {
124
146
  const socketSdk = await setupSdk(getDefaultKey() || FREE_API_KEY)
125
- const result = await handleApiCall(socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion), 'looking up package')
126
- const scoreResult = await handleApiCall(socketSdk.getScoreByNPMPackage(pkgName, pkgVersion), 'looking up package score')
127
147
 
128
- if (result.success === false) {
129
- return handleUnsuccessfulApiResponse('getIssuesByNPMPackage', result, spinner)
130
- }
148
+ const components = packages.map(pkg => {
149
+ return { 'purl': `pkg:${pkg}` }
150
+ })
131
151
 
132
- if (scoreResult.success === false) {
133
- return handleUnsuccessfulApiResponse('getScoreByNPMPackage', scoreResult, spinner)
152
+ const result = await handleApiCall(socketSdk.batchPackageFetch(
153
+ { alerts: includeAlerts.toString() },
154
+ {
155
+ components
156
+ }), 'looking up package')
157
+
158
+ if (!result.success) {
159
+ return handleUnsuccessfulApiResponse('batchPackageFetch', result, spinner)
134
160
  }
135
161
 
136
- // Conclude the status of the API call
137
- const severityCount = getSeverityCount(result.data, includeAllIssues ? undefined : 'high')
162
+ // @ts-ignore
163
+ result.data.map(pkg => {
164
+ const severityCount = pkg.alerts && getCountSeverity(pkg.alerts, includeAlerts ? undefined : 'high')
165
+ pkg.severityCount = severityCount
166
+ return pkg
167
+ })
168
+
169
+ spinner.stop()
138
170
 
139
171
  return {
140
- data: result.data,
141
- severityCount,
142
- score: scoreResult.data
172
+ data: result.data
143
173
  }
144
174
  }
145
175
 
146
176
  /**
147
- * @param {PackageData} packageData
177
+ * @param {CommandContext} data
148
178
  * @param {{ name: string } & CommandContext} context
149
179
  * @param {import('ora').Ora} spinner
150
180
  * @returns {void}
151
181
  */
152
- function formatPackageDataOutput ({ data, severityCount, score }, { name, outputJson, outputMarkdown, pkgName, pkgVersion, strict }, spinner) {
182
+ function formatPackageDataOutput (/** @type {{ [key: string]: any }} */ { data }, { outputJson, outputMarkdown, strict }, spinner) {
153
183
  if (outputJson) {
154
184
  console.log(JSON.stringify(data, undefined, 2))
155
185
  } else {
156
- console.log('\nPackage report card:')
157
- const scoreResult = {
158
- 'Supply Chain Risk': Math.floor(score.supplyChainRisk.score * 100),
159
- 'Maintenance': Math.floor(score.maintenance.score * 100),
160
- 'Quality': Math.floor(score.quality.score * 100),
161
- 'Vulnerabilities': Math.floor(score.vulnerability.score * 100),
162
- 'License': Math.floor(score.license.score * 100)
163
- }
164
- Object.entries(scoreResult).map(score => console.log(`- ${score[0]}: ${formatScore(score[1])}`))
165
-
166
- // Package issues list
167
- if (objectSome(severityCount)) {
168
- const issueSummary = formatSeverityCount(severityCount)
169
- console.log('\n')
170
- spinner[strict ? 'fail' : 'succeed'](`Package has these issues: ${issueSummary}`)
171
- formatPackageIssuesDetails(data, outputMarkdown)
172
- } else {
173
- console.log('\n')
174
- spinner.succeed('Package has no issues')
175
- }
186
+ data.map((/** @type {{[key:string]: any}} */ d) => {
187
+ const { score, license, name, severityCount, version } = d
188
+ console.log(`\nPackage metrics for ${name}:`)
189
+
190
+ const scoreResult = {
191
+ 'Supply Chain Risk': Math.floor(score.supplyChain * 100),
192
+ 'Maintenance': Math.floor(score.maintenance * 100),
193
+ 'Quality': Math.floor(score.quality * 100),
194
+ 'Vulnerabilities': Math.floor(score.vulnerability * 100),
195
+ 'License': Math.floor(score.license * 100),
196
+ 'Overall': Math.floor(score.overall * 100)
197
+ }
176
198
 
177
- // Link to issues list
178
- const format = new ChalkOrMarkdown(!!outputMarkdown)
179
- const url = `https://socket.dev/npm/package/${pkgName}/overview/${pkgVersion}`
180
- if (pkgVersion === 'latest') {
181
- console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${pkgName}`, url, { fallbackToUrl: true }))
182
- } else {
183
- console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${pkgName} v${pkgVersion}`, url, { fallbackToUrl: true }))
184
- }
185
- if (!outputMarkdown) {
186
- console.log(chalk.dim('\nOr rerun', chalk.italic(name), 'using the', chalk.italic('--json'), 'flag to get full JSON output'))
187
- }
188
- }
199
+ Object.entries(scoreResult).map(score => console.log(`- ${score[0]}: ${formatScore(score[1])}`))
200
+
201
+ // Package license
202
+ console.log('\nPackage license:')
203
+ console.log(`${license}`)
204
+
205
+ // Package issues list
206
+ if (objectSome(severityCount)) {
207
+ const issueSummary = formatSeverityCount(severityCount)
208
+ console.log('\n')
209
+ spinner[strict ? 'fail' : 'succeed'](`Package has these issues: ${issueSummary}`)
210
+ formatPackageIssuesDetails(data.alerts, outputMarkdown)
211
+ } else if (severityCount && !objectSome(severityCount)) {
212
+ console.log('\n')
213
+ spinner.succeed('Package has no issues')
214
+ }
189
215
 
190
- if (strict && objectSome(severityCount)) {
191
- process.exit(1)
216
+ // Link to issues list
217
+ const format = new ChalkOrMarkdown(!!outputMarkdown)
218
+ const url = `https://socket.dev/npm/package/${name}/overview/${version}`
219
+ if (version === 'latest') {
220
+ console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${name}`, url, { fallbackToUrl: true }))
221
+ } else {
222
+ console.log('\nDetailed info on socket.dev: ' + format.hyperlink(`${name} v${version}`, url, { fallbackToUrl: true }))
223
+ }
224
+ if (!outputMarkdown) {
225
+ console.log(chalk.dim('\nOr rerun', chalk.italic(name), 'using the', chalk.italic('--json'), 'flag to get full JSON output'))
226
+ }
227
+
228
+ if (strict && objectSome(severityCount)) {
229
+ process.exit(1)
230
+ }
231
+ return d
232
+ })
192
233
  }
193
234
  }
194
235
 
195
236
  /**
196
- * @param {import('@socketsecurity/sdk').SocketSdkReturnType<'getIssuesByNPMPackage'>["data"]} packageData
237
+ * @param {{[key: string]: any}[]} alertsData
197
238
  * @param {boolean} outputMarkdown
198
239
  * @returns {void[]}
199
240
  */
200
- function formatPackageIssuesDetails (packageData, outputMarkdown) {
201
- const issueDetails = packageData.filter(d => d.value?.severity === 'high' || d.value?.severity === 'critical')
241
+ function formatPackageIssuesDetails (alertsData, outputMarkdown) {
242
+ const issueDetails = alertsData.filter(d => d['severity'] === 'high' || d['severity'] === 'critical')
202
243
 
203
244
  const uniqueIssues = issueDetails.reduce((/** @type {{ [key: string]: {count: Number, label: string | undefined} }} */ acc, issue) => {
204
245
  const { type } = issue
205
246
  if (type) {
206
247
  if (!acc[type]) {
207
248
  acc[type] = {
208
- label: issue.value?.label,
249
+ label: issue['type'],
209
250
  count: 1
210
251
  }
211
252
  } else {
@@ -217,6 +258,7 @@ function formatPackageIssuesDetails (packageData, outputMarkdown) {
217
258
  }, {})
218
259
 
219
260
  const format = new ChalkOrMarkdown(!!outputMarkdown)
261
+
220
262
  return Object.keys(uniqueIssues).map(issue => {
221
263
  const issueWithLink = format.hyperlink(`${uniqueIssues[issue]?.label}`, `https://socket.dev/npm/issue/${issue}`, { fallbackToUrl: true })
222
264
  if (uniqueIssues[issue]?.count === 1) {
@@ -12,7 +12,7 @@ import { getSetting, updateSetting } from '../../utils/settings.js'
12
12
 
13
13
  const description = 'Socket API login'
14
14
 
15
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
15
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
16
16
  export const login = {
17
17
  description,
18
18
  run: async (argv, importMeta, { parentName }) => {
@@ -5,7 +5,7 @@ import { updateSetting } from '../../utils/settings.js'
5
5
 
6
6
  const description = 'Socket API logout'
7
7
 
8
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
8
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
9
9
  export const logout = {
10
10
  description,
11
11
  run: async (argv, importMeta, { parentName }) => {
@@ -1,16 +1,21 @@
1
- import { spawn } from 'child_process'
1
+ import { spawn, execSync } from 'child_process'
2
2
  import { fileURLToPath } from 'url'
3
3
 
4
4
  const description = 'npm wrapper functionality'
5
5
 
6
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
6
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
7
7
  export const npm = {
8
8
  description,
9
9
  run: async (argv, _importMeta, _ctx) => {
10
+ const npmVersion = execSync('npm -v').toString()
10
11
  const wrapperPath = fileURLToPath(new URL('../../shadow/npm-cli.cjs', import.meta.url))
11
12
  process.exitCode = 1
12
13
  spawn(process.execPath, [wrapperPath, ...argv], {
13
- stdio: 'inherit'
14
+ stdio: 'inherit',
15
+ env: {
16
+ ...process.env,
17
+ NPM_VERSION: npmVersion
18
+ }
14
19
  }).on('exit', (code, signal) => {
15
20
  if (signal) {
16
21
  process.kill(process.pid, signal)
@@ -3,7 +3,7 @@ import { fileURLToPath } from 'url'
3
3
 
4
4
  const description = 'npx wrapper functionality'
5
5
 
6
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
6
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
7
7
  export const npx = {
8
8
  description,
9
9
  run: async (argv, _importMeta, _ctx) => {
@@ -19,7 +19,7 @@ import { createDebugLogger } from '../../utils/misc.js'
19
19
  import { getPackageFiles } from '../../utils/path-resolve.js'
20
20
  import { setupSdk } from '../../utils/sdk.js'
21
21
 
22
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
22
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
23
23
  export const create = {
24
24
  description: 'Create a project report',
25
25
  async run (argv, importMeta, { parentName }) {
@@ -4,7 +4,7 @@ import { meowWithSubcommands } from '../../utils/meow-with-subcommands.js'
4
4
 
5
5
  const description = 'Project report related commands'
6
6
 
7
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
7
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
8
8
  export const report = {
9
9
  description,
10
10
  run: async (argv, importMeta, { parentName }) => {
@@ -13,7 +13,7 @@ import { getSeverityCount, formatSeverityCount } from '../../utils/format-issues
13
13
  import { printFlagList } from '../../utils/formatting.js'
14
14
  import { setupSdk } from '../../utils/sdk.js'
15
15
 
16
- /** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
16
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
17
17
  export const view = {
18
18
  description: 'View a project report',
19
19
  async run (argv, importMeta, { parentName }) {
@@ -0,0 +1,166 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import chalk from 'chalk'
4
+ import meow from 'meow'
5
+ import ora from 'ora'
6
+
7
+ import { outputFlags } from '../../flags/index.js'
8
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
9
+ import { prepareFlags } from '../../utils/flags.js'
10
+ import { printFlagList } from '../../utils/formatting.js'
11
+ import { getDefaultKey, setupSdk } from '../../utils/sdk.js'
12
+
13
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
14
+ export const create = {
15
+ description: 'Create a repository in an organization',
16
+ async run (argv, importMeta, { parentName }) {
17
+ const name = parentName + ' create'
18
+
19
+ const input = setupCommand(name, create.description, argv, importMeta)
20
+ if (input) {
21
+ const spinnerText = 'Creating repository... \n'
22
+ const spinner = ora(spinnerText).start()
23
+ await createRepo(input.orgSlug, input, spinner)
24
+ }
25
+ }
26
+ }
27
+
28
+ const repositoryCreationFlags = prepareFlags({
29
+ repoName: {
30
+ type: 'string',
31
+ shortFlag: 'n',
32
+ default: '',
33
+ description: 'Repository name',
34
+ },
35
+ repoDescription: {
36
+ type: 'string',
37
+ shortFlag: 'd',
38
+ default: '',
39
+ description: 'Repository description',
40
+ },
41
+ homepage: {
42
+ type: 'string',
43
+ shortFlag: 'h',
44
+ default: '',
45
+ description: 'Repository url',
46
+ },
47
+ defaultBranch: {
48
+ type: 'string',
49
+ shortFlag: 'b',
50
+ default: 'main',
51
+ description: 'Repository default branch',
52
+ },
53
+ visibility: {
54
+ type: 'string',
55
+ shortFlag: 'v',
56
+ default: 'private',
57
+ description: 'Repository visibility (Default Private)',
58
+ }
59
+ })
60
+
61
+ // Internal functions
62
+
63
+ /**
64
+ * @typedef CommandContext
65
+ * @property {boolean} outputJson
66
+ * @property {boolean} outputMarkdown
67
+ * @property {string} orgSlug
68
+ * @property {string} name
69
+ * @property {string} description
70
+ * @property {string} homepage
71
+ * @property {string} default_branch
72
+ * @property {string} visibility
73
+ */
74
+
75
+ /**
76
+ * @param {string} name
77
+ * @param {string} description
78
+ * @param {readonly string[]} argv
79
+ * @param {ImportMeta} importMeta
80
+ * @returns {void|CommandContext}
81
+ */
82
+ function setupCommand (name, description, argv, importMeta) {
83
+ const flags = {
84
+ ...outputFlags,
85
+ ...repositoryCreationFlags
86
+ }
87
+
88
+ const cli = meow(`
89
+ Usage
90
+ $ ${name} <org slug>
91
+
92
+ Options
93
+ ${printFlagList(flags, 6)}
94
+
95
+ Examples
96
+ $ ${name} FakeOrg --repoName=test-repo
97
+ `, {
98
+ argv,
99
+ description,
100
+ importMeta,
101
+ flags
102
+ })
103
+
104
+ const {
105
+ json: outputJson,
106
+ markdown: outputMarkdown,
107
+ repoName,
108
+ repoDescription,
109
+ homepage,
110
+ defaultBranch,
111
+ visibility
112
+ } = cli.flags
113
+
114
+ const [orgSlug = ''] = cli.input
115
+
116
+ if (!orgSlug) {
117
+ console.error(`${chalk.bgRed('Input error')}: Please provide an organization slug \n`)
118
+ cli.showHelp()
119
+ return
120
+ }
121
+
122
+ if (!repoName) {
123
+ console.error(`${chalk.bgRed('Input error')}: Repository name is required. \n`)
124
+ cli.showHelp()
125
+ return
126
+ }
127
+
128
+ return {
129
+ outputJson,
130
+ outputMarkdown,
131
+ orgSlug,
132
+ name: repoName,
133
+ description: repoDescription,
134
+ homepage,
135
+ default_branch: defaultBranch,
136
+ visibility
137
+ }
138
+ }
139
+
140
+ /**
141
+ * @typedef RepositoryData
142
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'createOrgRepo'>["data"]} data
143
+ */
144
+
145
+ /**
146
+ * @param {string} orgSlug
147
+ * @param {CommandContext} input
148
+ * @param {import('ora').Ora} spinner
149
+ * @returns {Promise<void|RepositoryData>}
150
+ */
151
+ async function createRepo (orgSlug, input, spinner) {
152
+ const socketSdk = await setupSdk(getDefaultKey())
153
+ const result = await handleApiCall(socketSdk.createOrgRepo(orgSlug, input), 'creating repository')
154
+
155
+ if (!result.success) {
156
+ return handleUnsuccessfulApiResponse('createOrgRepo', result, spinner)
157
+ }
158
+
159
+ spinner.stop()
160
+
161
+ console.log('\n✅ Repository created successfully \n')
162
+
163
+ return {
164
+ data: result.data
165
+ }
166
+ }
@@ -0,0 +1,93 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import chalk from 'chalk'
4
+ import meow from 'meow'
5
+ import ora from 'ora'
6
+
7
+ import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
8
+ import { getDefaultKey, setupSdk } from '../../utils/sdk.js'
9
+
10
+ /** @type {import('../../utils/meow-with-subcommands.js').CliSubcommand} */
11
+ export const del = {
12
+ description: 'Delete a repository in an organization',
13
+ async run (argv, importMeta, { parentName }) {
14
+ const name = parentName + ' del'
15
+
16
+ const input = setupCommand(name, del.description, argv, importMeta)
17
+ if (input) {
18
+ const spinnerText = 'Deleting repository... \n'
19
+ const spinner = ora(spinnerText).start()
20
+ await deleteRepository(input.orgSlug, input.repoName, spinner)
21
+ }
22
+ }
23
+ }
24
+
25
+ // Internal functions
26
+
27
+ /**
28
+ * @typedef CommandContext
29
+ * @property {string} orgSlug
30
+ * @property {string} repoName
31
+ */
32
+
33
+ /**
34
+ * @param {string} name
35
+ * @param {string} description
36
+ * @param {readonly string[]} argv
37
+ * @param {ImportMeta} importMeta
38
+ * @returns {void|CommandContext}
39
+ */
40
+ function setupCommand (name, description, argv, importMeta) {
41
+ const cli = meow(`
42
+ Usage
43
+ $ ${name} <org slug> <repo slug>
44
+
45
+ Examples
46
+ $ ${name} FakeOrg test-repo
47
+ `, {
48
+ argv,
49
+ description,
50
+ importMeta
51
+ })
52
+
53
+ const [orgSlug = '', repoName = ''] = cli.input
54
+
55
+ if (!orgSlug || !repoName) {
56
+ console.error(`${chalk.bgRed('Input error')}: Please provide an organization slug and repository slug \n`)
57
+ cli.showHelp()
58
+ return
59
+ }
60
+
61
+ return {
62
+ orgSlug,
63
+ repoName
64
+ }
65
+ }
66
+
67
+ /**
68
+ * @typedef RepositoryData
69
+ * @property {import('@socketsecurity/sdk').SocketSdkReturnType<'deleteOrgRepo'>["data"]} data
70
+ */
71
+
72
+ /**
73
+ * @param {string} orgSlug
74
+ * @param {string} repoName
75
+ * @param {import('ora').Ora} spinner
76
+ * @returns {Promise<void|RepositoryData>}
77
+ */
78
+ async function deleteRepository (orgSlug, repoName, spinner) {
79
+ const socketSdk = await setupSdk(getDefaultKey())
80
+ const result = await handleApiCall(socketSdk.deleteOrgRepo(orgSlug, repoName), 'deleting repository')
81
+
82
+ if (!result.success) {
83
+ return handleUnsuccessfulApiResponse('deleteOrgRepo', result, spinner)
84
+ }
85
+
86
+ spinner.stop()
87
+
88
+ console.log('\n✅ Repository deleted successfully \n')
89
+
90
+ return {
91
+ data: result.data
92
+ }
93
+ }