@contrast/agent 4.26.0 → 4.27.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/bootstrap.js +7 -0
- package/config-diagnostics.js +130 -0
- package/lib/contrast.js +29 -8
- package/lib/util/config-diagnostics-utils.js +221 -0
- package/package.json +5 -3
package/bootstrap.js
CHANGED
|
@@ -41,6 +41,13 @@ Module.runMain = async function (...args) {
|
|
|
41
41
|
await loader.bootstrap(process.argv);
|
|
42
42
|
}
|
|
43
43
|
await loader.resetArgs(process.argv[0], process.argv[1]);
|
|
44
|
+
|
|
45
|
+
const diagnostics = process.env['CONTRAST__AGENT__DIAGNOSTICS__ENABLE'];
|
|
46
|
+
if (diagnostics === 'true') {
|
|
47
|
+
// eslint-disable-next-line no-process-exit
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
loader.logTime(startTime, 'agent');
|
|
45
52
|
const appStartTime = process.hrtime();
|
|
46
53
|
orig.apply(this, args);
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
Copyright: 2022 Contrast Security, Inc
|
|
4
|
+
Contact: support@contrastsecurity.com
|
|
5
|
+
License: Commercial
|
|
6
|
+
|
|
7
|
+
NOTICE: This Software and the patented inventions embodied within may only be
|
|
8
|
+
used as part of Contrast Security’s commercial offerings. Even though it is
|
|
9
|
+
made available through public repositories, use of this Software is subject to
|
|
10
|
+
the applicable End User Licensing Agreement found at
|
|
11
|
+
https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
12
|
+
between Contrast Security and the End User. The Software may not be reverse
|
|
13
|
+
engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
14
|
+
way not consistent with the End User License Agreement.
|
|
15
|
+
*/
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const { exec } = require('child_process');
|
|
19
|
+
|
|
20
|
+
function handleDependentParameter(args, currParameter, nextParameter) {
|
|
21
|
+
switch (currParameter) {
|
|
22
|
+
case '--output':
|
|
23
|
+
case '-o':
|
|
24
|
+
args.output = nextParameter;
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
throw new Error(`Invalid parameter ${currParameter}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function handleIndependentParameter(args, parameter) {
|
|
32
|
+
switch (parameter) {
|
|
33
|
+
case '--help':
|
|
34
|
+
args.help = true;
|
|
35
|
+
break;
|
|
36
|
+
case '--quiet':
|
|
37
|
+
case '-q':
|
|
38
|
+
args.quiet = true;
|
|
39
|
+
break;
|
|
40
|
+
default:
|
|
41
|
+
throw new Error(`Invalid parameter ${parameter}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function printHelpMessage() {
|
|
46
|
+
console.log(
|
|
47
|
+
'\nDescription:\n' +
|
|
48
|
+
'The config-diagnostics utility returns the current effective node agent configuration\n' +
|
|
49
|
+
'\nUsage:\n' +
|
|
50
|
+
'npx -p @contrast/agent config-diagnostics <path/to/entry/script> [options]\n' +
|
|
51
|
+
'npx -p @contrast/agent config-diagnostics --help\n' +
|
|
52
|
+
'\nOptions:\n' +
|
|
53
|
+
'--quiet -q Prevents output of the report to the console.\n' +
|
|
54
|
+
'--output -o The directory to write the report in. Defaults to the current directory.\n'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseArgv() {
|
|
59
|
+
const argv = process.argv.slice(2);
|
|
60
|
+
const args = {
|
|
61
|
+
output: 'contrast_effective_config.json',
|
|
62
|
+
quiet: false,
|
|
63
|
+
help: false,
|
|
64
|
+
};
|
|
65
|
+
let currParameter = null;
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < argv.length; i++) {
|
|
68
|
+
if (!currParameter) {
|
|
69
|
+
currParameter = argv[i];
|
|
70
|
+
}
|
|
71
|
+
if (!(currParameter.startsWith('--') || currParameter.startsWith('-'))) {
|
|
72
|
+
currParameter = null;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (i + 1 == argv.length || argv[i + 1].startsWith('--') || argv[i + 1].startsWith('-')) {
|
|
77
|
+
handleIndependentParameter(args, currParameter);
|
|
78
|
+
currParameter = null;
|
|
79
|
+
} else {
|
|
80
|
+
handleDependentParameter(args, currParameter, argv[i + 1]);
|
|
81
|
+
currParameter = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return args;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function executeNodeAgent(args) {
|
|
89
|
+
let agentEnvArgs = 'CONTRAST__SHOW__BANNER=false CONTRAST__AGENT__DIAGNOSTICS__ENABLE=true';
|
|
90
|
+
const entry = process.argv.slice(2).shift();
|
|
91
|
+
|
|
92
|
+
if (!args.quiet) {
|
|
93
|
+
agentEnvArgs = agentEnvArgs.concat(' CONTRAST__AGENT__DIAGNOSTICS__QUIET=false');
|
|
94
|
+
}
|
|
95
|
+
if (args.output) {
|
|
96
|
+
agentEnvArgs = agentEnvArgs.concat(` CONTRAST__AGENT__DIAGNOSTICS__REPORT_PATH=${args.output}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
exec(`${agentEnvArgs} node -r ./node_modules/@contrast/agent/bootstrap.js ${entry}`,
|
|
100
|
+
(error, stdout, stderr) => {
|
|
101
|
+
if (error && !args.quiet) {
|
|
102
|
+
console.log(`error: ${error.message}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// by default errors still won't be visible since stdout == sterr
|
|
107
|
+
if (stderr) {
|
|
108
|
+
console.log(`stderr: ${stderr}`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!args.quiet) {
|
|
113
|
+
console.log(stdout);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function fetchAgentConfig(args) {
|
|
120
|
+
if (process.argv.slice(2).length == 1 && args.help) {
|
|
121
|
+
printHelpMessage();
|
|
122
|
+
} else {
|
|
123
|
+
executeNodeAgent(args);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
fetchAgentConfig(parseArgv());
|
|
129
|
+
// exporting this stuff for testing purposes only
|
|
130
|
+
module.exports = { printHelpMessage, parseArgv, executeNodeAgent, fetchAgentConfig };
|
package/lib/contrast.js
CHANGED
|
@@ -14,7 +14,7 @@ Copyright: 2022 Contrast Security, Inc
|
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const { program } = require('./core/config/options');
|
|
17
|
+
const { program, options } = require('./core/config/options');
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const os = require('os');
|
|
20
20
|
const semver = require('semver');
|
|
@@ -25,6 +25,7 @@ const Module = require('module');
|
|
|
25
25
|
const sourceMapUtility = require('./util/source-map');
|
|
26
26
|
const loggerFactory = require('./core/logger');
|
|
27
27
|
const logger = loggerFactory('contrast:contrast-init');
|
|
28
|
+
const { outputAgentConfigFile } = require('./util/config-diagnostics-utils');
|
|
28
29
|
|
|
29
30
|
function getAgentSnippet() {
|
|
30
31
|
// getting reference to name every time for testing purposes
|
|
@@ -59,9 +60,12 @@ contrastAgent.showBanner = function showBanner() {
|
|
|
59
60
|
console.log(colors.red(cat));
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const showAgentBanner = (process.env['CONTRAST__SHOW__BANNER'] === 'false') ? false : true;
|
|
64
|
+
if (showAgentBanner) {
|
|
65
|
+
logger.console();
|
|
66
|
+
logger.console('%s %s', NAME, VERSION);
|
|
67
|
+
logger.console(colors.green('--------------------------------------'));
|
|
68
|
+
}
|
|
65
69
|
};
|
|
66
70
|
|
|
67
71
|
/**
|
|
@@ -284,18 +288,16 @@ contrastAgent.prepare = function(...args) {
|
|
|
284
288
|
|
|
285
289
|
/**
|
|
286
290
|
* Sends startup message to TeamServer then instruments user code
|
|
287
|
-
*
|
|
288
291
|
* @param {Array} args process.argv
|
|
289
292
|
*/
|
|
290
293
|
contrastAgent.bootstrap = function(args) {
|
|
291
294
|
const reporter = new TSReporter(agent);
|
|
295
|
+
|
|
292
296
|
// returning promise for testing purposes only
|
|
293
297
|
return reporter
|
|
294
298
|
.onboard(args)
|
|
295
299
|
.catch((err) => {
|
|
296
|
-
logger.error(
|
|
297
|
-
'Reporter onboarding failed. Continuing without instrumentation.'
|
|
298
|
-
);
|
|
300
|
+
logger.error('Reporter onboarding failed. Continuing without instrumentation.');
|
|
299
301
|
logger.error(err);
|
|
300
302
|
agent.clearIntervals();
|
|
301
303
|
return;
|
|
@@ -314,6 +316,25 @@ contrastAgent.bootstrap = function(args) {
|
|
|
314
316
|
'Unexpected error while trying to start contrast. Continuing without instrumentation. Error: %o',
|
|
315
317
|
err
|
|
316
318
|
);
|
|
319
|
+
})
|
|
320
|
+
.finally(async () => {
|
|
321
|
+
let destination = process.env['CONTRAST__AGENT__DIAGNOSTICS__REPORT_PATH'];
|
|
322
|
+
if (destination == null) {
|
|
323
|
+
destination = agent.config._flat['agent.logger.path'].split('/');
|
|
324
|
+
destination.pop() && destination.push('contrast_effective_config.json');
|
|
325
|
+
destination = destination.join('/');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const args = {
|
|
329
|
+
quiet: (process.env['CONTRAST__AGENT__DIAGNOSTICS__QUIET'] === 'false') ? false : true,
|
|
330
|
+
output: destination,
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
outputAgentConfigFile(agent, options, args, null);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
outputAgentConfigFile(agent, options, args, err);
|
|
337
|
+
}
|
|
317
338
|
});
|
|
318
339
|
};
|
|
319
340
|
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright: 2022 Contrast Security, Inc
|
|
3
|
+
Contact: support@contrastsecurity.com
|
|
4
|
+
License: Commercial
|
|
5
|
+
|
|
6
|
+
NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
made available through public repositories, use of this Software is subject to
|
|
9
|
+
the applicable End User Licensing Agreement found at
|
|
10
|
+
https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line complexity
|
|
21
|
+
function getLoggerValues(option, config, tsData) {
|
|
22
|
+
let tsValue, effectiveValue = config._flat[option.name];
|
|
23
|
+
|
|
24
|
+
switch (option.name) {
|
|
25
|
+
case 'agent.logger.level':
|
|
26
|
+
tsValue = tsData.logLevel;
|
|
27
|
+
break;
|
|
28
|
+
case 'agent.logger.path':
|
|
29
|
+
tsValue = tsData.logFile;
|
|
30
|
+
break;
|
|
31
|
+
case 'agent.security_logger.syslog.enable':
|
|
32
|
+
tsValue = tsData.defend.syslog.enabled;
|
|
33
|
+
break;
|
|
34
|
+
case 'agent.security_logger.syslog.ip':
|
|
35
|
+
tsValue = tsData.defend.syslog.ipAddress;
|
|
36
|
+
break;
|
|
37
|
+
case 'agent.security_logger.syslog.port':
|
|
38
|
+
tsValue = tsData.defend.syslog.port;
|
|
39
|
+
break;
|
|
40
|
+
case 'agent.security_logger.syslog.fascility':
|
|
41
|
+
tsValue = tsData.defend.syslog.fascilityCode;
|
|
42
|
+
break;
|
|
43
|
+
case 'agent.security_logger.syslog.severity_exploited':
|
|
44
|
+
tsValue = tsData.defend.syslog.severityExploited;
|
|
45
|
+
break;
|
|
46
|
+
case 'agent.security_logger.syslog.severity_blocked':
|
|
47
|
+
tsValue = tsData.defend.syslog.severityBlocked;
|
|
48
|
+
break;
|
|
49
|
+
case 'agent.security_logger.syslog.severity_probed':
|
|
50
|
+
tsValue = tsData.defend.syslog.severityProbed;
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (config._sources[option.name] == 'DEFAULT' && tsValue != null) {
|
|
57
|
+
effectiveValue = tsValue;
|
|
58
|
+
config._sources[option.name] = 'ContrastUI';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
CanonicalName: option.name,
|
|
63
|
+
Name: option.name,
|
|
64
|
+
Value: effectiveValue,
|
|
65
|
+
Source: config._sources[option.name],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getAssessValues(option, config, tsData) {
|
|
70
|
+
let tsValue, effectiveValue = config._flat[option.name];
|
|
71
|
+
|
|
72
|
+
switch (option.name) {
|
|
73
|
+
case 'assess.enable':
|
|
74
|
+
tsValue = tsData.assess.enabled;
|
|
75
|
+
break;
|
|
76
|
+
case 'assess.enable_propagators':
|
|
77
|
+
tsValue = tsData.assess.propagators;
|
|
78
|
+
break;
|
|
79
|
+
case 'assess.sampling.enable':
|
|
80
|
+
tsValue = tsData.assess.sampling;
|
|
81
|
+
break;
|
|
82
|
+
default:
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (config._sources[option.name] == 'DEFAULT' && tsValue != null) {
|
|
87
|
+
effectiveValue = tsValue;
|
|
88
|
+
config._sources[option.name] = 'ContrastUI';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
CanonicalName: option.name,
|
|
93
|
+
Name: option.name,
|
|
94
|
+
Value: effectiveValue,
|
|
95
|
+
Source: config._sources[option.name],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getProtectValues(option, config, tsData) {
|
|
100
|
+
let value = {};
|
|
101
|
+
|
|
102
|
+
if (option.name.includes('ip-denylist')) {
|
|
103
|
+
value = {
|
|
104
|
+
CanonicalName: option.name.split('.')[2],
|
|
105
|
+
Name: option.name,
|
|
106
|
+
Value: tsData.ipDenylistsList,
|
|
107
|
+
Source: 'ContrastUI',
|
|
108
|
+
};
|
|
109
|
+
} else if (option.name.includes('virtual-patch')) {
|
|
110
|
+
value = {
|
|
111
|
+
CanonicalName: option.name.split('.')[2],
|
|
112
|
+
Name: option.name,
|
|
113
|
+
Value: tsData.virtualPatchesList,
|
|
114
|
+
Source: 'ContrastUI',
|
|
115
|
+
};
|
|
116
|
+
} else if (option.name.includes('protect.rules') && option.name.includes('mode')) {
|
|
117
|
+
const apiRule = tsData.protectionRulesList.find(r => r.id == option.name.split('.')[2]);
|
|
118
|
+
|
|
119
|
+
const apiMode = apiRule ? apiRule.mode : undefined;
|
|
120
|
+
const ymlMode = config._flat[option.name];
|
|
121
|
+
let effectiveMode;
|
|
122
|
+
|
|
123
|
+
if (!ymlMode && apiMode) {
|
|
124
|
+
config._sources[option.name] = 'ContrastUI';
|
|
125
|
+
effectiveMode = apiMode;
|
|
126
|
+
} else if (ymlMode) {
|
|
127
|
+
effectiveMode = ymlMode;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
value = {
|
|
131
|
+
CanonicalName: option.name.split('.')[2],
|
|
132
|
+
Name: option.name,
|
|
133
|
+
Value: effectiveMode,
|
|
134
|
+
Source: config._sources[option.name],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getEffectiveConfigValues(agent, options, err) {
|
|
142
|
+
const tsData = { ...agent.tsFeatureSet.serverFeatures, ...agent.tsFeatureSet.applicationSettings };
|
|
143
|
+
const values = [];
|
|
144
|
+
|
|
145
|
+
for (const option of options) {
|
|
146
|
+
if (!err && option.name.includes('protect.rules') && option.name.includes('mode')) {
|
|
147
|
+
// get all protect rules
|
|
148
|
+
values.push(getProtectValues(option, agent.config, tsData));
|
|
149
|
+
} else if (!err && option.name.includes('logger')) {
|
|
150
|
+
// fetch agent/service/sys logger settings
|
|
151
|
+
values.push(getLoggerValues(option, agent.config, tsData));
|
|
152
|
+
} else if (!err && option.name.includes('assess')) {
|
|
153
|
+
// fetch assess settings
|
|
154
|
+
values.push(getAssessValues(option, agent.config, tsData));
|
|
155
|
+
} else if (agent.config._flat[option.name] != null) {
|
|
156
|
+
// fetch everything else
|
|
157
|
+
values.push({
|
|
158
|
+
CanonicalName: option.name,
|
|
159
|
+
Name: option.name,
|
|
160
|
+
Value: agent.config._flat[option.name],
|
|
161
|
+
Source: agent.config._sources[option.name],
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return values;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getEffectiveConfig(agent, options, err) {
|
|
169
|
+
const effectiveConfigValues = getEffectiveConfigValues(agent, options, err);
|
|
170
|
+
const envValues = effectiveConfigValues.filter(v => v.Source == 'ENV');
|
|
171
|
+
const cliValues = effectiveConfigValues.filter(v => v.Source == 'CLI');
|
|
172
|
+
const ymlValues = effectiveConfigValues.filter(v => v.Source == 'YAML');
|
|
173
|
+
const apiValues = effectiveConfigValues.filter(v => v.Source == 'ContrastUI');
|
|
174
|
+
|
|
175
|
+
const effectiveConfig = {
|
|
176
|
+
ReportCreate: new Date(Date.now()),
|
|
177
|
+
Config: {
|
|
178
|
+
EffectiveConfig: {
|
|
179
|
+
Status: undefined,
|
|
180
|
+
Values: effectiveConfigValues
|
|
181
|
+
},
|
|
182
|
+
Environment: envValues,
|
|
183
|
+
CommandLine: cliValues,
|
|
184
|
+
ContrastUI: apiValues,
|
|
185
|
+
File: ymlValues,
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return effectiveConfig;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function outputAgentConfigFile(agent, options, args, err) {
|
|
193
|
+
const effectiveConfig = getEffectiveConfig(agent, options, err);
|
|
194
|
+
|
|
195
|
+
if (err) {
|
|
196
|
+
effectiveConfig.Config.Status = 'Failure! ' +
|
|
197
|
+
`Could not connect to TeamServer, so no TeamServer data will be included. Error: ${err}`;
|
|
198
|
+
} else {
|
|
199
|
+
effectiveConfig.Config.Status = 'Success';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
fs.accessSync(path.join(args.output, '..'), fs.constants.RDWD_OK);
|
|
204
|
+
fs.writeFileSync(args.output, JSON.stringify(effectiveConfig, null, 2), 'utf-8');
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.log(`Couldn't create effective config file: ${err}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!args.quiet) {
|
|
210
|
+
console.log(JSON.stringify(effectiveConfig, null, 2));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
getEffectiveConfigValues,
|
|
216
|
+
getEffectiveConfig,
|
|
217
|
+
outputAgentConfigFile,
|
|
218
|
+
getProtectValues,
|
|
219
|
+
getAssessValues,
|
|
220
|
+
getLoggerValues
|
|
221
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/agent",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.27.0",
|
|
4
4
|
"description": "Node.js security instrumentation by Contrast Security",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"security",
|
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
"bin": {
|
|
55
55
|
"node-contrast": "cli.js",
|
|
56
56
|
"perf-logs": "perf-logs.js",
|
|
57
|
-
"contrast-transpile": "cli-rewriter.js"
|
|
57
|
+
"contrast-transpile": "cli-rewriter.js",
|
|
58
|
+
"read-contrast-config": "config-diagnostics.js"
|
|
58
59
|
},
|
|
59
60
|
"files": [
|
|
60
61
|
"bin/**",
|
|
@@ -64,7 +65,8 @@
|
|
|
64
65
|
"cli.js",
|
|
65
66
|
"esm.mjs",
|
|
66
67
|
"perf-logs.js",
|
|
67
|
-
"cli-rewriter.js"
|
|
68
|
+
"cli-rewriter.js",
|
|
69
|
+
"config-diagnostics.js"
|
|
68
70
|
],
|
|
69
71
|
"repository": {
|
|
70
72
|
"type": "git"
|