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/README.md CHANGED
@@ -222,6 +222,23 @@ npq install express --dry-run
222
222
  npq install express --plain
223
223
  ```
224
224
 
225
+ ### Machine-readable audits
226
+
227
+ Use `--json` for a non-interactive, audit-only result suitable for CI:
228
+
229
+ ```sh
230
+ npq install express --json
231
+ ```
232
+
233
+ When npq detects a supported coding-agent environment, it automatically uses
234
+ the same audit-only JSON mode. This applies to `npq` and to install commands
235
+ intercepted by `npq-hero`; non-install `npq-hero` commands still pass through to
236
+ the configured package manager. See [JSON audit output](docs/feature/json-output.md#coding-agent-detection).
237
+
238
+ JSON mode never invokes the package manager. It exits `0` for a clean audit,
239
+ `1` for findings, and `2` when the audit cannot complete. See
240
+ [JSON audit output](docs/feature/json-output.md).
241
+
225
242
  ### Disable auto-continue countdown
226
243
 
227
244
  By default, when npq detects only warnings (no errors), it automatically proceeds with installation after a 15-second countdown. To disable this behavior and always require explicit confirmation:
package/bin/npq-hero.js CHANGED
@@ -14,133 +14,142 @@ const { Spinner } = require('../lib/helpers/cliSpinner')
14
14
  const { promiseThrottleHelper } = require('../lib/helpers/promiseThrottler')
15
15
  const RegistryConfig = require('../lib/helpers/registryConfig')
16
16
  const RegistryClient = require('../lib/helpers/registryClient')
17
+ const { createJsonOutput } = require('../lib/helpers/jsonOutput')
18
+ const { runJsonCli, writeInvalidJsonInvocation } = require('../lib/jsonCli')
19
+ const { isCodingAgentEnvironment } = require('../lib/helpers/codingAgentEnvironment')
17
20
 
18
21
  const PACKAGE_MANAGER_TOOL = process.env.NPQ_PKG_MGR
19
22
  const DISABLE_AUTO_CONTINUE = process.env.NPQ_DISABLE_AUTO_CONTINUE === 'true'
20
23
 
21
- const cliArgs = CliParser.parseArgsMinimal()
24
+ const codingAgentEnvironment = isCodingAgentEnvironment()
25
+ let cliArgs
22
26
 
23
- const silentModeNoPackages = !cliArgs || !cliArgs.packages || cliArgs.packages.length === 0
27
+ try {
28
+ cliArgs = CliParser.parseArgsMinimal({ codingAgentEnvironment })
29
+ } catch (error) {
30
+ if (!error || error.npqJsonMode !== true) throw error
31
+ writeInvalidJsonInvocation(createJsonOutput())
32
+ process.exitCode = 2
33
+ }
24
34
 
25
- const isInteractive = cliSupport.isInteractiveTerminal() && !silentModeNoPackages
26
- const spinner = isInteractive ? new Spinner({ text: 'Initiating...' }) : null
35
+ if (cliArgs && cliArgs.json) {
36
+ runJsonCli(cliArgs, { output: createJsonOutput() })
37
+ } else if (cliArgs) {
38
+ const silentModeNoPackages = !cliArgs || !cliArgs.packages || cliArgs.packages.length === 0
27
39
 
28
- if (spinner) {
29
- spinner.start()
30
- }
40
+ const isInteractive = cliSupport.isInteractiveTerminal() && !silentModeNoPackages
41
+ const spinner = isInteractive ? new Spinner({ text: 'Initiating...' }) : null
42
+
43
+ if (spinner) {
44
+ spinner.start()
45
+ }
31
46
 
32
- RegistryConfig.load({ argv: cliArgs.registryConfigArgs || [] })
33
- .then((registryConfig) => {
34
- const registryClient = new RegistryClient(registryConfig)
35
- const marshall = new Marshall({
36
- pkgs: cliArgs.packages,
37
- registryClient,
38
- progressManager: spinner,
39
- promiseThrottleHelper
47
+ RegistryConfig.load({ argv: cliArgs.registryConfigArgs || [] })
48
+ .then((registryConfig) => {
49
+ const registryClient = new RegistryClient(registryConfig)
50
+ const marshall = new Marshall({
51
+ pkgs: cliArgs.packages,
52
+ registryClient,
53
+ progressManager: spinner,
54
+ promiseThrottleHelper
55
+ })
56
+ return marshall.process()
40
57
  })
41
- return marshall.process()
42
- })
43
- .then((marshallResults) => {
44
- if (spinner) {
45
- spinner.stop()
46
- }
58
+ .then((marshallResults) => {
59
+ if (spinner) {
60
+ spinner.stop()
61
+ }
47
62
 
48
- const results = reportResults(marshallResults)
49
- if (
50
- results &&
51
- Object.hasOwn(results, 'countErrors') &&
52
- Object.hasOwn(results, 'countWarnings')
53
- ) {
54
- const {
55
- countErrors,
56
- countWarnings,
57
- countNotEvaluated = 0,
58
- useRichFormatting
59
- } = results
60
- const hasFindings = countErrors > 0 || countWarnings > 0
61
- const hasReportableResults = hasFindings || countNotEvaluated > 0
63
+ const results = reportResults(marshallResults)
64
+ if (
65
+ results &&
66
+ Object.hasOwn(results, 'countErrors') &&
67
+ Object.hasOwn(results, 'countWarnings')
68
+ ) {
69
+ const { countErrors, countWarnings, countNotEvaluated = 0, useRichFormatting } = results
70
+ const hasFindings = countErrors > 0 || countWarnings > 0
71
+ const hasReportableResults = hasFindings || countNotEvaluated > 0
62
72
 
63
- if (hasReportableResults) {
64
- console.log()
65
- console.log(
66
- hasFindings ? 'Packages with issues found:' : 'Package checks not evaluated:'
67
- )
73
+ if (hasReportableResults) {
74
+ console.log()
75
+ console.log(hasFindings ? 'Packages with issues found:' : 'Package checks not evaluated:')
68
76
 
69
- if (useRichFormatting) {
70
- console.log(results.resultsForPrettyPrint)
71
- console.log(results.summaryForPrettyPrint)
72
- } else {
73
- console.log(results.resultsForPlainTextPrint)
74
- console.log(results.summaryForPlainTextPrint)
77
+ if (useRichFormatting) {
78
+ console.log(results.resultsForPrettyPrint)
79
+ console.log(results.summaryForPrettyPrint)
80
+ } else {
81
+ console.log(results.resultsForPlainTextPrint)
82
+ console.log(results.summaryForPlainTextPrint)
83
+ }
75
84
  }
76
- }
77
85
 
78
- return {
79
- anyIssues: hasFindings,
80
- countErrors,
81
- countWarnings,
82
- countNotEvaluated
86
+ return {
87
+ anyIssues: hasFindings,
88
+ countErrors,
89
+ countWarnings,
90
+ countNotEvaluated
91
+ }
83
92
  }
84
- }
85
- return undefined
86
- })
87
- .then((result) => {
88
- if (result && result.countErrors > 0) {
89
- console.log()
90
- return cliPrompt.prompt({
91
- name: 'install',
92
- message: 'Continue install ?',
93
- default: false
94
- })
95
- } else {
96
- if (result && result.countWarnings > 0) {
93
+ return undefined
94
+ })
95
+ .then((result) => {
96
+ if (result && result.countErrors > 0) {
97
97
  console.log()
98
- // Check if auto-continue is disabled via environment variable
99
- if (DISABLE_AUTO_CONTINUE) {
100
- return cliPrompt.prompt({
98
+ return cliPrompt.prompt({
99
+ name: 'install',
100
+ message: 'Continue install ?',
101
+ default: false
102
+ })
103
+ } else {
104
+ if (result && result.countWarnings > 0) {
105
+ console.log()
106
+ // Check if auto-continue is disabled via environment variable
107
+ if (DISABLE_AUTO_CONTINUE) {
108
+ return cliPrompt.prompt({
109
+ name: 'install',
110
+ message: 'Continue install ?',
111
+ default: false
112
+ })
113
+ }
114
+ return cliPrompt.autoContinue({
101
115
  name: 'install',
102
- message: 'Continue install ?',
103
- default: false
116
+ message: 'Auto-continue with install in... ',
117
+ timeInSeconds: 15
104
118
  })
105
119
  }
106
- return cliPrompt.autoContinue({
107
- name: 'install',
108
- message: 'Auto-continue with install in... ',
109
- timeInSeconds: 15
110
- })
111
120
  }
112
- }
113
121
 
114
- return { install: true }
115
- })
116
- .then((status) => {
117
- if (
118
- status &&
119
- Object.prototype.hasOwnProperty.call(status, 'install') &&
120
- status.install === true
121
- ) {
122
- return pkgMgr.process(PACKAGE_MANAGER_TOOL)
123
- }
124
- })
125
- .then((exitCode) => {
126
- if (typeof exitCode === 'number') {
127
- process.exitCode = exitCode
128
- }
129
- })
130
- .catch((error) => {
131
- // Ensure errorCode is always a number
132
- let errorCode = -1
133
- if (typeof error.code === 'number') {
134
- errorCode = error.code
135
- } else if (error.code === 'ABORT_ERR') {
136
- errorCode = 1
137
- } else if (error.code === 'USER_ABORT') {
138
- errorCode = error.exitCode || 1
139
- }
122
+ return { install: true }
123
+ })
124
+ .then((status) => {
125
+ if (
126
+ status &&
127
+ Object.prototype.hasOwnProperty.call(status, 'install') &&
128
+ status.install === true
129
+ ) {
130
+ return pkgMgr.process(PACKAGE_MANAGER_TOOL)
131
+ }
132
+ })
133
+ .then((exitCode) => {
134
+ if (typeof exitCode === 'number') {
135
+ process.exitCode = exitCode
136
+ }
137
+ })
138
+ .catch((error) => {
139
+ // Ensure errorCode is always a number
140
+ let errorCode = -1
141
+ if (typeof error.code === 'number') {
142
+ errorCode = error.code
143
+ } else if (error.code === 'ABORT_ERR') {
144
+ errorCode = 1
145
+ } else if (error.code === 'USER_ABORT') {
146
+ errorCode = error.exitCode || 1
147
+ }
140
148
 
141
- CliParser.exit({
142
- errorCode,
143
- message: error.message || 'An error occurred',
144
- spinner
149
+ CliParser.exit({
150
+ errorCode,
151
+ message: error.message || 'An error occurred',
152
+ spinner
153
+ })
145
154
  })
146
- })
155
+ }
package/bin/npq.js CHANGED
@@ -15,171 +15,183 @@ const cliPrompt = require('../lib/helpers/cliPrompt.js')
15
15
  const { reportResults } = require('../lib/helpers/reportResults')
16
16
  const { Spinner } = require('../lib/helpers/cliSpinner')
17
17
  const { promiseThrottleHelper } = require('../lib/helpers/promiseThrottler')
18
+ const { createJsonOutput } = require('../lib/helpers/jsonOutput')
19
+ const { runJsonCli, writeInvalidJsonInvocation } = require('../lib/jsonCli')
20
+ const { isCodingAgentEnvironment } = require('../lib/helpers/codingAgentEnvironment')
18
21
  const RegistryConfig = require('../lib/helpers/registryConfig')
19
22
  const RegistryClient = require('../lib/helpers/registryClient')
20
23
 
21
24
  const debug = util.debuglog('npq')
22
25
 
23
- const cliArgs = CliParser.parseArgsFull()
24
- const auditOnly = cliArgs.dryRun || !cliArgs.installSubcommandExplicit
25
- const isInteractive = cliSupport.isInteractiveTerminal() && !cliArgs.plain
26
- const spinner = isInteractive ? new Spinner({ text: 'Initiating...' }) : null
26
+ const jsonRequested = process.argv.slice(2).includes('--json') || isCodingAgentEnvironment()
27
+ const jsonOutput = jsonRequested ? createJsonOutput() : null
28
+ let cliArgs
27
29
 
28
- if (spinner) {
29
- spinner.start()
30
+ try {
31
+ cliArgs = CliParser.parseArgsFull()
32
+ } catch (error) {
33
+ if (!jsonRequested) throw error
34
+ writeInvalidJsonInvocation(jsonOutput)
35
+ process.exitCode = 2
30
36
  }
31
37
 
32
- let registryClient
38
+ if (cliArgs && cliArgs.json) {
39
+ runJsonCli(cliArgs, { output: jsonOutput })
40
+ } else if (cliArgs) {
41
+ const auditOnly = cliArgs.dryRun || !cliArgs.installSubcommandExplicit
42
+ const isInteractive = cliSupport.isInteractiveTerminal() && !cliArgs.plain
43
+ const spinner = isInteractive ? new Spinner({ text: 'Initiating...' }) : null
33
44
 
34
- RegistryConfig.load({ argv: cliArgs.registryConfigArgs || [] })
35
- .then((registryConfig) => {
36
- registryClient = new RegistryClient(registryConfig)
37
- })
38
- .then(() => {
39
- if (cliArgs.packages.length === 0) {
40
- debug('\nNo packages specified, using project packages from package.json')
41
- return getProjectPackages()
42
- }
45
+ if (spinner) {
46
+ spinner.start()
47
+ }
43
48
 
44
- return cliArgs.packages
45
- })
46
- .then((packages) => {
47
- if (packages.error) {
48
- console.log()
49
- CliParser.exit({
50
- errorCode: packages.error.code || -1,
51
- message: packages.message,
52
- spinner
53
- })
54
- }
49
+ let registryClient
55
50
 
56
- const marshall = new Marshall({
57
- pkgs: packages,
58
- registryClient,
59
- progressManager: spinner,
60
- promiseThrottleHelper
51
+ Promise.resolve()
52
+ .then(() => RegistryConfig.load({ argv: cliArgs.registryConfigArgs || [] }))
53
+ .then((registryConfig) => {
54
+ registryClient = new RegistryClient(registryConfig)
61
55
  })
56
+ .then(() => {
57
+ if (cliArgs.packages.length === 0) {
58
+ debug('\nNo packages specified, using project packages from package.json')
59
+ return getProjectPackages()
60
+ }
62
61
 
63
- return marshall.process()
64
- })
65
- .then((marshallResults) => {
66
- if (spinner) {
67
- spinner.stop()
68
- }
69
-
70
- const results = reportResults(marshallResults, { plain: cliArgs.plain })
71
- if (
72
- results &&
73
- Object.hasOwn(results, 'countErrors') &&
74
- Object.hasOwn(results, 'countWarnings')
75
- ) {
76
- const {
77
- countErrors,
78
- countWarnings,
79
- countNotEvaluated = 0,
80
- useRichFormatting
81
- } = results
82
- const hasFindings = countErrors > 0 || countWarnings > 0
83
- const hasReportableResults = hasFindings || countNotEvaluated > 0
84
-
85
- if (hasReportableResults) {
62
+ return cliArgs.packages
63
+ })
64
+ .then((packages) => {
65
+ if (packages.error) {
86
66
  console.log()
87
- console.log(
88
- hasFindings ? 'Packages with issues found:' : 'Package checks not evaluated:'
89
- )
90
-
91
- if (useRichFormatting) {
92
- console.log(results.resultsForPrettyPrint)
93
- console.log(results.summaryForPrettyPrint)
94
- } else {
95
- console.log(results.resultsForPlainTextPrint)
96
- console.log(results.summaryForPlainTextPrint)
97
- }
67
+ CliParser.exit({
68
+ errorCode: packages.error.code || -1,
69
+ message: packages.message,
70
+ spinner
71
+ })
98
72
  }
99
73
 
100
- return {
101
- anyIssues: hasFindings,
102
- countErrors,
103
- countWarnings,
104
- countNotEvaluated
105
- }
106
- }
107
- return undefined
108
- })
109
- .then((result) => {
110
- if (auditOnly) {
111
- const auditFailed = result && (result.countErrors > 0 || result.countWarnings > 0)
112
- CliParser.exit({
113
- errorCode: auditFailed ? 1 : 0,
114
- spinner
74
+ const marshall = new Marshall({
75
+ pkgs: packages,
76
+ registryClient,
77
+ progressManager: spinner,
78
+ promiseThrottleHelper
115
79
  })
80
+
81
+ return marshall.process()
82
+ })
83
+ .then((marshallResults) => {
84
+ if (spinner) {
85
+ spinner.stop()
86
+ }
87
+
88
+ const results = reportResults(marshallResults, { plain: cliArgs.plain })
89
+ if (
90
+ results &&
91
+ Object.hasOwn(results, 'countErrors') &&
92
+ Object.hasOwn(results, 'countWarnings')
93
+ ) {
94
+ const { countErrors, countWarnings, countNotEvaluated = 0, useRichFormatting } = results
95
+ const hasFindings = countErrors > 0 || countWarnings > 0
96
+ const hasReportableResults = hasFindings || countNotEvaluated > 0
97
+
98
+ if (hasReportableResults) {
99
+ console.log()
100
+ console.log(hasFindings ? 'Packages with issues found:' : 'Package checks not evaluated:')
101
+
102
+ if (useRichFormatting) {
103
+ console.log(results.resultsForPrettyPrint)
104
+ console.log(results.summaryForPrettyPrint)
105
+ } else {
106
+ console.log(results.resultsForPlainTextPrint)
107
+ console.log(results.summaryForPlainTextPrint)
108
+ }
109
+ }
110
+
111
+ return {
112
+ anyIssues: hasFindings,
113
+ countErrors,
114
+ countWarnings,
115
+ countNotEvaluated
116
+ }
117
+ }
116
118
  return undefined
117
- }
118
-
119
- if (result && result.countErrors > 0) {
120
- console.log()
121
- return cliPrompt.prompt({
122
- name: 'install',
123
- message: 'Continue install ?',
124
- default: false
125
- })
126
- } else {
127
- if (result && result.countWarnings > 0) {
119
+ })
120
+ .then((result) => {
121
+ if (auditOnly) {
122
+ const auditFailed = result && (result.countErrors > 0 || result.countWarnings > 0)
123
+ CliParser.exit({
124
+ errorCode: auditFailed ? 1 : 0,
125
+ spinner
126
+ })
127
+ return undefined
128
+ }
129
+
130
+ if (result && result.countErrors > 0) {
128
131
  console.log()
129
- // Check if auto-continue is disabled via CLI flag or environment variable
130
- if (cliArgs.disableAutoContinue) {
131
- return cliPrompt.prompt({
132
+ return cliPrompt.prompt({
133
+ name: 'install',
134
+ message: 'Continue install ?',
135
+ default: false
136
+ })
137
+ } else {
138
+ if (result && result.countWarnings > 0) {
139
+ console.log()
140
+ // Check if auto-continue is disabled via CLI flag or environment variable
141
+ if (cliArgs.disableAutoContinue) {
142
+ return cliPrompt.prompt({
143
+ name: 'install',
144
+ message: 'Continue install ?',
145
+ default: false
146
+ })
147
+ }
148
+ return cliPrompt.autoContinue({
132
149
  name: 'install',
133
- message: 'Continue install ?',
134
- default: false
150
+ message: 'Auto-continue with install in... ',
151
+ timeInSeconds: 15
135
152
  })
136
153
  }
137
- return cliPrompt.autoContinue({
138
- name: 'install',
139
- message: 'Auto-continue with install in... ',
140
- timeInSeconds: 15
141
- })
142
154
  }
143
- }
144
155
 
145
- return { install: true }
146
- })
147
- .then((status) => {
148
- if (
149
- status &&
150
- Object.prototype.hasOwnProperty.call(status, 'install') &&
151
- status.install === true
152
- ) {
153
- return pkgMgr.process(cliArgs.packageManager)
154
- }
155
- })
156
- .then((exitCode) => {
157
- if (typeof exitCode === 'number') {
158
- process.exitCode = exitCode
159
- }
160
- })
161
- .catch((error) => {
162
- // Ensure errorCode is always a number
163
- let errorCode = -1
164
- if (typeof error.code === 'number') {
165
- errorCode = error.code
166
- } else if (error.code === 'ABORT_ERR') {
167
- errorCode = 1
168
- } else if (error.code === 'USER_ABORT') {
169
- errorCode = error.exitCode || 1
170
- }
156
+ return { install: true }
157
+ })
158
+ .then((status) => {
159
+ if (
160
+ status &&
161
+ Object.prototype.hasOwnProperty.call(status, 'install') &&
162
+ status.install === true
163
+ ) {
164
+ return pkgMgr.process(cliArgs.packageManager)
165
+ }
166
+ })
167
+ .then((exitCode) => {
168
+ if (typeof exitCode === 'number') {
169
+ process.exitCode = exitCode
170
+ }
171
+ })
172
+ .catch((error) => {
173
+ // Ensure errorCode is always a number
174
+ let errorCode = -1
175
+ if (typeof error.code === 'number') {
176
+ errorCode = error.code
177
+ } else if (error.code === 'ABORT_ERR') {
178
+ errorCode = 1
179
+ } else if (error.code === 'USER_ABORT') {
180
+ errorCode = error.exitCode || 1
181
+ }
182
+
183
+ CliParser.exit({
184
+ errorCode,
185
+ message: error.message || 'An error occurred',
186
+ spinner
187
+ })
188
+ })
171
189
 
190
+ // attach event handler for CTRL+C
191
+ process.on('SIGINT', () => {
172
192
  CliParser.exit({
173
- errorCode,
174
- message: error.message || 'An error occurred',
193
+ errorCode: 0,
175
194
  spinner
176
195
  })
177
196
  })
178
-
179
- // attach event handler for CTRL+C
180
- process.on('SIGINT', () => {
181
- CliParser.exit({
182
- errorCode: 0,
183
- spinner
184
- })
185
- })
197
+ }