@contrast/agentify 1.32.0 → 1.34.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/diagnostics.js +4 -2
- package/lib/function-hooks.js +12 -12
- package/lib/index.d.ts +2 -0
- package/lib/index.js +14 -7
- package/lib/index.test.js +27 -8
- package/lib/rewrite-is-deadzoned.js +2 -1
- package/lib/utils.js +24 -5
- package/lib/utils.test.js +110 -0
- package/lib/validators.js +3 -3
- package/package.json +15 -14
package/lib/diagnostics.js
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
const fs = require('fs/promises');
|
|
19
19
|
const path = require('path');
|
|
20
20
|
|
|
21
|
+
const { primordials: { JSONStringify } } = require('@contrast/common');
|
|
22
|
+
|
|
21
23
|
module.exports = function init(core) {
|
|
22
24
|
async function logDiagnosticFiles() {
|
|
23
25
|
const { config, logger } = core;
|
|
@@ -32,13 +34,13 @@ module.exports = function init(core) {
|
|
|
32
34
|
return Promise.all([
|
|
33
35
|
fs.writeFile(
|
|
34
36
|
path.join(report_path, 'contrast_effective_config.json'),
|
|
35
|
-
|
|
37
|
+
JSONStringify(effectiveConfig, null, 2)
|
|
36
38
|
).catch((err) => {
|
|
37
39
|
logger.warn({ err }, 'unable to write effective config file');
|
|
38
40
|
}),
|
|
39
41
|
fs.writeFile(
|
|
40
42
|
path.join(report_path, 'contrast_system_info.json'),
|
|
41
|
-
|
|
43
|
+
JSONStringify(systemInfo, null, 2)
|
|
42
44
|
).catch((err) => {
|
|
43
45
|
logger.warn({ err }, 'unable to write system info file');
|
|
44
46
|
}),
|
package/lib/function-hooks.js
CHANGED
|
@@ -21,6 +21,8 @@ const IS_RETURN_REGEX = /^return\W/;
|
|
|
21
21
|
const FUNCTION_DECL = 'function _contrast_';
|
|
22
22
|
const VARIABLE_DECL = 'const _contrast_fn = ';
|
|
23
23
|
|
|
24
|
+
const { primordials: { StringPrototypeReplace, StringPrototypeSplit, RegExpPrototypeExec, FunctionPrototypeToString } } = require('@contrast/common');
|
|
25
|
+
|
|
24
26
|
/**
|
|
25
27
|
* Injects some context around a function's toString() to make it syntactically
|
|
26
28
|
* valid. The `swc` parser expects input to be a valid `Statement` like one of
|
|
@@ -40,23 +42,23 @@ const VARIABLE_DECL = 'const _contrast_fn = ';
|
|
|
40
42
|
*/
|
|
41
43
|
function contextualize(code) {
|
|
42
44
|
// in case the class is anonymous we need to prepend a variable declaration.
|
|
43
|
-
if (
|
|
45
|
+
if (RegExpPrototypeExec.call(IS_CLASS_REGEX, code)) {
|
|
44
46
|
return `${VARIABLE_DECL}${code}`;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
// if the function is a return, we don't need to add context.
|
|
48
|
-
if (!
|
|
49
|
-
const [orig] =
|
|
50
|
+
if (!RegExpPrototypeExec.call(IS_RETURN_REGEX, code)) {
|
|
51
|
+
const [orig] = StringPrototypeSplit.call(code, '{');
|
|
50
52
|
let lineOne = orig;
|
|
51
53
|
|
|
52
54
|
// When stringified, class methods look like normal function without the
|
|
53
55
|
// `function` keyword. We can normalize this by prepending `function`.
|
|
54
|
-
if (!
|
|
56
|
+
if (!RegExpPrototypeExec.call(IS_FUNCTION_REGEX, lineOne) && lineOne.indexOf('=>') === -1) {
|
|
55
57
|
lineOne = `${FUNCTION_DECL}${lineOne}`;
|
|
56
58
|
}
|
|
57
59
|
lineOne = `${VARIABLE_DECL}${lineOne}`;
|
|
58
60
|
|
|
59
|
-
code =
|
|
61
|
+
code = StringPrototypeReplace.call(code, orig, lineOne);
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
return code;
|
|
@@ -70,8 +72,6 @@ module.exports = function (deps) {
|
|
|
70
72
|
patcher: { hookedFunctions },
|
|
71
73
|
} = deps;
|
|
72
74
|
|
|
73
|
-
const toString = patcher.unwrap(Function.prototype.toString);
|
|
74
|
-
|
|
75
75
|
const functionHooks = (deps.functionHooks = {
|
|
76
76
|
installed: false,
|
|
77
77
|
cache: new WeakMap(),
|
|
@@ -88,9 +88,9 @@ module.exports = function (deps) {
|
|
|
88
88
|
try {
|
|
89
89
|
let unwritten = contextualize(code);
|
|
90
90
|
unwritten = rewriter.unwriteSync(unwritten);
|
|
91
|
-
unwritten =
|
|
92
|
-
unwritten =
|
|
93
|
-
unwritten =
|
|
91
|
+
unwritten = StringPrototypeReplace.call(unwritten, FUNCTION_DECL, '');
|
|
92
|
+
unwritten = StringPrototypeReplace.call(unwritten, VARIABLE_DECL, '');
|
|
93
|
+
unwritten = StringPrototypeReplace.call(unwritten, /;\s*$/, ''); // removes trailing semicolon/whitespace
|
|
94
94
|
return unwritten;
|
|
95
95
|
} catch (err) {
|
|
96
96
|
logger.warn({ err, code }, 'Failed to unwrite function code');
|
|
@@ -114,7 +114,7 @@ module.exports = function (deps) {
|
|
|
114
114
|
|
|
115
115
|
let code;
|
|
116
116
|
if (hookedFunctions.has(data.obj)) {
|
|
117
|
-
code =
|
|
117
|
+
code = FunctionPrototypeToString.call(hookedFunctions.get(data.obj).fn);
|
|
118
118
|
} else {
|
|
119
119
|
code = orig();
|
|
120
120
|
}
|
|
@@ -135,7 +135,7 @@ module.exports = function (deps) {
|
|
|
135
135
|
};
|
|
136
136
|
|
|
137
137
|
functionHooks.uninstall = function () {
|
|
138
|
-
Function.prototype.toString =
|
|
138
|
+
Function.prototype.toString = FunctionPrototypeToString;
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
return functionHooks;
|
package/lib/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { Installable } from '@contrast/common';
|
|
17
17
|
import { Config } from '@contrast/config';
|
|
18
|
+
import { Perf } from '@contrast/perf';
|
|
18
19
|
import { Core as _Core } from '@contrast/core';
|
|
19
20
|
import { Deadzones } from '@contrast/deadzones';
|
|
20
21
|
import { DepHooks } from '@contrast/dep-hooks';
|
|
@@ -53,6 +54,7 @@ declare module 'node:module' {
|
|
|
53
54
|
|
|
54
55
|
export interface Core extends _Core {
|
|
55
56
|
config: Config;
|
|
57
|
+
Perf: Perf;
|
|
56
58
|
logger: Logger;
|
|
57
59
|
depHooks: DepHooks;
|
|
58
60
|
patcher: Patcher
|
package/lib/index.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const Module = require('module');
|
|
19
|
+
|
|
19
20
|
const {
|
|
20
21
|
assertValidOpts,
|
|
21
22
|
preStartupValidation,
|
|
@@ -53,7 +54,7 @@ const DEFAULT_INSTALL_ORDER = [
|
|
|
53
54
|
];
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
|
-
* Utility for making agents: entrypoints
|
|
57
|
+
* Utility for making agents: entrypoints intended to be run via --require, --loader, --import.
|
|
57
58
|
* Composes all needed dependencies during factory instantiation, and installs various components
|
|
58
59
|
* that alter an application's environment in order for composed features to operate e.g. request scopes,
|
|
59
60
|
* source code rewrite hooks etc.
|
|
@@ -62,9 +63,13 @@ const DEFAULT_INSTALL_ORDER = [
|
|
|
62
63
|
*/
|
|
63
64
|
module.exports = function init(core = {}) {
|
|
64
65
|
core.startTime = process.hrtime.bigint();
|
|
66
|
+
if (!core.Perf) {
|
|
67
|
+
core.Perf = require('@contrast/perf');
|
|
68
|
+
}
|
|
65
69
|
|
|
66
70
|
let _callback;
|
|
67
71
|
let _opts;
|
|
72
|
+
const _perf = new core.Perf('agentify');
|
|
68
73
|
|
|
69
74
|
core.agentify = async function agentify(callback, opts = {}) {
|
|
70
75
|
// options are hardcoded, so if this throws it's going to be in testing/dev situation
|
|
@@ -126,7 +131,10 @@ module.exports = function init(core = {}) {
|
|
|
126
131
|
await plugin.install();
|
|
127
132
|
}
|
|
128
133
|
} catch (err) {
|
|
129
|
-
|
|
134
|
+
console.error(err);
|
|
135
|
+
console.error(ERROR_MESSAGE);
|
|
136
|
+
|
|
137
|
+
// TODO: Consider proper UNINSTALLATION and normal startup w/o agent
|
|
130
138
|
logger.error({ err }, ERROR_MESSAGE);
|
|
131
139
|
}
|
|
132
140
|
}
|
|
@@ -194,7 +202,7 @@ module.exports = function init(core = {}) {
|
|
|
194
202
|
if (isDefault) {
|
|
195
203
|
mod = mod.default;
|
|
196
204
|
}
|
|
197
|
-
mod(core);
|
|
205
|
+
_perf.wrapInit(mod, spec)(core);
|
|
198
206
|
|
|
199
207
|
// perform any validations that can take place now that this module is loaded.
|
|
200
208
|
if (name === 'logger') {
|
|
@@ -215,11 +223,10 @@ module.exports = function init(core = {}) {
|
|
|
215
223
|
// IntentionalError is used when the agent is disabled by config. We want
|
|
216
224
|
// to abort the installation but not issue any messages.
|
|
217
225
|
if (!(err instanceof IntentionalError)) {
|
|
226
|
+
console.error(err);
|
|
227
|
+
console.error(ERROR_MESSAGE);
|
|
218
228
|
if (core.logger) {
|
|
219
|
-
core.logger
|
|
220
|
-
} else {
|
|
221
|
-
console.error(err);
|
|
222
|
-
console.error(ERROR_MESSAGE);
|
|
229
|
+
core.logger?.error?.({ err }, ERROR_MESSAGE);
|
|
223
230
|
}
|
|
224
231
|
}
|
|
225
232
|
}
|
package/lib/index.test.js
CHANGED
|
@@ -4,10 +4,17 @@ const { expect } = require('chai');
|
|
|
4
4
|
const proxyquire = require('proxyquire');
|
|
5
5
|
const sinon = require('sinon');
|
|
6
6
|
const mocks = require('@contrast/test/mocks');
|
|
7
|
+
const Perf = require('@contrast/perf');
|
|
7
8
|
|
|
8
9
|
describe('agentify', function () {
|
|
9
10
|
const Module = require('module');
|
|
10
11
|
let agentify, preRunMain, runMain;
|
|
12
|
+
const allAgentify = new Map();
|
|
13
|
+
// the sinon stubs are captured in beforeEach(), before the agentify call,
|
|
14
|
+
// because the Perf wrapper will replace them on the core object. this way we
|
|
15
|
+
// can still determine that the function was called/not called as expected.
|
|
16
|
+
let loggerError;
|
|
17
|
+
let depHooksInstall;
|
|
11
18
|
|
|
12
19
|
beforeEach(function () {
|
|
13
20
|
// added because the config validation code verifies that the agent has
|
|
@@ -19,21 +26,33 @@ describe('agentify', function () {
|
|
|
19
26
|
agentify = proxyquire('.', {
|
|
20
27
|
'@contrast/logger': {
|
|
21
28
|
default(core) {
|
|
22
|
-
|
|
29
|
+
core.logger = mocks.logger();
|
|
30
|
+
loggerError = core.logger.error;
|
|
31
|
+
return core.logger;
|
|
23
32
|
}
|
|
24
33
|
},
|
|
25
34
|
'@contrast/dep-hooks'(core) {
|
|
26
|
-
|
|
35
|
+
core.depHooks = mocks.depHooks();
|
|
36
|
+
depHooksInstall = core.depHooks.install;
|
|
37
|
+
return core.depHooks;
|
|
27
38
|
},
|
|
28
39
|
})();
|
|
29
40
|
preRunMain = sinon.stub();
|
|
30
41
|
runMain = sinon.stub(Module, 'runMain');
|
|
31
42
|
});
|
|
32
43
|
|
|
44
|
+
afterEach(function() {
|
|
45
|
+
Perf.fromAllToMap('agentify', allAgentify);
|
|
46
|
+
});
|
|
47
|
+
|
|
33
48
|
after(function() {
|
|
34
49
|
delete process.env.CONTRAST__API__API_KEY;
|
|
35
50
|
delete process.env.CONTRAST__API__SERVICE_KEY;
|
|
36
51
|
delete process.env.CONTRAST__API_USER_NAME;
|
|
52
|
+
const stats = Perf.getStats(allAgentify);
|
|
53
|
+
for (const [key, { n, totalMicros, mean }] of stats.entries()) {
|
|
54
|
+
console.log(key, n, totalMicros, 'nsec', mean, 'mean');
|
|
55
|
+
}
|
|
37
56
|
});
|
|
38
57
|
|
|
39
58
|
it('invoking with callback will initialize and patch runMain', async function () {
|
|
@@ -46,16 +65,16 @@ describe('agentify', function () {
|
|
|
46
65
|
|
|
47
66
|
expect(preRunMain).to.have.been.calledWith(core);
|
|
48
67
|
expect(runMain).to.have.been.called;
|
|
49
|
-
expect(
|
|
50
|
-
expect(
|
|
68
|
+
expect(depHooksInstall).to.have.been.called;
|
|
69
|
+
expect(loggerError).not.to.have.been.called;
|
|
51
70
|
});
|
|
52
71
|
|
|
53
72
|
it('will not run install methods when installOrder option is empty array', async function () {
|
|
54
|
-
const
|
|
73
|
+
const _core = await agentify(preRunMain, { installOrder: [] });
|
|
55
74
|
await Module.runMain();
|
|
56
75
|
|
|
57
|
-
expect(
|
|
58
|
-
expect(
|
|
76
|
+
expect(depHooksInstall).not.called;
|
|
77
|
+
expect(loggerError).not.called;
|
|
59
78
|
});
|
|
60
79
|
|
|
61
80
|
|
|
@@ -121,7 +140,7 @@ describe('agentify', function () {
|
|
|
121
140
|
|
|
122
141
|
await Module.runMain();
|
|
123
142
|
|
|
124
|
-
expect(
|
|
143
|
+
expect(depHooksInstall).not.to.have.been.called;
|
|
125
144
|
expect(runMain).to.have.been.called;
|
|
126
145
|
expect(core.logger.error).to.have.been.calledWith({ err });
|
|
127
146
|
});
|
|
@@ -37,9 +37,10 @@ const DEADZONED_PATHS = [
|
|
|
37
37
|
'browserify',
|
|
38
38
|
'bson',
|
|
39
39
|
'bunyan',
|
|
40
|
-
'
|
|
40
|
+
'chai', // not sure why chai was rewritten
|
|
41
41
|
'coffeescript',
|
|
42
42
|
'compression',
|
|
43
|
+
'@cyclonedx',
|
|
43
44
|
'etag',
|
|
44
45
|
// 'cookie', // todo: verify this doesn't break sources/propagation (*)
|
|
45
46
|
// 'cookie-signature', // (*)
|
package/lib/utils.js
CHANGED
|
@@ -25,12 +25,15 @@ const {
|
|
|
25
25
|
}
|
|
26
26
|
} = require('../package.json');
|
|
27
27
|
|
|
28
|
+
const { primordials: { StringPrototypeSlice, StringPrototypeSplit, StringPrototypeTrim } } = require('@contrast/common');
|
|
29
|
+
|
|
28
30
|
/**
|
|
29
31
|
* Performs various startup validation checks. All checks throw errors when they
|
|
30
32
|
* fail so startup/installation stops.
|
|
31
33
|
* @param {string} core.nodeEngines
|
|
32
34
|
*/
|
|
33
35
|
function preStartupValidation(core) {
|
|
36
|
+
assertNoExperimentalFeatureFlags();
|
|
34
37
|
assertSupportedNodeVersion(core.nodeEngines || nodeEngines);
|
|
35
38
|
assertSupportedPreloadUsage();
|
|
36
39
|
}
|
|
@@ -45,16 +48,31 @@ function preStartupValidation(core) {
|
|
|
45
48
|
function assertSupportedNodeVersion(engines) {
|
|
46
49
|
if (!semver.satisfies(process.version, engines)) {
|
|
47
50
|
let validRanges = '';
|
|
48
|
-
|
|
51
|
+
StringPrototypeSplit.call(engines, '||').forEach((range) => {
|
|
49
52
|
const minVersion = semver.minVersion(range).toString();
|
|
50
|
-
const maxVersion =
|
|
51
|
-
.trim();
|
|
53
|
+
const maxVersion = StringPrototypeTrim.call(StringPrototypeSplit.call(range, '<').pop());
|
|
52
54
|
validRanges += `${minVersion} and ${maxVersion}, `;
|
|
53
55
|
});
|
|
54
56
|
throw new Error(`Contrast only officially supports Node LTS versions between ${validRanges}but detected ${process.version}.`);
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Checks that no experimental feature flags are used.
|
|
62
|
+
* @throws {Error}
|
|
63
|
+
*/
|
|
64
|
+
function assertNoExperimentalFeatureFlags() {
|
|
65
|
+
const {
|
|
66
|
+
execArgv,
|
|
67
|
+
env: { NODE_OPTIONS },
|
|
68
|
+
} = process;
|
|
69
|
+
|
|
70
|
+
if (execArgv.some(arg => arg.includes('--experimental')) || NODE_OPTIONS?.includes('--experimental')) {
|
|
71
|
+
const msg = 'Contrast Agent does not support experimental features.';
|
|
72
|
+
throw new Error(msg);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
58
76
|
/**
|
|
59
77
|
* Checks that the correct preload flag is used given running node version.
|
|
60
78
|
* @throws {Error}
|
|
@@ -72,8 +90,8 @@ function assertSupportedPreloadUsage() {
|
|
|
72
90
|
if (CSI_EXPOSE_CORE === 'no-validate') return;
|
|
73
91
|
|
|
74
92
|
// eslint-disable-next-line newline-per-chained-call
|
|
75
|
-
const [major, minor] =
|
|
76
|
-
const nodeOptionArgs = (NODE_OPTIONS || ''
|
|
93
|
+
const [major, minor] = StringPrototypeSplit.call(StringPrototypeSlice.call(version, 1), '.').map(Number);
|
|
94
|
+
const nodeOptionArgs = StringPrototypeSplit.call(NODE_OPTIONS || '', /\s+/).filter((v) => v);
|
|
77
95
|
const allArgsToCheck = [...execArgv, ...nodeOptionArgs];
|
|
78
96
|
|
|
79
97
|
let checkOccurred = false;
|
|
@@ -150,5 +168,6 @@ module.exports = {
|
|
|
150
168
|
assertValidOpts,
|
|
151
169
|
assertSupportedNodeVersion,
|
|
152
170
|
assertSupportedPreloadUsage,
|
|
171
|
+
assertNoExperimentalFeatureFlags,
|
|
153
172
|
preStartupValidation,
|
|
154
173
|
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { expect } = require('chai');
|
|
4
|
+
const sinon = require('sinon');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
assertValidOpts,
|
|
8
|
+
assertSupportedNodeVersion,
|
|
9
|
+
assertSupportedPreloadUsage,
|
|
10
|
+
assertNoExperimentalFeatureFlags,
|
|
11
|
+
} = require('./utils');
|
|
12
|
+
|
|
13
|
+
describe('preStartupValidation', function() {
|
|
14
|
+
|
|
15
|
+
describe('assertValidOpts', function() {
|
|
16
|
+
it('Does not validate when noValidate is true', function() {
|
|
17
|
+
const opts = {
|
|
18
|
+
noValidate: true
|
|
19
|
+
};
|
|
20
|
+
expect(assertValidOpts(opts)).to.be.undefined;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('Throws an error if install order is incorrect', function() {
|
|
24
|
+
const opts = {
|
|
25
|
+
installOrder: [
|
|
26
|
+
'telemetry',
|
|
27
|
+
'reporter',
|
|
28
|
+
'startupValidation'
|
|
29
|
+
]
|
|
30
|
+
};
|
|
31
|
+
expect(() => assertValidOpts(opts)).to.throw('The \'installOrder\' option must include \'reporter\' as first element. found \'telemetry\'');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('assertSupportedNodeVersion', function() {
|
|
36
|
+
|
|
37
|
+
let engines;
|
|
38
|
+
beforeEach(function() {
|
|
39
|
+
engines = '>=16.9.1 <17 || >=18.7.0 <19 || >=20.6.0 <21 || >= 22.5.1 <23';
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('Does not throw an error if process.version is supported', function() {
|
|
43
|
+
sinon.stub(process, 'version').value('v22.9.0');
|
|
44
|
+
expect(assertSupportedNodeVersion(engines)).to.be.undefined;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('Throws an error if process.version is unsupported', function() {
|
|
48
|
+
sinon.stub(process, 'version').value('v14.0.0');
|
|
49
|
+
expect(() => assertSupportedNodeVersion(engines)).to.throw('Contrast only officially supports Node LTS versions between 16.9.1 and 17, 18.7.0 and 19, 20.6.0 and 21, 22.5.1 and 23, but detected v14.0.0');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('assertSupportedPreloadUsage', function() {
|
|
55
|
+
|
|
56
|
+
it('does not throw an err if --import flag is used (>= 18.19.0)', function() {
|
|
57
|
+
sinon.stub(process, 'execArgv').value(['--import', '@contrast/agent']);
|
|
58
|
+
sinon.stub(process, 'version').value('v22.9.0');
|
|
59
|
+
expect(assertSupportedPreloadUsage()).to.be.true;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('throws an err if --import flag is used (<= 18.19.0)', function() {
|
|
63
|
+
sinon.stub(process, 'execArgv').value(['--import', '@contrast/agent']);
|
|
64
|
+
sinon.stub(process, 'version').value('v18.18.0');
|
|
65
|
+
expect(() => assertSupportedPreloadUsage()).to.throw('Contrast requires that Node LTS versions >= 18.19.0 use the --import flag for ESM support');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('throws an err if --loader flag is used (>= 18.19.0)', function() {
|
|
69
|
+
sinon.stub(process, 'execArgv').value(['--loader', '@contrast/agent']);
|
|
70
|
+
sinon.stub(process, 'version').value('v18.20.0');
|
|
71
|
+
expect(() => assertSupportedPreloadUsage()).to.throw('Contrast requires that Node versions less than 18.19.0 use the --loader flag for ESM support');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('assertNoExperimentalFeatureFlags', function() {
|
|
77
|
+
describe('process.execArgv', function() {
|
|
78
|
+
it('Does not throw an error if no experimental feature flag is used', function() {
|
|
79
|
+
sinon.stub(process, 'execArgv').value(['--import', '@contrast/agent']);
|
|
80
|
+
expect(assertNoExperimentalFeatureFlags()).to.be.undefined;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('Throws an error if an experimental feature flag is used', function() {
|
|
84
|
+
sinon.stub(process, 'execArgv').value(['--experimental-feature', '--import', '@contrast/agent']);
|
|
85
|
+
expect(() => assertNoExperimentalFeatureFlags()).to.throw('Contrast Agent does not support experimental features.');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('NODE_OPTIONS', function() {
|
|
90
|
+
|
|
91
|
+
const origNodeOptions = process.env?.NODE_OPTIONS;
|
|
92
|
+
|
|
93
|
+
afterEach(function() {
|
|
94
|
+
process.env.NODE_OPTIONS = origNodeOptions;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('Does not throw an error if no experimental feature flag is used', function() {
|
|
98
|
+
process.env.NODE_OPTIONS = '--import @contrast/agent';
|
|
99
|
+
expect(assertNoExperimentalFeatureFlags()).to.be.undefined;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('Throws an error if an experimental feature flag is used', function() {
|
|
103
|
+
process.env.NODE_OPTIONS = '--experimental-feature --import @contrast/agent';
|
|
104
|
+
expect(() => assertNoExperimentalFeatureFlags()).to.throw('Contrast Agent does not support experimental features.');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
});
|
package/lib/validators.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { IntentionalError } = require('@contrast/common');
|
|
18
|
+
const { IntentionalError, primordials: { ArrayPrototypeSlice, StringPrototypeToLowerCase } } = require('@contrast/common');
|
|
19
19
|
|
|
20
20
|
// v4 accepted `-c` or `--configFile` option in argv, but v5 does not. so if
|
|
21
21
|
// something that looks like a config flag is present on the command line, we
|
|
@@ -49,8 +49,8 @@ function getConfigFlag(argv) {
|
|
|
49
49
|
// let's not because that seems false-negative prone.
|
|
50
50
|
|
|
51
51
|
// if the next arg is another flag, then this isn't a contrast config flag
|
|
52
|
-
if (!argv[i + 1].startsWith('-') && argv[i + 1]
|
|
53
|
-
return
|
|
52
|
+
if (!argv[i + 1].startsWith('-') && StringPrototypeToLowerCase.call(argv[i + 1]).includes('contrast')) {
|
|
53
|
+
return ArrayPrototypeSlice.call(argv, i, i + 2);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/agentify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.0",
|
|
4
4
|
"description": "Configures Contrast agent services and instrumentation within an application",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -17,20 +17,21 @@
|
|
|
17
17
|
"test": "../scripts/test.sh"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@contrast/common": "1.
|
|
21
|
-
"@contrast/config": "1.
|
|
22
|
-
"@contrast/core": "1.
|
|
23
|
-
"@contrast/deadzones": "1.
|
|
24
|
-
"@contrast/dep-hooks": "1.
|
|
25
|
-
"@contrast/esm-hooks": "2.
|
|
20
|
+
"@contrast/common": "1.26.0",
|
|
21
|
+
"@contrast/config": "1.34.0",
|
|
22
|
+
"@contrast/core": "1.39.0",
|
|
23
|
+
"@contrast/deadzones": "1.9.0",
|
|
24
|
+
"@contrast/dep-hooks": "1.7.0",
|
|
25
|
+
"@contrast/esm-hooks": "2.13.0",
|
|
26
26
|
"@contrast/find-package-json": "^1.1.0",
|
|
27
|
-
"@contrast/instrumentation": "1.
|
|
28
|
-
"@contrast/logger": "1.
|
|
29
|
-
"@contrast/metrics": "1.
|
|
30
|
-
"@contrast/patcher": "1.
|
|
31
|
-
"@contrast/
|
|
32
|
-
"@contrast/
|
|
33
|
-
"@contrast/
|
|
27
|
+
"@contrast/instrumentation": "1.17.0",
|
|
28
|
+
"@contrast/logger": "1.12.0",
|
|
29
|
+
"@contrast/metrics": "1.15.0",
|
|
30
|
+
"@contrast/patcher": "1.11.0",
|
|
31
|
+
"@contrast/perf": "1.2.0",
|
|
32
|
+
"@contrast/reporter": "1.34.0",
|
|
33
|
+
"@contrast/rewriter": "1.15.0",
|
|
34
|
+
"@contrast/scopes": "1.8.0",
|
|
34
35
|
"semver": "^7.6.0"
|
|
35
36
|
}
|
|
36
37
|
}
|