@contrast/core 1.28.0 → 1.29.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.
Files changed (2) hide show
  1. package/lib/app-info.js +142 -149
  2. package/package.json +3 -2
package/lib/app-info.js CHANGED
@@ -19,190 +19,183 @@ 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
- const MAX_ATTEMPTS = 10; // temporary until test dir is flattened a bit
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
- const appInfo = (core.appInfo = {
36
+ let cmd;
37
+ let entrypoint;
38
+ let pkgInfo;
39
+ let err;
40
+
41
+ try {
42
+ cmd = getCommand();
43
+ entrypoint = getEntrypoint();
44
+ pkgInfo = getPackageInfo(entrypoint);
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
- let cmd, _path, pkg;
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
- try {
41
- const entrypoint = config.script || config.entrypoint;
42
- if (entrypoint) {
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
- _path = appInfo.path = resolveAppPath(
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
- appInfo.serverVersion = config.server.version;
62
- appInfo.node_version = process.version;
63
-
64
- appInfo.appPath = config.application.path || appInfo.app_dir;
65
- appInfo.indexFile = cmd;
66
- appInfo.serverName = config.server.name;
67
- appInfo.serverType = config.server.type;
68
- appInfo.serverEnvironment = config.server.environment;
69
- appInfo.group = config.application.group;
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];
76
118
 
77
- if (appRoot) {
78
- packageLocation = findFile({ directory: appRoot, file: 'package.json' });
79
- }
119
+ try {
120
+ if (entrypoint && fs.statSync(entrypoint).isDirectory()) {
121
+ const indexJs = path.join(entrypoint, './index.js');
122
+ try {
123
+ if (fs.statSync(indexJs)) {
124
+ entrypoint = indexJs;
125
+ }
126
+ } catch (err) {
127
+ entrypoint = null;
128
+ }
129
+ }
130
+ } catch (err) {} // eslint-disable-line no-empty
80
131
 
81
- if (!packageLocation) {
82
- packageLocation = findFile({
83
- directory: scriptPath,
84
- file: 'package.json',
85
- });
132
+ if (!entrypoint) {
133
+ logger.error('no entrypoint found for application');
134
+ throw new Error('No entrypoint found');
86
135
  }
87
136
 
88
- if (packageLocation) {
89
- core.logger.info('using package.json at %s', packageLocation);
137
+ if (exclusive_entrypoint) {
138
+ const expectedEntrypoint = path.resolve(app_root || process.cwd(), exclusive_entrypoint);
139
+ if (entrypoint !== expectedEntrypoint) {
140
+ const message = 'application does not match exclusive_entrypoint config option';
141
+ logger.trace({
142
+ entrypoint,
143
+ exclusive_entrypoint: expectedEntrypoint,
144
+ }, message);
145
+ throw new IntentionalError(message);
146
+ }
90
147
  }
91
148
 
92
- return packageLocation;
149
+ return entrypoint;
93
150
  }
94
151
 
95
152
  /**
96
- * Gets contents of a directory
97
- *
98
- * @param {String} directory
99
- * @param {Array} contents of a directory
153
+ * Will try to read the `package.json` file of the app. This will use find-pacakge-json
154
+ * starting first from entrypoint, then from CWD.
155
+ * NOTE: If the `app_root` value is specified, this will check only there and then throw if not found.
156
+ * @param {string} entrypoint app entrypoint
157
+ * @returns {PackageInfo} dir, packageData, and packageFile
158
+ * @throws {Error} if package can't be found or parsed
100
159
  */
101
- function getDirectoryEntries(directory) {
102
- try {
103
- return fs.readdirSync(directory);
104
- } catch (err) {
105
- logger.error({ err }, 'error reading directory %s', directory);
106
- return [];
107
- }
108
- }
160
+ function getPackageInfo(entrypoint) {
161
+ const cwd = process.cwd();
162
+ const dirs = new Set();
109
163
 
110
- /**
111
- * Filters files with name matching the file param
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
- }
164
+ let dir;
165
+ let packageData;
166
+ let packageFile;
123
167
 
124
- /**
125
- * Checks each direct sibling folder for file
126
- *
127
- * @param {Object} params
128
- * @param {String} params.file file to check
129
- * @param {String} params.directory path to check for file
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;
168
+ // if this is not the default value, we should only check it
169
+ if (app_root && app_root !== cwd) {
170
+ dirs.add(path.resolve(app_root));
171
+ } else {
172
+ // otherwise check up folder tree from entrypoint and then cwd
173
+ dirs.add(path.dirname(entrypoint));
174
+ dirs.add(cwd);
176
175
  }
177
176
 
178
- attempts++;
179
- directory = path.resolve(directory);
180
-
181
- const entries = getDirectoryEntries(directory);
182
- const location = filterFiles(directory, entries, file);
183
- if (location) {
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
- // we only want to check files for each directory in the intial call to this function
188
- if (checkNested) {
189
- const foundFiles = [];
190
- checkNestedFolders({ directory, entries, file, foundFiles });
191
- // we found a file in the child folders return path to it
192
- if (foundFiles.length > 0) {
193
- return foundFiles[0];
194
- }
185
+ if (!packageData) {
186
+ const message = 'unable to locate application package.json';
187
+ logger.error({
188
+ app_root,
189
+ entrypoint,
190
+ paths: Array.from(dirs),
191
+ }, message);
192
+ throw new Error(message);
195
193
  }
196
194
 
197
- // we only want to look at dirs in the initial directory
198
- // assign parent directory to directory
199
- directory = path.dirname(directory);
200
- return findFile({
201
- directory,
202
- file,
203
- maxAttempts,
204
- attempts,
205
- checkNested: false,
206
- });
195
+ return {
196
+ dir: path.dirname(packageFile),
197
+ packageData,
198
+ packageFile,
199
+ };
207
200
  }
208
201
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/core",
3
- "version": "1.28.0",
3
+ "version": "1.29.0",
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.17.0",
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
  }