@contrast/core 1.28.0 → 1.29.1
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/app-info.js +141 -149
- package/package.json +3 -2
package/lib/app-info.js
CHANGED
|
@@ -19,190 +19,182 @@ const os = require('os');
|
|
|
19
19
|
const path = require('path');
|
|
20
20
|
const fs = require('fs');
|
|
21
21
|
const process = require('process');
|
|
22
|
+
const { IntentionalError } = require('@contrast/common');
|
|
23
|
+
const { findPackageJsonSync } = require('@contrast/find-package-json');
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} PackageInfo
|
|
27
|
+
* @property {string} dir - the directory containing the `package.json`
|
|
28
|
+
* @property {object} packageData - parsed package contents
|
|
29
|
+
* @property {string} packageFile - filename of `package.json` (abs path)
|
|
30
|
+
*/
|
|
24
31
|
|
|
25
32
|
module.exports = function (core) {
|
|
26
33
|
const { logger, config } = core;
|
|
34
|
+
const { app_root, cmd_ignore_list, exclusive_entrypoint } = config.agent.node;
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
let cmd;
|
|
37
|
+
let entrypoint;
|
|
38
|
+
let pkgInfo;
|
|
39
|
+
let err;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
cmd = getCommand();
|
|
43
|
+
pkgInfo = getPackageInfo();
|
|
44
|
+
entrypoint = getEntrypoint();
|
|
45
|
+
} catch (_err) {
|
|
46
|
+
err = _err;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return core.appInfo = !err ? {
|
|
50
|
+
// dedupe this? - it's already in systemInfo
|
|
29
51
|
os: {
|
|
30
52
|
type: os.type(),
|
|
31
53
|
platform: os.platform(),
|
|
32
54
|
architecture: os.arch(),
|
|
33
55
|
release: os.release(),
|
|
34
56
|
},
|
|
57
|
+
cmd,
|
|
35
58
|
hostname: os.hostname(),
|
|
36
|
-
|
|
59
|
+
indexFile: entrypoint,
|
|
60
|
+
path: pkgInfo.packageFile,
|
|
61
|
+
pkg: pkgInfo.packageData,
|
|
62
|
+
name: config.application.name || pkgInfo.packageData.name,
|
|
63
|
+
app_dir: pkgInfo.dir,
|
|
64
|
+
version: config.application.version || pkgInfo.packageData.version,
|
|
65
|
+
serverVersion: config.server.version,
|
|
66
|
+
node_version: process.version,
|
|
67
|
+
appPath: config.application.path || pkgInfo.dir,
|
|
68
|
+
serverName: config.server.name,
|
|
69
|
+
serverType: config.server.type,
|
|
70
|
+
serverEnvironment: config.server.environment,
|
|
71
|
+
group: config.application.group,
|
|
72
|
+
metadata: config.application.metadata,
|
|
73
|
+
} : {
|
|
74
|
+
_errors: [err]
|
|
75
|
+
};
|
|
37
76
|
|
|
38
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Generates a command string based on ARGV and process.argv0, which will be used
|
|
79
|
+
* for the `appInfo.cmd` field. This function will throw if any of the config's
|
|
80
|
+
* cmd_ignore_list values match the command.
|
|
81
|
+
* @returns {string} the issued command that started the current node process
|
|
82
|
+
* @throws {IntentionalError} when command should be is ignored by Contrast
|
|
83
|
+
*/
|
|
84
|
+
function getCommand() {
|
|
85
|
+
const args = [process.argv0, ...process.argv].map((a) => path.basename(a));
|
|
86
|
+
const cmd = Array.from(new Set(args)).join(' ');
|
|
87
|
+
const message = 'application command matches cmd_ignore_list config option';
|
|
88
|
+
let err;
|
|
89
|
+
|
|
90
|
+
if (cmd_ignore_list) {
|
|
91
|
+
for (const ignoreCommand of cmd_ignore_list) {
|
|
92
|
+
if (err) break;
|
|
93
|
+
|
|
94
|
+
if (ignoreCommand === 'npm*') {
|
|
95
|
+
if (cmd.includes('npm ')) err = new IntentionalError(message);
|
|
96
|
+
} else {
|
|
97
|
+
if (cmd.includes(ignoreCommand)) err = new IntentionalError(message);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
39
101
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
cmd = appInfo.cmd = path.resolve(entrypoint);
|
|
44
|
-
} else {
|
|
45
|
-
appInfo.cmd = undefined;
|
|
102
|
+
if (err) {
|
|
103
|
+
logger.trace({ cmd_ignore_list, cmd }, message);
|
|
104
|
+
throw err;
|
|
46
105
|
}
|
|
47
106
|
|
|
48
|
-
|
|
49
|
-
config.agent.node.app_root,
|
|
50
|
-
cmd ? path.dirname(cmd) : undefined
|
|
51
|
-
);
|
|
52
|
-
pkg = require(_path);
|
|
53
|
-
appInfo.pkg = pkg;
|
|
54
|
-
appInfo.name = config.application.name || pkg.name;
|
|
55
|
-
appInfo.app_dir = path.dirname(appInfo.path);
|
|
56
|
-
appInfo.version = config.application.version || pkg.version;
|
|
57
|
-
} catch (e) {
|
|
58
|
-
throw new Error(`Unable to find application's package.json: ${_path}`);
|
|
107
|
+
return cmd;
|
|
59
108
|
}
|
|
60
109
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
appInfo.metadata = config.application.metadata;
|
|
71
|
-
|
|
72
|
-
return appInfo;
|
|
73
|
-
|
|
74
|
-
function resolveAppPath(appRoot, scriptPath) {
|
|
75
|
-
let packageLocation;
|
|
110
|
+
/**
|
|
111
|
+
* Returns the entrypoint file. If none is found, or the one discovered doesn't match the
|
|
112
|
+
* config's `agent.node.exclusive_entrypoint` value, this will throw.
|
|
113
|
+
* @returns {string} entrypoint file name
|
|
114
|
+
* @throws {Error|IntentionalError} if no entrypoint is found or we're supposed to ignore the app
|
|
115
|
+
*/
|
|
116
|
+
function getEntrypoint() {
|
|
117
|
+
let entrypoint = process.argv[1];
|
|
118
|
+
const { packageData } = pkgInfo;
|
|
76
119
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
120
|
+
try {
|
|
121
|
+
if (entrypoint && fs.statSync(entrypoint).isDirectory()) {
|
|
122
|
+
const main = path.join(entrypoint, packageData.main || 'index.js');
|
|
123
|
+
try {
|
|
124
|
+
if (fs.statSync(main)) {
|
|
125
|
+
entrypoint = main;
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
entrypoint = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {} // eslint-disable-line no-empty
|
|
80
132
|
|
|
81
|
-
if (!
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
file: 'package.json',
|
|
85
|
-
});
|
|
133
|
+
if (!entrypoint) {
|
|
134
|
+
logger.error('no entrypoint found for application');
|
|
135
|
+
throw new Error('No entrypoint found');
|
|
86
136
|
}
|
|
87
137
|
|
|
88
|
-
if (
|
|
89
|
-
|
|
138
|
+
if (exclusive_entrypoint) {
|
|
139
|
+
const expectedEntrypoint = path.resolve(app_root || process.cwd(), exclusive_entrypoint);
|
|
140
|
+
if (entrypoint !== expectedEntrypoint) {
|
|
141
|
+
const message = 'application does not match exclusive_entrypoint config option';
|
|
142
|
+
logger.trace({
|
|
143
|
+
entrypoint,
|
|
144
|
+
exclusive_entrypoint: expectedEntrypoint,
|
|
145
|
+
}, message);
|
|
146
|
+
throw new IntentionalError(message);
|
|
147
|
+
}
|
|
90
148
|
}
|
|
91
149
|
|
|
92
|
-
return
|
|
150
|
+
return entrypoint;
|
|
93
151
|
}
|
|
94
152
|
|
|
95
153
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* @param {
|
|
154
|
+
* Will try to read the `package.json` file of the app. This will use find-pacakge-json
|
|
155
|
+
* starting first from entrypoint, then from CWD.
|
|
156
|
+
* NOTE: If the `app_root` value is specified, this will check only there and then throw if not found.
|
|
157
|
+
* @param {string} entrypoint app entrypoint
|
|
158
|
+
* @returns {PackageInfo} dir, packageData, and packageFile
|
|
159
|
+
* @throws {Error} if package can't be found or parsed
|
|
100
160
|
*/
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
} catch (err) {
|
|
105
|
-
logger.error({ err }, 'error reading directory %s', directory);
|
|
106
|
-
return [];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
161
|
+
function getPackageInfo() {
|
|
162
|
+
const cwd = process.cwd();
|
|
163
|
+
const dirs = new Set();
|
|
109
164
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
* @param {String} directory path to check for file
|
|
114
|
-
* @param {Array} files list of entries in a directory
|
|
115
|
-
* @param {String} file file to check
|
|
116
|
-
*
|
|
117
|
-
* @return {*} path to file file or false
|
|
118
|
-
*/
|
|
119
|
-
function filterFiles(directory, files, file) {
|
|
120
|
-
const hit = files.filter((entry) => entry === file)[0];
|
|
121
|
-
return hit ? path.resolve(directory, hit) : false;
|
|
122
|
-
}
|
|
165
|
+
let dir;
|
|
166
|
+
let packageData;
|
|
167
|
+
let packageFile;
|
|
123
168
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
* @param {Array} params.foundFiles array to hold found files
|
|
131
|
-
* @param {Array} params.entries list of directory contents
|
|
132
|
-
*/
|
|
133
|
-
function checkNestedFolders({ file, directory, entries, foundFiles }) {
|
|
134
|
-
entries.reduce((foundFiles, entry) => {
|
|
135
|
-
const resolvedEntry = path.resolve(directory, entry);
|
|
136
|
-
try {
|
|
137
|
-
if (!fs.statSync(resolvedEntry).isFile()) {
|
|
138
|
-
const path = filterFiles(
|
|
139
|
-
resolvedEntry,
|
|
140
|
-
getDirectoryEntries(resolvedEntry),
|
|
141
|
-
file
|
|
142
|
-
);
|
|
143
|
-
if (path) {
|
|
144
|
-
foundFiles.push(path);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
} catch (err) {
|
|
148
|
-
// swallow this error, we don't care
|
|
149
|
-
}
|
|
150
|
-
return foundFiles;
|
|
151
|
-
}, foundFiles);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Tries to find a file in entryPoint, 1 folder below or up to 5 directories
|
|
156
|
-
* above initial path
|
|
157
|
-
*
|
|
158
|
-
* @param {Object} params
|
|
159
|
-
* @param {String} params.directory path to check for file
|
|
160
|
-
* @param {String} params.file file to check
|
|
161
|
-
* @param {Int} [params.maxAttempts=5] max attempts to check above params.directory
|
|
162
|
-
* @param {Int} params.attempts current attempt to find file in path
|
|
163
|
-
* @param {Boolean} params.checkNested flag to check in nested directories
|
|
164
|
-
*
|
|
165
|
-
* @return {String} path to file
|
|
166
|
-
*/
|
|
167
|
-
function findFile({
|
|
168
|
-
directory,
|
|
169
|
-
file,
|
|
170
|
-
maxAttempts = MAX_ATTEMPTS,
|
|
171
|
-
attempts = 0,
|
|
172
|
-
checkNested = true,
|
|
173
|
-
}) {
|
|
174
|
-
if (attempts >= maxAttempts) {
|
|
175
|
-
return;
|
|
169
|
+
// if this is not the default value, we should only check it
|
|
170
|
+
if (app_root && app_root !== cwd) {
|
|
171
|
+
dirs.add(path.resolve(app_root));
|
|
172
|
+
} else {
|
|
173
|
+
// otherwise check up folder tree from entrypoint and then cwd
|
|
174
|
+
dirs.add(cwd);
|
|
176
175
|
}
|
|
177
176
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return location;
|
|
177
|
+
for (dir of dirs) {
|
|
178
|
+
try {
|
|
179
|
+
packageFile = findPackageJsonSync({ cwd: dir });
|
|
180
|
+
packageData = require(packageFile);
|
|
181
|
+
break;
|
|
182
|
+
} catch (err) {} // eslint-disable-line no-empty
|
|
185
183
|
}
|
|
186
184
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
185
|
+
if (!packageData) {
|
|
186
|
+
const message = 'unable to locate application package.json';
|
|
187
|
+
logger.error({
|
|
188
|
+
app_root,
|
|
189
|
+
paths: Array.from(dirs),
|
|
190
|
+
}, message);
|
|
191
|
+
throw new Error(message);
|
|
195
192
|
}
|
|
196
193
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
file,
|
|
203
|
-
maxAttempts,
|
|
204
|
-
attempts,
|
|
205
|
-
checkNested: false,
|
|
206
|
-
});
|
|
194
|
+
return {
|
|
195
|
+
dir: path.dirname(packageFile),
|
|
196
|
+
packageData,
|
|
197
|
+
packageFile,
|
|
198
|
+
};
|
|
207
199
|
}
|
|
208
200
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.1",
|
|
4
4
|
"description": "Preconfigured Contrast agent core services and models",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"test": "../scripts/test.sh"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@contrast/common": "1.
|
|
19
|
+
"@contrast/common": "1.18.0",
|
|
20
|
+
"@contrast/find-package-json": "^1.0.0",
|
|
20
21
|
"@contrast/fn-inspect": "^4.0.0"
|
|
21
22
|
}
|
|
22
23
|
}
|