npq 3.23.3 → 3.25.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.
package/lib/cli.js CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { parseArgs } = require('node:util')
4
4
  const npa = require('npm-package-arg')
5
+ const { parseJsonRegistryPackageSpec } = require('./helpers/jsonPackageSpec')
6
+ const { isCodingAgentEnvironment } = require('./helpers/codingAgentEnvironment')
5
7
  const pkg = require('../package.json')
6
8
  const { parsePackageManagerArguments } = require('./helpers/packageManagerArgumentParser')
7
9
 
@@ -45,7 +47,7 @@ class CliParser {
45
47
  process.exit(exitCode)
46
48
  }
47
49
 
48
- static _extractPackagesFromPositionals(positionals, earlyExitNoInstall = false) {
50
+ static _extractPackagesFromPositionals(positionals, earlyExitNoInstall = false, json = false) {
49
51
  let packages = []
50
52
  if (positionals.length > 0) {
51
53
  const command = positionals[0]
@@ -61,7 +63,7 @@ class CliParser {
61
63
 
62
64
  // Parse and normalize packages
63
65
  return packages.map((pkg) => {
64
- const parsedPackage = npa(pkg)
66
+ const parsedPackage = json ? parseJsonRegistryPackageSpec(pkg) : npa(pkg)
65
67
  const versionModifier = parsedPackage.fetchSpec === '*' ? 'latest' : parsedPackage.fetchSpec
66
68
  return `${parsedPackage.name}@${versionModifier}`
67
69
  })
@@ -71,6 +73,7 @@ class CliParser {
71
73
  const options = {
72
74
  'dry-run': { type: 'boolean' },
73
75
  plain: { type: 'boolean' },
76
+ json: { type: 'boolean' },
74
77
  packageManager: { type: 'string' },
75
78
  pkgMgr: { type: 'string' },
76
79
  'disable-auto-continue': { type: 'boolean' },
@@ -102,6 +105,7 @@ Commands:
102
105
  Options:
103
106
  --dry-run Run checks only, don't install
104
107
  --plain Force non-rich text output
108
+ --json Emit JSON and never install
105
109
  --packageManager Package Manager to use (default: npm)
106
110
  --pkgMgr Alias for packageManager
107
111
  --disable-auto-continue Disable auto-continue countdown, always prompt
@@ -129,7 +133,8 @@ curated by Liran Tal at https://github.com/lirantal/npq`)
129
133
  }
130
134
 
131
135
  // Process install command and packages
132
- const normalizedPackages = this._extractPackagesFromPositionals(positionals)
136
+ const json = values.json === true || isCodingAgentEnvironment()
137
+ const normalizedPackages = this._extractPackagesFromPositionals(positionals, false, json)
133
138
  const installSubcommandExplicit =
134
139
  positionals.length > 0 && this.isInstallSubcommand(positionals[0])
135
140
 
@@ -138,6 +143,7 @@ curated by Liran Tal at https://github.com/lirantal/npq`)
138
143
  packageManager: process.env.NPQ_PKG_MGR || values.packageManager || values.pkgMgr || 'npm',
139
144
  dryRun: values['dry-run'] || false,
140
145
  plain: values.plain || false,
146
+ json,
141
147
  disableAutoContinue:
142
148
  values['disable-auto-continue'] || process.env.NPQ_DISABLE_AUTO_CONTINUE === 'true',
143
149
  registryConfigArgs: this.registryConfigArgs(values),
@@ -145,19 +151,31 @@ curated by Liran Tal at https://github.com/lirantal/npq`)
145
151
  }
146
152
  }
147
153
 
148
- static parseArgsMinimal() {
154
+ static parseArgsMinimal({ codingAgentEnvironment = isCodingAgentEnvironment() } = {}) {
149
155
  const packageManager = process.env.NPQ_PKG_MGR || 'npm'
150
156
  const { values, positionals } = parsePackageManagerArguments({
151
157
  packageManager,
152
158
  args: process.argv.slice(2)
153
159
  })
154
-
155
- const earlyExitNoInstall = true
156
- const normalizedPackages = this._extractPackagesFromPositionals(positionals, earlyExitNoInstall)
160
+ const installSubcommandExplicit =
161
+ positionals.length > 0 && this.isInstallSubcommand(positionals[0])
162
+ const json = codingAgentEnvironment && installSubcommandExplicit
163
+ let normalizedPackages
164
+
165
+ try {
166
+ normalizedPackages = this._extractPackagesFromPositionals(positionals, true, json)
167
+ } catch (error) {
168
+ if (json && error && typeof error === 'object') {
169
+ Object.defineProperty(error, 'npqJsonMode', { value: true })
170
+ }
171
+ throw error
172
+ }
157
173
 
158
174
  return {
159
175
  packages: normalizedPackages,
160
- registryConfigArgs: this.registryConfigArgs(values)
176
+ registryConfigArgs: this.registryConfigArgs(values),
177
+ installSubcommandExplicit,
178
+ json
161
179
  }
162
180
  }
163
181
  }
@@ -0,0 +1,25 @@
1
+ 'use strict'
2
+
3
+ const AUDIT_FAILURE_CODES = Object.freeze({
4
+ INVALID_INPUT: 'INVALID_INPUT',
5
+ PROJECT_MANIFEST_ERROR: 'PROJECT_MANIFEST_ERROR',
6
+ PACKAGE_LOOKUP_FAILED: 'PACKAGE_LOOKUP_FAILED',
7
+ AUDIT_CHECK_FAILED: 'AUDIT_CHECK_FAILED',
8
+ INTERRUPTED: 'INTERRUPTED',
9
+ INTERNAL_ERROR: 'INTERNAL_ERROR'
10
+ })
11
+
12
+ const knownCodes = new Set(Object.values(AUDIT_FAILURE_CODES))
13
+
14
+ function createAuditFailure(code, message, context = {}) {
15
+ const failure = {
16
+ code: knownCodes.has(code) ? code : AUDIT_FAILURE_CODES.INTERNAL_ERROR,
17
+ message: String(message)
18
+ }
19
+
20
+ if (context.package) failure.package = String(context.package)
21
+ if (context.marshall) failure.marshall = String(context.marshall)
22
+ return failure
23
+ }
24
+
25
+ module.exports = { AUDIT_FAILURE_CODES, createAuditFailure }
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ const CODING_AGENT_ENVIRONMENT_VARIABLES = Object.freeze([
4
+ 'CLAUDECODE',
5
+ 'CLAUDE_CODE_CHILD_SESSION',
6
+ 'CURSOR_AGENT',
7
+ 'PI_CODING_AGENT',
8
+ 'CODEX_SANDBOX',
9
+ 'CODEX_THREAD_ID',
10
+ 'GEMINI_CLI',
11
+ 'WINDSURF_AGENT',
12
+ 'CODEIUM_AGENT',
13
+ 'AGENT',
14
+ 'AI_AGENT'
15
+ ])
16
+
17
+ function isCodingAgentEnvironment(env = process.env) {
18
+ return CODING_AGENT_ENVIRONMENT_VARIABLES.some(
19
+ (name) => typeof env[name] === 'string' && env[name].length > 0
20
+ )
21
+ }
22
+
23
+ module.exports = { CODING_AGENT_ENVIRONMENT_VARIABLES, isCodingAgentEnvironment }
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ function createJsonOutput(write = (value, callback) => process.stdout.write(value, callback)) {
4
+ let written = false
5
+
6
+ return {
7
+ write(report, onComplete) {
8
+ if (written) return false
9
+ written = true
10
+ const serialized = `${JSON.stringify(report)}\n`
11
+ if (typeof onComplete === 'function') {
12
+ write(serialized, onComplete)
13
+ } else {
14
+ write(serialized)
15
+ }
16
+ return true
17
+ },
18
+ hasWritten() {
19
+ return written
20
+ }
21
+ }
22
+ }
23
+
24
+ module.exports = { createJsonOutput }
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+
3
+ const npa = require('npm-package-arg')
4
+
5
+ const JSON_REGISTRY_SPEC_TYPES = new Set(['tag', 'version', 'range'])
6
+
7
+ function parseJsonRegistryPackageSpec(packageSpec) {
8
+ try {
9
+ const parsedPackage = npa(packageSpec)
10
+ if (!parsedPackage.name || !JSON_REGISTRY_SPEC_TYPES.has(parsedPackage.type)) {
11
+ throw new Error('Unsupported package spec')
12
+ }
13
+
14
+ return parsedPackage
15
+ } catch {
16
+ throw new Error('Invalid JSON package input')
17
+ }
18
+ }
19
+
20
+ function isJsonRegistryPackageSpec(packageSpec) {
21
+ try {
22
+ parseJsonRegistryPackageSpec(packageSpec)
23
+ return true
24
+ } catch {
25
+ return false
26
+ }
27
+ }
28
+
29
+ module.exports = { isJsonRegistryPackageSpec, parseJsonRegistryPackageSpec }
@@ -0,0 +1,76 @@
1
+ 'use strict'
2
+
3
+ const pkg = require('../../package.json')
4
+ const { marshallCategories } = require('../marshalls/constants')
5
+ const { createAuditFailure } = require('./auditFailure')
6
+
7
+ function normalizeMarshallGroup(group = {}) {
8
+ const findings = []
9
+
10
+ for (const value of Object.values(group)) {
11
+ if (!value || typeof value !== 'object') continue
12
+ const category = marshallCategories[value.categoryId] || {
13
+ id: value.categoryId,
14
+ title: value.categoryId
15
+ }
16
+
17
+ for (const warning of value.warnings || []) {
18
+ findings.push({
19
+ severity: 'warning',
20
+ marshall: value.marshall,
21
+ category: { id: category.id, title: category.title },
22
+ message: warning.message
23
+ })
24
+ }
25
+ for (const error of value.errors || []) {
26
+ findings.push({
27
+ severity: 'error',
28
+ marshall: value.marshall,
29
+ category: { id: category.id, title: category.title },
30
+ message: error.message
31
+ })
32
+ }
33
+ }
34
+
35
+ return findings
36
+ }
37
+
38
+ function buildJsonReport({
39
+ packages = [],
40
+ marshallResults = {},
41
+ failures = [],
42
+ version = pkg.version
43
+ } = {}) {
44
+ const positionalResults = Array.isArray(marshallResults)
45
+ const packageReports = packages.map((requested, index) => {
46
+ const results = positionalResults ? marshallResults[index] : marshallResults[requested]
47
+ return {
48
+ requested,
49
+ findings: (results || []).flatMap(normalizeMarshallGroup)
50
+ }
51
+ })
52
+ const findings = packageReports.flatMap((entry) => entry.findings)
53
+ const errors = findings.filter((item) => item.severity === 'error').length
54
+ const warnings = findings.filter((item) => item.severity === 'warning').length
55
+ const status = failures.length > 0 ? 'failed' : errors + warnings > 0 ? 'findings' : 'clean'
56
+
57
+ return {
58
+ schemaVersion: 1,
59
+ tool: { name: 'npq', version },
60
+ status,
61
+ summary: { packagesAudited: packageReports.length, errors, warnings },
62
+ packages: packageReports,
63
+ failures: failures.map((failure) =>
64
+ createAuditFailure(failure.code, failure.message, {
65
+ package: failure.package,
66
+ marshall: failure.marshall
67
+ })
68
+ )
69
+ }
70
+ }
71
+
72
+ function exitCodeForJsonReport(report) {
73
+ return report.status === 'failed' ? 2 : report.status === 'findings' ? 1 : 0
74
+ }
75
+
76
+ module.exports = { buildJsonReport, exitCodeForJsonReport }
@@ -16,8 +16,7 @@ const CAPABILITY = Object.freeze({
16
16
  const CAPABILITY_MESSAGE = Object.freeze({
17
17
  [CAPABILITY.SIGNING_KEYS]: 'configured registry does not expose signing keys',
18
18
  [CAPABILITY.ATTESTATIONS]: 'configured registry does not expose attestations',
19
- [CAPABILITY.DOWNLOAD_COUNTS]:
20
- 'download counts are available only for the public npm registry'
19
+ [CAPABILITY.DOWNLOAD_COUNTS]: 'download counts are available only for the public npm registry'
21
20
  })
22
21
 
23
22
  class RegistryClient {
@@ -47,11 +46,7 @@ class RegistryClient {
47
46
  }
48
47
  }
49
48
 
50
- async requestJson(
51
- requestPath,
52
- packageSpec,
53
- { capability = null, notFoundAsData = false } = {}
54
- ) {
49
+ async requestJson(requestPath, packageSpec, { capability = null, notFoundAsData = false } = {}) {
55
50
  const registry = this.registryFor(packageSpec)
56
51
  try {
57
52
  return await this.fetcher.json(requestPath, this.requestOptions(packageSpec))
@@ -3,12 +3,7 @@
3
3
  const path = require('node:path')
4
4
  const Config = require('@npmcli/config')
5
5
  const npmFetch = require('npm-registry-fetch')
6
- const {
7
- definitions,
8
- flatten,
9
- nerfDarts,
10
- shorthands
11
- } = require('@npmcli/config/lib/definitions')
6
+ const { definitions, flatten, nerfDarts, shorthands } = require('@npmcli/config/lib/definitions')
12
7
  const { RegistryError, sanitizeRegistryUrl } = require('./registryErrors')
13
8
 
14
9
  const DEFAULT_REGISTRY = 'https://registry.npmjs.org/'
@@ -57,8 +52,7 @@ class RegistryConfig {
57
52
  }
58
53
  return new RegistryConfig(config.flat)
59
54
  } catch (error) {
60
- const code =
61
- error.code === 'ERR_INVALID_AUTH' ? 'EREGISTRYCONFIGAUTH' : 'EREGISTRYCONFIG'
55
+ const code = error.code === 'ERR_INVALID_AUTH' ? 'EREGISTRYCONFIGAUTH' : 'EREGISTRYCONFIG'
62
56
  const message =
63
57
  error.code === 'ERR_INVALID_AUTH'
64
58
  ? 'Invalid npm registry authentication configuration'
@@ -230,15 +230,9 @@ function reportResults(marshallResults, { plain = false } = {}) {
230
230
  entry.categoryFriendlyName.padEnd(maxTextLength, ' ')
231
231
  )
232
232
  const iconRich = styleText(['cyan'], '○')
233
- const wrappedMessage = wrapTextWithIndent(
234
- entry.message,
235
- maxMessageWidth,
236
- indentString
237
- )
238
- resultsForPrettyPrint +=
239
- `\n ${prefixRich} ${iconRich} ${topicRich} ${separatorRich} ${wrappedMessage} `
240
- resultsForPlainTextPrint +=
241
- `\n NOT EVALUATED: ${entry.categoryFriendlyName} - ${entry.message}`
233
+ const wrappedMessage = wrapTextWithIndent(entry.message, maxMessageWidth, indentString)
234
+ resultsForPrettyPrint += `\n ${prefixRich} ${iconRich} ${topicRich} ${separatorRich} ${wrappedMessage} `
235
+ resultsForPlainTextPrint += `\n NOT EVALUATED: ${entry.categoryFriendlyName} - ${entry.message}`
242
236
  })
243
237
  })
244
238
  }
@@ -256,16 +250,11 @@ function reportResults(marshallResults, { plain = false } = {}) {
256
250
  terminalWidth - getDisplayLength(' │ ℹ Suggested older release · ') - 2
257
251
  )
258
252
  const detailWidth = Math.max(20, terminalWidth - getDisplayLength(' │ ') - 2)
259
- const wrappedHeadline = wrapTextWithIndent(
260
- headline,
261
- headlineWidth,
262
- suggestionIndent
263
- )
253
+ const wrappedHeadline = wrapTextWithIndent(headline, headlineWidth, suggestionIndent)
264
254
  const wrappedDetail = wrapTextWithIndent(detail, detailWidth, suggestionIndent)
265
255
 
266
256
  resultsForPrettyPrint += `\n ${prefixRich}`
267
- resultsForPrettyPrint +=
268
- `\n ${prefixRich} ${iconRich} ${labelRich} ${separatorRich} ${wrappedHeadline}`
257
+ resultsForPrettyPrint += `\n ${prefixRich} ${iconRich} ${labelRich} ${separatorRich} ${wrappedHeadline}`
269
258
  resultsForPrettyPrint += `\n${suggestionIndent}${wrappedDetail}`
270
259
 
271
260
  resultsForPlainTextPrint +=
@@ -23,12 +23,7 @@ function findAlternativeVersion({
23
23
  requestedVersion,
24
24
  now = Date.now()
25
25
  }) {
26
- if (
27
- !packageInfo ||
28
- !packageInfo.versions ||
29
- !packageInfo.time ||
30
- !semver.valid(targetVersion)
31
- ) {
26
+ if (!packageInfo || !packageInfo.versions || !packageInfo.time || !semver.valid(targetVersion)) {
32
27
  return null
33
28
  }
34
29
 
@@ -49,9 +44,7 @@ function findAlternativeVersion({
49
44
  })
50
45
  .filter(
51
46
  ({ publishedAt, ageDays }) =>
52
- typeof publishedAt === 'string' &&
53
- ageDays !== null &&
54
- ageDays > MAX_VERSION_RECENCY_DAYS
47
+ typeof publishedAt === 'string' && ageDays !== null && ageDays > MAX_VERSION_RECENCY_DAYS
55
48
  )
56
49
  .sort((left, right) => semver.rcompare(left.version, right.version))
57
50
 
@@ -0,0 +1,71 @@
1
+ 'use strict'
2
+
3
+ const { getProjectPackages } = require('./helpers/sourcePackages')
4
+ const Marshall = require('./marshall')
5
+ const { promiseThrottleHelper } = require('./helpers/promiseThrottler')
6
+ const { buildJsonReport } = require('./helpers/jsonReport')
7
+ const { AUDIT_FAILURE_CODES, createAuditFailure } = require('./helpers/auditFailure')
8
+ const { isJsonRegistryPackageSpec } = require('./helpers/jsonPackageSpec')
9
+ const RegistryConfig = require('./helpers/registryConfig')
10
+ const RegistryClient = require('./helpers/registryClient')
11
+
12
+ async function runJsonAudit(cliArgs, dependencies = {}) {
13
+ const getPackages = dependencies.getProjectPackages || getProjectPackages
14
+ const MarshallClass = dependencies.Marshall || Marshall
15
+ const throttle = dependencies.promiseThrottleHelper || promiseThrottleHelper
16
+ const RegistryConfigClass = dependencies.RegistryConfig || RegistryConfig
17
+ const RegistryClientClass = dependencies.RegistryClient || RegistryClient
18
+ let packages = cliArgs.packages || []
19
+ const failures = []
20
+
21
+ try {
22
+ if (packages.length === 0) {
23
+ const projectPackages = await getPackages()
24
+ if (projectPackages && projectPackages.error) {
25
+ return buildJsonReport({
26
+ failures: [
27
+ createAuditFailure(
28
+ AUDIT_FAILURE_CODES.PROJECT_MANIFEST_ERROR,
29
+ 'Unable to read project package.json'
30
+ )
31
+ ]
32
+ })
33
+ }
34
+ packages = projectPackages
35
+ }
36
+
37
+ if (!packages.every(isJsonRegistryPackageSpec)) {
38
+ return buildJsonReport({
39
+ failures: [
40
+ createAuditFailure(
41
+ AUDIT_FAILURE_CODES.INVALID_INPUT,
42
+ 'Invalid package or option argument'
43
+ )
44
+ ]
45
+ })
46
+ }
47
+
48
+ const registryConfig = await RegistryConfigClass.load({
49
+ argv: cliArgs.registryConfigArgs || []
50
+ })
51
+ const registryClient = new RegistryClientClass(registryConfig)
52
+ const marshall = new MarshallClass({
53
+ pkgs: packages,
54
+ progressManager: null,
55
+ promiseThrottleHelper: throttle,
56
+ registryClient,
57
+ preserveRequestOrder: true,
58
+ suppressOutput: true,
59
+ onAuditFailure: (failure) => failures.push(failure)
60
+ })
61
+ const marshallResults = await marshall.process()
62
+ return buildJsonReport({ packages, marshallResults, failures })
63
+ } catch {
64
+ failures.push(
65
+ createAuditFailure(AUDIT_FAILURE_CODES.INTERNAL_ERROR, 'JSON audit could not complete')
66
+ )
67
+ return buildJsonReport({ packages, failures })
68
+ }
69
+ }
70
+
71
+ module.exports = { runJsonAudit }
package/lib/jsonCli.js ADDED
@@ -0,0 +1,61 @@
1
+ 'use strict'
2
+
3
+ const { createJsonOutput } = require('./helpers/jsonOutput')
4
+ const { buildJsonReport, exitCodeForJsonReport } = require('./helpers/jsonReport')
5
+ const { AUDIT_FAILURE_CODES, createAuditFailure } = require('./helpers/auditFailure')
6
+ const { runJsonAudit } = require('./jsonAudit')
7
+
8
+ function writeInvalidJsonInvocation(output = createJsonOutput()) {
9
+ const report = buildJsonReport({
10
+ failures: [
11
+ createAuditFailure(AUDIT_FAILURE_CODES.INVALID_INPUT, 'Invalid package or option argument')
12
+ ]
13
+ })
14
+ output.write(report)
15
+ return report
16
+ }
17
+
18
+ async function runJsonCli(cliArgs, dependencies = {}) {
19
+ const output = dependencies.output || createJsonOutput()
20
+ const processTarget = dependencies.processTarget || process
21
+ const audit = dependencies.runJsonAudit || runJsonAudit
22
+ const getExitCode = dependencies.exitCodeForJsonReport || exitCodeForJsonReport
23
+ let interrupted = false
24
+ let interruptedReport
25
+
26
+ const interrupt = () => {
27
+ interrupted = true
28
+ interruptedReport = buildJsonReport({
29
+ packages: cliArgs.packages || [],
30
+ failures: [createAuditFailure(AUDIT_FAILURE_CODES.INTERRUPTED, 'Audit interrupted')]
31
+ })
32
+ output.write(interruptedReport, () => {
33
+ processTarget.exit(2)
34
+ })
35
+ }
36
+
37
+ processTarget.once('SIGINT', interrupt)
38
+ let report
39
+ try {
40
+ report = await audit(cliArgs)
41
+ } catch {
42
+ report = buildJsonReport({
43
+ packages: cliArgs.packages || [],
44
+ failures: [
45
+ createAuditFailure(AUDIT_FAILURE_CODES.INTERNAL_ERROR, 'JSON audit could not complete')
46
+ ]
47
+ })
48
+ } finally {
49
+ processTarget.removeListener('SIGINT', interrupt)
50
+ }
51
+
52
+ if (interrupted) {
53
+ return interruptedReport
54
+ }
55
+
56
+ output.write(report)
57
+ processTarget.exitCode = getExitCode(report)
58
+ return report
59
+ }
60
+
61
+ module.exports = { runJsonCli, writeInvalidJsonInvocation }
package/lib/marshall.js CHANGED
@@ -11,14 +11,15 @@ class Marshall {
11
11
  options = options || {}
12
12
  this.pkgs = options.pkgs || null
13
13
  this.registryClient =
14
- options.registryClient ||
15
- options.packageRepoUtils?.registryClient ||
16
- RegistryClient.public()
14
+ options.registryClient || options.packageRepoUtils?.registryClient || RegistryClient.public()
17
15
  this.packageRepoUtils =
18
- options.packageRepoUtils ||
19
- new PackageRepoUtils({ registryClient: this.registryClient })
16
+ options.packageRepoUtils || new PackageRepoUtils({ registryClient: this.registryClient })
20
17
  this.progressManager = options.progressManager || null
21
18
  this.promiseThrottleHelper = options.promiseThrottleHelper || null
19
+ this.preserveRequestOrder = options.preserveRequestOrder === true
20
+ this.suppressOutput = options.suppressOutput === true
21
+ this.onAuditFailure =
22
+ typeof options.onAuditFailure === 'function' ? options.onAuditFailure : null
22
23
  }
23
24
 
24
25
  async process() {
@@ -57,6 +58,7 @@ class Marshall {
57
58
  this.progressManager.stop()
58
59
  }
59
60
 
61
+ if (this.preserveRequestOrder) return res
60
62
  return promiseResultsPerPackage
61
63
  }
62
64
 
@@ -67,7 +69,10 @@ class Marshall {
67
69
  packageRepoUtils: packageRepoUtils,
68
70
  registryClient: this.registryClient
69
71
  }
70
- return Marshalls.tasks(config, this.progressManager)
72
+ return Marshalls.tasks(config, this.progressManager, {
73
+ onAuditFailure: this.onAuditFailure,
74
+ suppressOutput: this.suppressOutput
75
+ })
71
76
  }
72
77
 
73
78
  createPackageVersionMaps(packages) {
@@ -9,7 +9,7 @@ const { marshallCategories } = require('./constants')
9
9
 
10
10
  class BaseMarshall {
11
11
  constructor(options) {
12
- this.debug = util.debuglog('npq')
12
+ this.debug = options.suppressOutput ? () => {} : util.debuglog('npq')
13
13
  this.packageRepoUtils = options.packageRepoUtils
14
14
  this.registryClient = options.registryClient
15
15
  this.categoryId = marshallCategories.PackageHealth.id
@@ -2,6 +2,7 @@
2
2
 
3
3
  const fs = require('node:fs')
4
4
  const path = require('node:path')
5
+ const { AUDIT_FAILURE_CODES, createAuditFailure } = require('../helpers/auditFailure')
5
6
  const { RegistryError } = require('../helpers/registryErrors')
6
7
 
7
8
  class Marshalls {
@@ -22,7 +23,8 @@ class Marshalls {
22
23
 
23
24
  const config = {
24
25
  packageRepoUtils: options.packageRepoUtils,
25
- registryClient: options.registryClient
26
+ registryClient: options.registryClient,
27
+ suppressOutput: options.suppressOutput === true
26
28
  }
27
29
 
28
30
  const marshallTasks = marshalls.reduce((prev, curr) => {
@@ -36,6 +38,7 @@ class Marshalls {
36
38
 
37
39
  return prev.concat({
38
40
  title: marshall.title(),
41
+ name: marshall.name,
39
42
  categoryId: marshall.categoryId,
40
43
  execute: async (options) => {
41
44
  const ctx = {
@@ -61,7 +64,11 @@ class Marshalls {
61
64
  * @param {Object} [progressManager] - Optional progress manager for displaying audit progress updates
62
65
  * @returns {Promise<Array<Object>>} Array of marshall results containing audit findings for each package
63
66
  */
64
- static async tasks(options, progressManager) {
67
+ static async tasks(
68
+ options,
69
+ progressManager,
70
+ { onAuditFailure = null, suppressOutput = false } = {}
71
+ ) {
65
72
  const marshallResults = []
66
73
 
67
74
  // @TODO handle throw error because fetch didn't work due to network issues
@@ -78,7 +85,23 @@ class Marshalls {
78
85
  // hostname: 'registry.npmjs.org'
79
86
  // }
80
87
  // }
81
- const packagesDataList = await Marshalls.warmUpPackagesCache(options)
88
+ let packagesDataList
89
+ try {
90
+ packagesDataList = await Marshalls.warmUpPackagesCache(options)
91
+ } catch (error) {
92
+ if (!onAuditFailure) throw error
93
+
94
+ for (const pkg of options.pkgs) {
95
+ onAuditFailure(
96
+ createAuditFailure(
97
+ AUDIT_FAILURE_CODES.PACKAGE_LOOKUP_FAILED,
98
+ 'Unable to retrieve package metadata',
99
+ { package: pkg.packageString }
100
+ )
101
+ )
102
+ }
103
+ return marshallResults
104
+ }
82
105
 
83
106
  // handle error in case we get just one package and it is not found
84
107
  // if (packagesDataList && packagesDataList.length === 1) {
@@ -123,7 +146,8 @@ class Marshalls {
123
146
  if (packagesToProcess.length > 0) {
124
147
  const marshallTasks = await Marshalls.buildMarshallTasks(marshalls, {
125
148
  packageRepoUtils: options.packageRepoUtils,
126
- registryClient: options.registryClient
149
+ registryClient: options.registryClient,
150
+ suppressOutput
127
151
  })
128
152
 
129
153
  for (const marshall of marshallTasks) {
@@ -134,10 +158,23 @@ class Marshalls {
134
158
  })
135
159
  marshallResults.push(res.marshalls)
136
160
  } catch (error) {
137
- if (error instanceof RegistryError) {
138
- throw error
161
+ if (!onAuditFailure) {
162
+ if (error instanceof RegistryError) {
163
+ throw error
164
+ }
165
+ console.error(`Error running task ${marshall.title}:`, error)
166
+ continue
167
+ }
168
+
169
+ for (const pkg of options.pkgs) {
170
+ onAuditFailure(
171
+ createAuditFailure(
172
+ AUDIT_FAILURE_CODES.AUDIT_CHECK_FAILED,
173
+ 'Audit check could not complete',
174
+ { package: pkg.packageString, marshall: marshall.name }
175
+ )
176
+ )
139
177
  }
140
- console.error(`Error running task ${marshall.title}:`, error)
141
178
  }
142
179
  }
143
180
  }